传送门
Description
你要杀n个怪,每杀掉一个怪那个怪会掉落一种武器,这种武器可以杀死特定的怪。游戏初始你有一把武器,能杀死一些怪物。每次只能杀一只,求有多少种杀怪方法。
Input
多组数据,第一行是数组组数T,对于每组数据,有:
- 第一行是怪物个数n
- 第二行以0/1串的形式描述初始武器能杀死的怪物
- 下面n行,第i行以0/1串的形式描述干掉第i只怪以后掉落武器能杀死的怪物
Output
对于每组数据,输出:
- 杀怪的顺序数,形式为Case X: Y
Sample Input
3 1 1 1 2 11 01 10 3 110 011 100 000
Sample Output
Case 1: 1 Case 2: 2 Case 3: 3
Hint
n≤16
Solution
看看数据范围,大概是个状压DP。
记录kldi为集合i在二进制意义下代表的怪物编号被干掉后掉落武器能干掉的怪物的集合。
由于有一把初始武器,我们可以认为干掉任意集合的怪物都能干掉初始武器能干掉怪物的集合。
设fi为干掉集合i在二进制意义下代表的怪物的顺序个数
枚举该集合的每个元素,考虑该元素的补集被干掉后能不能干掉他,若能,则转移。
转移用到加法原理,方程为fi+=fj | j是i中的元素且kld[j^i]&j。
Code
#include<cstdio> #include<cstring> #define rg register #define ci const int #define cl const long long int typedef long long int ll; inline void qr(int &x) { char ch=getchar(),lst=NULL; while(ch>'9'||ch<'0') lst=ch,ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); if (lst=='-') x=-x; } char buf[20]; inline void write(int x,const char aft,const bool pt) { if(x<0) {putchar('-');x=-x;} int top=0; do { buf[++top]=x%10+'0'; x/=10; } while(x); while(top) putchar(buf[top--]); if(pt) putchar(aft); } template <typename T> inline T mmax(const T &a,const T &b) {if(a>b) return a;return b;} template <typename T> inline T mmin(const T &a,const T &b) {if(a<b) return a;return b;} template <typename T> inline T mabs(const T &a) {if(a<0) return -a;return a;} template <typename T> inline void mswap(T &a,T &b) {T temp=a;a=b;b=temp;} const int maxn = 20; const int maxt = 131072; int t,cnt,n; ll frog[maxt]; int gorf[maxn],kld[maxt]; void clear(); int main() { qr(t); while(t--) { clear(); qr(n); for(rg int j=0;j<n;++j) { char ch=getchar();while((ch!='1') && (ch!='0')) ch=getchar(); if(ch=='1') kld[0]|=(1<<j); } for(rg int i=0;i<n;++i) { for(rg int j=0;j<n;++j) { char ch=getchar();while((ch!='1') && (ch!='0')) ch=getchar(); if(ch=='1') kld[1<<i]|=(1<<j); } } rg int upceil = (1<<n)-1; for(rg int i=0;i<=upceil;++i) { for(rg int j=0;j<n;++j) if((1<<j)&i) { kld[i]|=kld[1<<j]; } kld[i]|=kld[0]; } frog[0]=1; for(rg int i=1;i<=upceil;++i) { for(rg int j=0;j<n;++j) if(i&(1<<j)){ if(kld[i^(1<<j)]&(1<<j)) frog[i]+=frog[i^(1<<j)]; } } printf("Case %d: %lld ",++cnt,frog[upceil]); } } void clear() { n=0; memset(kld,0,sizeof kld); memset(frog,0,sizeof frog); memset(gorf,0,sizeof gorf); }
Summary
在状压DP进行转移的时候,不一定需要枚举子集进行转移,常用的另一种转移方式是枚举集合中元素进行转移。