小P的團戰


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

本人第一次寫博客,請多見諒。

如有轉載,請標明出處,謝謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM