20191005 T2
原题链接
By GKurumi
题目描述:
升级过技能的小P已经具备开团能力,他现在有N个技能,准备单挑对面的M个人
但小P血非常少,所以他必须要控制住对面所有M个人,不然就会死掉
已知第i种技能可以控制住Ki个人,分别为(a_{i1},a_{i2}……,a_{ik})
问有多少种放技能的方式控住对面所有(M)个人(控制技能只能放一次,只有顺序不同的方式被认为是同一种)
答案对1000000007取模
输入格式:
第一行两个整数(N)、(M)
接下来N行,每行第一个整数表示Ki,接Ki个整数表示(a_{i1},a_{i2}……,a_{ik})
输出格式:
一个整数表示放技能的方式数目
样例
样例输入
4 5
2 2 3
2 1 2
4 1 2 3 5
4 1 2 4 5
样例输出
6
题解:
首先,我是我们机房最菜的人。
这道题乍一看有点像组合计数,但是我不会 太麻烦了推不出来,于是我就回想起那个月黑风高的夜晚发生的事统计方案用状压,于是就走上了一条不归路。
思路:我们用一个数组存下每个技能能打到的位置,之后开始状压dp,dp的时候在用另一个数组存此时用技能打第i位上敌人的方案数。跑一遍下来之后,再遍历查找一遍位置,当i位上有方案的时候就加上方案数,无方案的时候就减去(因为我一开始算了一遍总的方案数(鬼知道我怎么想的)(可能太想跟计数原理扯上关系)我爱计数原理!!也可以直接计算方案数,看个人的喜欢。
转移方程:if(i&(1<<j))f[i^(1<<j)]+=f[i],w[i]++;
计算方案数:(ans+=(w[i]&1?-1:1)*ksm(2,f[i]))%=mod;
以防出锅最后ans如果成了负数,emmm,就这么mod:(ans+mod)%mod
考试时卑微的代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define mod 1000000007
int f[1<<20],w[1<<20];
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch-'0');ch=getchar();}
return x*f;
}
int ksm(int a,int b)
{
int ans=1;
while(b)
{
if(b&1)ans=(ans*a)%mod;
b>>=1;
a=(a*a)%mod;
}
return ans;
}
int n,m,x,k,sum,ans;
signed main()
{
freopen("carry.in","r",stdin);
freopen("carry.out","w",stdout);
n=read();m=read();
sum=(1<<m)-1;
for(int i=1;i<=n;i++)
{
k=read();
int w=0;
for(int j=1;j<=k;j++)
{
x=read();
w|=(1<<(x-1));
}
f[w^sum]++;
}
ans=ksm(2,n);
for(int j=0;j<m;j++)
{
for(int i=(1<<m)-1;i!=0;i--)
{
if(i&(1<<j))f[i^(1<<j)]+=f[i],w[i]++;
}
}
for(int i=1;i<(1<<m);i++)
{
(ans+=(w[i]&1?-1:1)*ksm(2,f[i]))%=mod;
}
cout<<(ans+mod)%mod;
return 0;
}