ZOJ 3777 11th省赛 B Problem Arrangement【状态压缩DP】

来源:互联网 发布:淘宝首页源代码 编辑:程序博客网 时间:2024/05/14 06:11

题目链接

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5264

思路

题意就是n个题目,第i个题目放到第j个位置的有趣值是p[i][j],问你随机排列使得有趣值总和大于等于m的期望值是多少。 
这个期望值很好算就是n!/cnt。

直接dfs会超时,这里用到了DP,而DP的话,如果设dp[i][j]表示放了前i个题目,有趣值总和为j的方案数的话,会有个很大的问题,就是我们根本不知道dp[i][j]时的排列情况,从而无法计算出dp[i+1]时的有趣值。所以状态中必须要多一维来表示当前的排列状况,然而排列状况最多有12个位置,总不可能开个12维的数组吧…(好像可以?试试?)

最简便的方法是用状态压缩,因为每一位只有“有题目”和“没题目”两种状态,所以完全可以用一个n位的二进制数来表示,这里n<=12所以int绰绰有余。这样这个十二维的数组就瞬间压缩成一个维度了,是不是很奇妙。 
于是设计状态dp[i][j] 表示排列状态为i时,有趣值和为j时的方案总数

然后排列状态的表示设计好了,怎么对这个状态进行操作呢,这就牵扯到位运算了。 
(i >> k)&1,判断第k+1位是否为1 
(1 << k ) | i ,把第k+1位置为1

状态的操作也搞定了,接下来就是状态转移 
dp[ i | (1 << k)][j + p[tot + 1][k+1]]+=dp[i][j] foreach (1 >> k)&1==0

转移搞定了,想一下计算顺序,i咋一看应该按照1的个数从少到多来遍历,但这个很难实现。其实可以直接从小到大直接遍历i,为什么呢,因为假设a推出了b,那么b的1的个数肯定比a多一个,换句话说,从b中删掉一个1所形成的数在这之前必须全都遍历完,而假设b删了一个1形成了a,那么肯定a<b,所以只要增序遍历i就能满足。 
j的顺序倒无所谓,因为肯定用不到当前i。

边界是dp[0][0]=1

卧槽想了这么多总算能把代码写出来了,于是我兴冲冲地写了一段代码:

dp[0][0]=1for(int i=0 ; i<=((1<<n)-1) ; ++i){    for(int j=0 ; j<=m ; ++j)    {        int tot=count_one(i);        for(int k=0 ; k<=n-1 ; ++k)        {            if(((i>>k)&1)==0)            {                int new_i=(i|(1<<k));                int new_j=j+p[tot+1][k+1];                if(new_j>m)new_j=m;                dp[new_i][new_j]+=dp[i][j];            }        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

然后光荣TLE,(一脸卧槽)。 
心灰意冷地去看了看大神们的代码,发现长得差不多?!(卧槽*2) 
仔细看了下,发现了关键所在:我的count_one(i)的位置好像有点奇怪….(当时有种拍死自己的冲动)

改成这样就AC了,1200ms:

dp[0][0]=1for(int i=0 ; i<=((1<<n)-1) ; ++i){    int tot=count_one(i);    for(int j=0 ; j<=m ; ++j)    {        for(int k=0 ; k<=n-1 ; ++k)        {            if(((i>>k)&1)==0)            {                int new_i=(i|(1<<k));                int new_j=j+p[tot+1][k+1];                if(new_j>m)new_j=m;                dp[new_i][new_j]+=dp[i][j];            }        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

然后又发现几个小优化。 
把k和j的嵌套顺序换一下,这样可以跳过几次j的循环,500ms:

dp[0][0]=1;for(int i=0 ; i<=((1<<n)-1) ; ++i){    int tot=count_one(i);    for(int k=0 ; k<=n-1 ; ++k)    {        if(i&(1<<k))continue;        for(int j=0 ; j<=m ; ++j)        {            int new_i=(i|(1<<k));            int new_j=j+p[tot+1][k+1];            if(new_j>m)new_j=m;            dp[new_i][new_j]+=dp[i][j];        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

AC代码

#include <bits/stdc++.h>using namespace std;int p[13][13];int dp[5000][500+10];int factorial[13];inline int gcd(int a, int b){    if(a%b==0)return b;    else return gcd(b,a%b);}inline int count_one(unsigned int n){    int cnt=0;    for(int i=0 ; i<=31 ; ++i)    {        if((n>>i)&1)cnt++;    }    return cnt;}void init(){    int fac=1;    for(int i=1 ; i<=13 ; ++i)    {        fac*=i;        factorial[i]=fac;    }    factorial[0]=0;}int main(){    init();    int T;    scanf("%d",&T);    while(T--)    {        memset(dp,0,sizeof dp);        int n,m;        scanf("%d%d",&n,&m);        for(int i=1 ; i<=n ; ++i)        {            for(int j=1 ; j<=n ; ++j)            {                scanf("%d",&p[i][j]);            }        }        dp[0][0]=1;        for(int i=0 ; i<=((1<<n)-1) ; ++i)        {            int tot=count_one(i);            for(int k=0 ; k<=n-1 ; ++k)            {                if(((i>>k)&1))continue;                for(int j=0 ; j<=m ; ++j)                {                    int new_i=(i|(1<<k));                    int new_j=j+p[tot+1][k+1];                    if(new_j>m)new_j=m;                    dp[new_i][new_j]+=dp[i][j];                }            }        }        int fac=factorial[n],cnt=dp[((1<<n)-1)][m];        if(cnt==0)            printf("No solution\n");        else        {            int g=gcd(fac,cnt);            printf("%d/%d\n",fac/g,cnt/g);        }    }    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

转载地址:http://blog.csdn.net/wlx65003/article/details/50805705


0 0
原创粉丝点击