没有标签是因为我真的不知道这算什么类型
这题我说不来大意你们还是看题面描述吧
小Z住的房子一共有n个人,他们每人有一个重要度。
房子的门上可以装若干把锁。
假设共有k把锁,命名为1到k。每把锁有一种对应的钥匙,也用1到k表示。
钥匙可以复制若干份并发给任意多个居民。每个人都可以持有若干钥匙,可以不持有钥匙。
如果几名居民钥匙的并集是全集,他们都在场时就能打开房门。
房东规定,一组居民都在场时能打开房门当且仅当他们的重要度加起来至少为m。
问至少需要给房间装多少把锁。即,求最小的k,使得可以适当地给居民们每人若干钥匙,使得任意一组重要度之和小于m的居民持有的钥匙不能打开所有房门,使得任意一组重要度之和大于等于m的居民持有的钥匙能打开所有房门。
样例输入
4 3
1 1 1 1
样例输出
6
对于前30%的测试数据,满足所有ai=1。
对于另外30%的测试数据,1≤n≤8。
对于100%的测试数据,1≤n≤20,1≤m≤1e9,任意居民重要度≤m。
第一眼看不出来是什么题。于是跑去做T3
看到20想了状压DP,但不知道该描述什么状态以及怎么转移。后来对着样例手玩了20分钟,终于第一次找到正确的方案(没错我样例都看不懂),然后由于我画出那个方案的方式非常特别,跟组合数学完全一致,于是用组合数过掉了前三十分。
公式就是 C m − 1 n C_{m-1}^n Cm−1n。至于原因……花二十分钟画出样例找规律?
正解就是推理,看完之后恍然大悟。推理如下:
{
答案是这样的居民子集个数x:重要度的和不足m,但加入任何一个新居民都将导致重要度的和大于等于m。
必要性:由于上面的集合重要度都不够,他们都至少缺一把锁。若不足x 把锁,这些子集中必有两个x,y 缺同一把锁l。把这两个子集x 和y 并起来,仍然缺l 这把锁,无法开门但现在子集的重要度已经达到n 了,与题目要求矛盾。
充分性:一共x 把锁,每把锁上面各写一个这种居民的子集(互不相同)。一个居民持有所
有上面的子集不包括自己的锁的钥匙,这样满足要求。
注意到如果所有人加起来重要度都不够,则需要一把锁,无人有钥匙。
}
所以暴力一点跑个dfs就过去了~(当然这个dfs是可以优化的)
#include
#define rint register int
#define iint inline int
#define ivoid inline void
#define endll '\n'
#define ll long long
using namespace std;
const int N=2e6+5;
const int M=3e3+5;
const int inf=0x3f3f3f3f;
int m,n,q,k,x,y,z,u,v,w,s,t,l,r;
ll a[21];
int vis[21];
int sum,cnt,ans,res,num,tot,mx;
iint rad()
{
int x=0,f=1;char c;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
ivoid dfs(int x,ll sum)
{
if(x==n+1){
if(sum>=m)return;
for(rint i=1;i<=n;i++){
if(!vis[i]&&a[i]+sum<m)return;
}ans++;return;
}
vis[x]=1;
dfs(x+1,sum+a[x]);
vis[x]=0;
dfs(x+1,sum);
}
int main()
{
// freopen("lock.in","r",stdin);
// freopen("lock.out","w",stdout);
n=rad();m=rad();
for(rint i=1;i<=n;i++)a[i]=rad();
dfs(1,0);
cout<<ans;
return 0;
}