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;
}
