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

 

posted @ 2013-03-13 18:55  Titanium  阅读(333)  评论(0编辑  收藏  举报