poj 2441 Arrange the Bulls
状态压缩DP
多数也把这题分类在图论中,算是状态压缩在图论中的一个应用
题意:有n只牛和m个场,下面n行给出每只牛喜欢去的场的个数,再给出每个场的编号(而且每只牛只能去他们喜欢的场)。然后要你安排好这些牛去他们喜欢的场,一个场只能有一只牛,问有多少种分配方案
状态压缩,定义一个m位长的二进制数,从右到左依次代表第1,第2,第3个场,1表示这个场已经被占用,0表示没有。最后我们是要把n个牛都安排进去,那么这个二进制数将有n个1,这些就是我们要的目标状态。显然我们是按照牛的个数进行DP,先放第1只牛,再放第2只……最后放第n只。
所以状态转移可以表示为 s'--->s , 其中s‘有k-1个1,s有k个1,表示从第k-1个牛到第k个牛的转移,另外看消掉的1在哪一位也就是那个农场,必须满足第k只牛是喜欢这个农场的
边界条件dp[0]=1; 即一个1都没有的状态
记忆化搜索,写起来方便一点
/* m个农场,用一个长度为m的二进制表示,第i个农场被占据了则为1,否则为0 目标状态是有n个农场被占据,对应过来就是一个十进制数转为二进制有n个1 我们以牛的编号进行dp,从第一只牛开始放,一直放到第n只牛 所以每次状态转移,我们考虑放进第i个牛有多少方案,把所有可能的加起来即可 这种类型的状态压缩,用记忆化搜索实现比较容易,用递推则要做多点工作 为了锻炼代码能力和加深递推的理解,决定两种都写 */ #include <cstdio> #include <cstring> #define N 25 #define M 25 #define MAX 1100000 long long dp[MAX]; int n,m; bool g[N][M]; //g[i][j]=1表示第i只牛可以第j个农场 int bit(long long num) {//计算十进制数num对应的二进制数有多少个1 int count=0; while(num) { count += num&1; num=num>>1; } return count; } long long dfs(long long s , int numb) { if(dp[s]!=-1) return dp[s]; if(!s || !numb) //全部为0 return dp[s]=1; dp[s]=0; for(int i=0; i<m; i++) { if(g[numb][i+1] && s&(1<<i)) {//s的二进制的第i位有1即该农场有牛,且numb牛可以去那个农场 dp[s] += dfs(s-(1<<i),numb-1); } } return dp[s]; } int main() { while(scanf("%d%d",&n,&m)!=EOF) { memset(g,false,sizeof(g)); int num,tmp; for(int i=1; i<=n; i++) { scanf("%d",&num); for(int j=1; j<=num; j++) { scanf("%d",&tmp); g[i][tmp]=1; } } long long ans=0; int numb; //记录一个状态有多少个1 memset(dp,-1,sizeof(dp)); for(long long i=0; i<(1<<m); i++) { numb=bit(i); if(numb==n) //是我们要找的目标状态 ans += dfs(i,numb); } printf("%lld\n",ans); } return 0; }
递推,先预处理一下。init()函数就是给所有可能的状态分类,1的个数相同点的状态把他们放到一起,不用每次都计算,但是这样做似乎并没有提高时间,反而比记忆化搜索更慢了
#include <cstdio> #include <cstring> #define MAX 1100000 #define MAXS 1100000 #define N 25 #define M 25 bool g[N][M]; int c[N]; int state[N][MAXS]; long long dp[MAX]; int bit(int s) { int count=0; while(s) { count += s&1; s=s>>1; } return count; } void init() {//最多的状态为2^20-1 memset(c,0,sizeof(c)); for(int i=0; i<(1<<20); i++) { int numb=bit(i); state[numb][c[numb]++]=i; } } int main() { init(); int n,m,maxs; while(scanf("%d%d",&n,&m)!=EOF) { maxs=(1<<m)-1; memset(g,0,sizeof(g)); for(int i=0; i<n; i++) { int cc,k; scanf("%d",&cc); for(int j=0; j<cc; j++) { scanf("%d",&k); g[i+1][k]=true; } } /**********************************/ memset(dp,0,sizeof(dp)); dp[0]=1; for(int nn=1; nn<=n; nn++) //枚举所有的牛怎么放 { for(int i=0; i<c[nn]; i++) //枚举有nn个1的状态 { int s=state[nn][i]; if(s>maxs) continue; for(int k=0; k<m; k++) if((s&(1<<k)) && g[nn][k+1]) dp[s] += dp[s-(1<<k)]; } } long long ans=0; for(int i=0; i<c[n]; i++) { int s=state[n][i]; if(s>maxs) continue; ans += dp[s]; } printf("%lld\n",ans); } return 0; }