状态压缩DP总结【POJ3311】【HDU3001】【POJ2288】【ZOJ4257】【POJ2411】【HDU3681】

来源:互联网 发布:中小学生网络知识竞赛 编辑:程序博客网 时间:2024/05/18 09:12

【POJ3311】Hie With The Pie

【题目大意】类似于TSP问题,只是每个点可以走多次,比经典TSP问题不同的是要先用弗洛伊的预处理一下两两之间的距离。求最短距离。

【解析】可以用全排列做,求出一个最短的距离即可。或者用状态压缩DP.用一个二进制数表示城市是否走过

【状态表示】dp[state][i]表示到达i点状态为state的最短距离

【状态转移方程】dp[state][i] =min{dp[state][i],dp[state'][j]+dis[j][i]} dis[j][i]为j到i的最短距离

【DP边界条件】dp[state][i] =dis[0][i]  state是只经过i的状态

【代码】

[cpp] view plaincopyprint?
  1. #include<iostream>   
  2. #define INF 100000000   
  3. using namespace std;   
  4. int dis[12][12];   
  5. int dp[1<<11][12];   
  6. int n,ans,_min;   
  7. int main()   
  8. {   
  9.    //freopen("in.txt","r",stdin);   
  10.    while(scanf("%d",&n) && n)   
  11.    {   
  12.        for(int i = 0;i <= n;++i)   
  13.            for(int j = 0;j <= n;++j)   
  14.                scanf("%d",&dis[i][j]);   
  15.        for(int k = 0;k <= n;++k)   
  16.            for(int i = 0;i <= n;++i)   
  17.                 for(int j = 0;j <=n;++j)   
  18.                     if(dis[i][k] + dis[k][j]< dis[i][j])   
  19.                         dis[i][j] = dis[i][k] +dis[k][j];   
  20.            
  21.        for(int S = 0;S <= (1<<n)-1;++S)//枚举所有状态,用位运算表示   
  22.            for(int i = 1;i <= n;++i)   
  23.            {   
  24.                 if(S & (1<<(i-1)))//状态S中已经过城市i   
  25.                 {   
  26.                     if(S ==(1<<(i-1)))   dp[S][i] =dis[0][i];//状态S只经过城市I,最优解自然是从0出发到i的dis,这也是DP的边界  
  27.                     else//如果S有经过多个城市   
  28.                     {   
  29.                         dp[S][i] = INF;   
  30.                         for(int j = 1;j <=n;++j)   
  31.                         {   
  32.                             if(S &(1<<(j-1)) && j != i)//枚举不是城市I的其他城市   
  33.                                 dp[S][i] =min(dp[S^(1<<(i-1))][j] + dis[j][i],dp[S][i]);   
  34.                             //在没经过城市I的状态中,寻找合适的中间点J使得距离更短  
  35.                         }   
  36.                     }   
  37.                 }   
  38.             }   
  39.        ans = dp[(1<<n)-1][1] + dis[1][0];   
  40.        for(int i = 2;i <= n;++i)   
  41.            if(dp[(1<<n)-1][i] + dis[i][0] < ans)   
  42.                 ans = dp[(1<<n)-1][i] +dis[i][0];   
  43.        printf("%d\n",ans);   
  44.    }   
  45.    return 0;   
  46. }  


【HDU3001】Traveling

【题目大意】10个点的TSP问题,但是要求每个点最多走两边,不是只可以走一次,所以要用三进制的状态压缩解决这个问题。可以预处理每个状态的第k位是什么。

【解析】和tsp问题相同,类似于上面那个题

【状态表示】【状态转移方程】同上题,具体见代码

【代码】

[cpp] view plaincopyprint?
  1. #include <cstdio>  
  2. #include <cstring>  
  3. #define INF 0x1f1f1f1f    //刚发现这里写0x1f1f1f跑的比0x1f1f1f1f差不多慢了一倍!Orz~  
  4. #define min(a,b) (a) < (b) ? (a) : (b)  
  5. using namespace std;  
  6.    
  7. int N,M;  
  8. int tri[12] ={0,1,3,9,27,81,243,729,2187,6561,19683,59049};  
  9. int dig[59050][11];  //dig[state][k_dig]  状态state的第k位是多少  
  10. int edge[11][11],dp[59050][11];  
  11.    
  12. int main(){  
  13.    for(int i = 0; i < 59050; ++i){  
  14.        int t = i;  
  15.        for(int j = 1; j <= 10; ++j){  
  16.            dig[i][j] = t%3;  
  17.            t /= 3;  
  18.            if(t == 0)break;  
  19.        }  
  20.    }  
  21.    
  22.    while(scanf("%d%d",&N,&M) != EOF){  
  23.        memset(edge,INF,sizeof(edge));  
  24.    
  25.       int a,b,c;  
  26.       while(M --){  
  27.           scanf("%d%d%d",&a,&b,&c);  
  28.           if(c < edge[a][b])edge[a][b] = edge[b][a] = c;  
  29.       }  
  30.    
  31.       memset(dp,INF,sizeof(dp));  
  32.    
  33.       for(int i = 1; i <= N; ++i)dp[tri[i]][i] = 0;  
  34.       int ans = INF;  
  35.       for(int S = 0; S < tri[N+1]; ++S){  
  36.           int visit_all = 1;  
  37.           for(int i = 1; i <= N; ++i){  
  38.                if(dig[S][i] == 0)visit_all = 0;  
  39.                if(dp[S][i] == INF)continue;  
  40.    
  41.                for(int j = 1; j <= N; ++j){  
  42.                    if(i == j)continue;  
  43.                    if(edge[i][j] == INF ||dig[S][j] >= 2)continue;  
  44.                    int newS = S + tri[j];  
  45.                    dp[newS][j] =min(dp[newS][j],dp[S][i] + edge[i][j]);  
  46.                }  
  47.           }  
  48.           if(visit_all){  
  49.                for(int j = 1; j <= N; ++j)  
  50.                 ans = min(ans,dp[S][j]);  
  51.           }  
  52.    
  53.       }  
  54.       if(ans == INF){  
  55.           puts("-1");  
  56.           continue;  
  57.       }  
  58.       printf("%d\n",ans);  
  59.    }  
  60.    return 0;  
  61. }  


【POJ2288】Islands and Bridge

【题目大意】求汉密尔顿的一道变形问题,中间每个点有权值,关于最后得分的描述如下

Suppose there are n islands. The value of aHamilton path C1C2...Cn is calculated as the sum of three parts. Let Vi be thevalue for the island Ci. As the first part, we sum over all the Vi values foreach island in the path. For the second part, for each edge CiCi+1 in the path,we add the product Vi*Vi+1. And for the third part, whenever three consecutiveislands CiCi+1Ci+2 in the path forms a triangle in the map, i.e. there is abridge between Ci and Ci+2, we add the product Vi*Vi+1*Vi+2. 

这题要求让得分最高

【解析】发现每个点的状态由前面两个点确定,用DP(S,A,B)表示状态为S时,当前到达A,而上一个点是B时的最大得分,这个状态由DP(S',B,C)通过从B走到A得到,S'=S-(1<<A),即S'状态就是经过B和C但不经过A的一个状态,C是不同于A和B的一个点。

【状态转移】dp[S][A][B] =max(dp[S][A][B],dp[S'][B][C]+temp) 这里的temp指的是加上的得分即Vb*Va+Va,如果构成三角关系(即A和C间有边),temp就要再加上Vb*Va*Vc.

【边界条件】DP((1<<A)+(1<<B),A,B)=Va+Vb+Va*Vb(A和B间有边)表示

【代码】

[cpp] view plaincopyprint?
  1. #include <cstdio>  
  2. #include <cstring>  
  3. using namespace std;  
  4. const int MAXN = 13;  
  5. const int MAX_S = 1<<(MAXN+1);  
  6. long long dp[MAX_S][MAXN+1][MAXN+1];  
  7. long long way[MAX_S][MAXN+1][MAXN+1];  
  8. int edge[MAXN+1][MAXN+1];  
  9. long long V[MAXN+1];  
  10.    
  11. int N,M;  
  12. int main(){  
  13.    int cas;  
  14.    scanf("%d",&cas);  
  15.    while(cas --){  
  16.        memset(edge,0,sizeof(edge));  
  17.        scanf("%d%d",&N,&M);  
  18.        for(int i = 1; i <= N; ++i)  
  19.            scanf("%d",&V[i]);  
  20.        if(N == 1){  
  21.            printf("%d 1\n",V[1]);  
  22.            continue;  
  23.        }  
  24.        int a,b;  
  25.        while(M --){  
  26.            scanf("%d%d",&a,&b);  
  27.            edge[a][b] = edge[b][a] = 1;  
  28.        }  
  29.    
  30.         memset(dp,-1,sizeof(dp));  
  31.        memset(way,0,sizeof(way));  
  32.        int ii,jj;  
  33.        long long temp;  
  34.        for(int i = 1; i <= N; ++i)  
  35.        for(int j = 1; j <= N; ++j){  
  36.            if(i == j || !edge[i][j])continue;  
  37.            ii = 1<<(i-1);  
  38.            jj = 1<<(j-1);  
  39.            temp = V[i]+V[j]+V[i]*V[j];  
  40.            dp[ii+jj][i][j] = temp;  
  41.            way[ii+jj][i][j] = 1;  
  42.        }  
  43.    
  44.        for(int S = 0; S < (1<<N); ++S)  
  45.        for(int i = 1; i <= N; ++i){  
  46.            if((S&(1<<(i-1))) == 0)continue;  
  47.            for(int j = 1; j <= N; ++j){  
  48.                 if((S&(1<<(j-1))) ==0 || i == j || !edge[i][j])continue;  
  49.                 for(int k = 1; k <= N; ++k){  
  50.                     if(i == k || j == k ||(S&(1<<(k-1))) == 0)continue;  
  51.                    int newS = S -(1<<(i-1));  
  52.                     if(dp[newS][j][k] ==-1)continue;  
  53.                     if(!edge[j][k])continue;  
  54.    
  55.                     temp =V[i]+V[i]*V[j]+dp[newS][j][k];  
  56.                     if(edge[i][k])temp +=V[i]*V[j]*V[k];  
  57.                     if(dp[S][i][j] < temp){  
  58.                         dp[S][i][j] = temp;  
  59.                         way[S][i][j] =way[newS][j][k];  //如果dp更新,way直接更新  
  60.                     }  
  61.                     //如果dp不更新,但dp=temp,way加上原来的值  
  62.                     else if(temp ==dp[S][i][j])way[S][i][j] += way[newS][j][k];  
  63.                 }  
  64.            }  
  65.        }  
  66.        long long ans = -1,num = 0;  
  67.        int p = (1<<(N)) - 1;  
  68.        for (int i = 1; i <= N; ++i)  
  69.        for (int j = 1; j <= N; ++j){  
  70.            if(i == j)continue;  
  71.            if (ans < dp[p][i][j]){  
  72.                 ans = dp[p][i][j];  
  73.                 num = way[p][i][j];  
  74.            }  
  75.            else if (ans == dp[p][i][j])  
  76.                 num += way[p][i][j];  
  77.        }  
  78.        if(ans == -1){  
  79.            puts("0 0");  
  80.            continue;  
  81.        }  
  82.        printf("%lld %lld\n",ans,num/2);  
  83.    }  
  84.    return 0;  
  85. }  



【ZOJ4257】MostPowerful  

【题目大意】不超过10种气体,两两之间相互碰撞可以产生一定的能量,如a碰b,那么b气体就消失,自身不能碰自身,问最后所能得到的最大能量。

【题目解析】用10位二进制表示气体是否存在,0表示存在,1表示不存在,S(上一个状态)中的两种气体碰撞并且有一种消失,可以得到newS的状态(状态转移)

【状态表示】dp[state] 状态为state时的最大能量

【转移方程】dp[state] = max(dp[state],dp[state']+a[i][j])

【边界条件】dp[i] = 0;

【代码】

[cpp] view plaincopyprint?
  1. #include <cstdio>  
  2.    
  3. #include <cstring>  
  4.    
  5. using namespace std;  
  6.    
  7. #define max(a,b) (a) > (b) ? (a) : (b)  
  8.    
  9. const int MAXN = 10;  
  10.    
  11. const int MAX_S = 1 << 10;  
  12.    
  13. int a[MAXN+1][MAXN+1];  
  14.    
  15. int dp[MAX_S];  
  16.    
  17. int N;  
  18.    
  19. int main(){  
  20.    
  21.     while(scanf("%d",&N) != EOF){  
  22.    
  23.         if(N == 0)break;  
  24.    
  25.         for(int i = 0; i < N; ++i)  
  26.    
  27.         for(int j = 0; j < N; ++j){  
  28.    
  29.             scanf("%d",&a[i][j]);  
  30.    
  31.         }  
  32.    
  33.         memset(dp,0,sizeof(dp));  
  34.    
  35.         int full = 1 << N;  
  36.    
  37.         for(int s = 0; s < full; ++s){  
  38.    
  39.             for(int i = 0; i < N; ++i){  
  40.    
  41.                 if((s&(1<<i)))continue;  
  42.    
  43.                 for(int j = 0; j < N; ++j){  
  44.    
  45.                     if(i == j)continue;  
  46.    
  47.                     if( (s&(1<<j)) )continue;  
  48.    
  49.                     int newS = s + (1<<j);  
  50.    
  51.                     dp[newS] = max(dp[newS],dp[s] + a[i][j]);  
  52.    
  53.                 }  
  54.    
  55.    
  56.    
  57.             }  
  58.    
  59.         }  
  60.    
  61.         int ans = 0;  
  62.    
  63.         for(int s = 0; s < full ; ++s)  
  64.    
  65.             ans = max(ans,dp[s]);  
  66.    
  67.         printf("%d\n",ans);  
  68.    
  69.     }  
  70.    
  71.     return 0;  
  72.    
  73. }  


【POJ2411】Mondriaan'sDream   

【题目大意】一个矩阵,只能放1*2的木块,问将这个矩阵完全覆盖的不同放法有多少种。

【解析】如果是横着的就定义11,如果竖着的定义为竖着的01,这样按行dp只需要考虑两件事儿,当前行&上一行,是不是全为1,不是说明竖着有空(不可能出现竖着的00),另一个要检查当前行里有没有横放的,但为奇数的1。

【状态表示】dp[state][i]第i行状态为state时候的方案数 

【转移方程】dp[state][i] += dp[state'][i-1] state'为i-1行的,不与i行状态state冲突的状态

【边界条件】第一行 符合条件的状态记为1 即dp[state][0] = 1;

【代码】

[cpp] view plaincopyprint?
  1. #include <cstdio>  
  2.    
  3. #include <cstring>  
  4.    
  5. using namespace std;  
  6.    
  7. int M,N;  
  8.    
  9. long long dp[1<<11][11];  
  10.    
  11. bool kexing[1<<11];  
  12.    
  13. int full;  
  14.    
  15. inline bool check(int in)  
  16.    
  17. {  
  18.    
  19.         int bit=0;  
  20.    
  21.         while(in>0){  
  22.    
  23.                 if( (in&1)==1)  
  24.    
  25.                          bit++;  
  26.    
  27.                 else{  
  28.    
  29.                          if( (bit&1)==1)  
  30.    
  31.                                   return false;  
  32.    
  33.                          bit=0;  
  34.    
  35.                 }  
  36.    
  37.                 in>>=1;  
  38.    
  39.         }  
  40.    
  41.         if( (bit&1)==1)  
  42.    
  43.                 return false;  
  44.    
  45.         return true;  
  46.    
  47. }  
  48.    
  49. inline bool check2(int x1,int x2){  
  50.    
  51.    if( (x1|x2)!= full-1 )  //如果这里不用位运算,时间很长,要用约3000MS  
  52.    
  53.                 return false;  
  54.    
  55.     return kexing[x1&x2];  
  56.    
  57. }  
  58.    
  59. int main(){  
  60.    
  61.      full = 1<<11;  
  62.    
  63.     for(int s = 0; s < full; ++s)if(check(s))kexing[s] = 1;  
  64.    
  65.     while(scanf("%d%d",&M,&N) != EOF){  
  66.    
  67.         if(N == 0 && M == 0)break;  
  68.    
  69.         memset(dp,0,sizeof(dp));  
  70.    
  71.         full = 1<<N;  
  72.    
  73.         for(int s = 0; s < full; ++s){  
  74.    
  75.             if(kexing[s])  
  76.    
  77.                 dp[s][0] = 1;  
  78.    
  79.         }  
  80.    
  81.         for(int i = 1; i < M; ++i){  
  82.    
  83.             for(int s = 0; s < full; ++s){  
  84.    
  85.                 for(int s1 = 0; s1 < full; ++s1){  
  86.    
  87.                     if(!check2(s1,s))continue;  
  88.    
  89.                     //if(dp[s1][i-1] == 0)continue;    
  90.    
  91.                     /*这一步判断的时间要大于位运算和+=的时间,如果先把这一步放在 
  92.   
  93.                     check2前面,1300多MS,如果放在check2后面,610多MS,如果不加这 
  94.   
  95.                     一步,560MS,但如果check2用的不是位运算,将这一步加在check2前 
  96.   
  97.                     面3000MS左右(水过),如果不加这一步,TLE 
  98.   
  99.                     */  
  100.    
  101.                     dp[s][i] += dp[s1][i-1];  
  102.    
  103.                 }  
  104.    
  105.             }  
  106.    
  107.         }  
  108.    
  109.         int S = (1<<N) - 1;  
  110.    
  111.         printf("%lld\n",dp[S][M-1]);  
  112.    
  113.     }  
  114.    
  115.     return 0;  
  116.    
  117. }  


【HDU3681】PrisonBreak 状态压缩DP+BFS+二分答案

2010杭州赛区的题目,以现在的水平遇到这种题也就能想一下,赛场上动手写这个题是不会的。前天做状压DP的时候又看到这个题,没想起来怎么做,昨天看了一下解题报告开始下手,遇到了各种问题。调试了N久,终于过了。

【题目大意】机器人从F出发,走到G可以充电,走到Y关掉开关,D不能走进,要求把所有开关关掉,且电量最少,并求出该最小电量。

【题目解析】机器人从出发点出发要求走过所有的Y,因为点很少,所以就能想到经典的TSP问题(起初我也想到了),但关于G点(不要YY)能充电的问题不知道怎么办,看了下解题报告才知道。G点可以充电,到达G点就把当前能量更新为电池容量然后继续走。因为每个G点只能充一次电,这就好像TSP中的每个点只能走一次一样(G和Y都可以走多次,但走到G充电后,该点就变为了S,而走到Y关上开关以后,Y也变成了S。这是一个很巧妙地想法,所以要求Y点只能关一次开关,G点只能充一次电,这就是TSP了。Orz赛场上可以秒杀这题的大神们),然后就是二分答案了,用状压DP判定当前电池容量的情况下是否能符合条件。

【状态表示】dp[s][i]表示到达当前i点状态为s时最大的剩余的能量

【转移方程】同TSP问题了

【边界条件】dp[1<<sid][sid] = rongliang.即出发点的能量就是电池容量

【代码】:

[cpp] view plaincopyprint?
  1. #include <cstdio>  
  2.    
  3. #include <cstring>  
  4.    
  5. #include <cmath>  
  6.    
  7. #include <queue>  
  8.    
  9. using namespace std;  
  10.    
  11. #define INF 0x1f1f1f1f  
  12.    
  13. int dp[32769][16];  
  14.    
  15. int dist[16][16][16][16];  
  16.    
  17. int di[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};  
  18.    
  19. int M,N,sid,nCnt,FinalState;  
  20.    
  21. char map[16][16];  
  22.    
  23. struct node{  
  24.    
  25.     int x,y;  
  26.    
  27.     node(){}  
  28.    
  29.     node(int _x,int _y):x(_x),y(_y){}  
  30.    
  31. }nodes[16];  
  32.    
  33.    
  34.    
  35. inline void BFS(node start)  
  36.    
  37. {  
  38.    
  39.     queue<node> que;  
  40.    
  41.     int sx = start.x,sy = start.y;  
  42.    
  43.     dist[sx][sy][sx][sy] = 0;  
  44.    
  45.     que.push(start);  
  46.    
  47.     node cur;  
  48.    
  49.     while(!que.empty()){  
  50.    
  51.         cur = que.front();  
  52.    
  53.         que.pop();  
  54.    
  55.         int x = cur.x,y = cur.y,tx,ty;  
  56.    
  57.         for(int i = 0; i < 4; ++i){  
  58.    
  59.             tx = x + di[i][0];  
  60.    
  61.             ty = y + di[i][1];  
  62.    
  63.             if(tx < 0 || tx >= M || ty < 0 || ty >= N || map[tx][ty] == 'D')continue;  
  64.    
  65.             if(dist[sx][sy][tx][ty] == -1){  
  66.    
  67.                 dist[sx][sy][tx][ty] = dist[sx][sy][x][y] + 1;  
  68.    
  69.                 que.push(node(tx,ty));  
  70.    
  71.             }  
  72.    
  73.         }  
  74.    
  75.     }  
  76.    
  77. }  
  78.    
  79. inline bool ok(int s,int t){ //s状态中走过所有t状态中的城市  
  80.     if(((s&t)&t) == t)return 1;  
  81.     return 0;  
  82. }  
  83. inline bool check(int step){   
  84.     int res = -1;   
  85.     memset(dp,-1,sizeof(dp));   
  86.     dp[1<<sid][sid] = step;   
  87.     int full = 1<<nCnt;   
  88.     for(int s = 0; s < full; ++s){  
  89.    
  90.         for(int i = 0; i < nCnt; ++i){   
  91.             if((s&(1<<i)) == 0 || dp[s][i] == -1)continue;   
  92.             if(ok(s,FinalState))res = max(res,dp[s][i]);   
  93.             for(int j = 0; j < nCnt; ++j){   
  94.                 int temp = dist[nodes[i].x][nodes[i].y][nodes[j].x][nodes[j].y];   
  95.                 if(i == j || temp == -1 || (s&(1<<j)))continue;   
  96.                 temp = dp[s][i] - temp;   
  97.                 if(temp < 0)continue;   
  98.                 int newS = s + (1<<j);   
  99.                 dp[newS][j] = max(dp[newS][j],temp);   
  100.                 if(map[nodes[j].x][nodes[j].y] == 'G')dp[newS][j] = step;  
  101.    
  102.             }  
  103.    
  104.         }  
  105.    
  106.     }  
  107.    
  108.     if(res < 0)return 0;  
  109.    
  110.     return 1;  
  111.    
  112. }  
  113.    
  114.    
  115.    
  116. inline int solve(){  
  117.    
  118.     int low = 0,high = 300;  
  119.    
  120.     int mid;  
  121.    
  122.     while(low <= high){  
  123.    
  124.         mid = (low+high)/2;  
  125.    
  126.         if(check(mid))high = mid-1;  
  127.    
  128.         else low = mid+1;  
  129.    
  130.     }  
  131.    
  132.     if(low == 301)return -1;  
  133.    
  134.     return low;  
  135.    
  136.    
  137.    
  138. }  
  139.    
  140.    
  141.    
  142. int main(){  
  143.    
  144.     while(scanf("%d%d",&M,&N) != EOF){  
  145.    
  146.         if(M == 0 && N == 0)break;  
  147.    
  148.         nCnt = 0;  
  149.    
  150.         FinalState = 0;  
  151.    
  152.         for(int i = 0; i < M; ++i){  
  153.    
  154.             scanf(" %s",map[i]);  
  155.    
  156.             for(int j = 0; j < N; ++j){  
  157.    
  158.                 if(map[i][j] == 'F'){  
  159.    
  160.                     sid = nCnt;  
  161.    
  162.                     nodes[nCnt++] = node(i,j);  
  163.    
  164.                     FinalState += (1<<sid);  
  165.    
  166.                 }  
  167.    
  168.                 else if(map[i][j] == 'G')  
  169.    
  170.                     nodes[nCnt++] = node(i,j);  
  171.    
  172.                 else if(map[i][j] == 'Y'){  
  173.    
  174.                     int tid = nCnt;  
  175.    
  176.                     nodes[nCnt++] = node(i,j);  
  177.    
  178.                     FinalState += (1<<tid);  
  179.    
  180.                 }  
  181.    
  182.             }  
  183.    
  184.         }  
  185.    
  186.         memset(dist,-1,sizeof(dist));  
  187.    
  188.         for(int i = 0; i < nCnt; ++i)BFS(nodes[i]);  
  189.    
  190.         int ans = solve();  
  191.    
  192.         printf("%d\n",ans);  
  193.    
  194.     }  
  195.    
  196.     return 0;  
  197.    
  198. }  
0 0
原创粉丝点击