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;

}

 

你可能感兴趣的:(poj)