• [知识点]状态压缩DP


    // 此博文为迁移而来,写于2015年7月15日,不代表本人现在的观点与看法。原始地址:http://blog.sina.com.cn/s/blog_6022c4720102w6jf.html

     

    1、前言
           动态规划,永远的痛。
           好了不扯远了。状态压缩动态规划,其实看名字还是较好理解的。我们在动态规划的时候,最重要的就在于状态的设计和状态转移方程。那么,如果当我们状态过多导致时间或空间不够的饿时候,就可以用到状态压缩。王队(@wyh2000)说状态压缩DP难起来的话会很难,但是今天我们只讲最最最基础的状态压缩方式,以后再慢慢补充。
     
    2、区别
           ①有一个1*n的棋盘(n<=80),需要在上面放置k个棋子,使棋子之间不相邻,求方案数。
               那么这是一道很简单的棋盘型DP。设f[i][j][0]为前i个放置j个棋子的方案数且第i位必放,f[i][j][1]为前i个放置j个棋子的方案数且第i位必不放。则存在方程:
                       
           ②有一个m*n的棋盘(n*m<=80),需要在上面放置k个棋子,使棋子之间不相邻,求方案数。
               多了一个行的状态,这就让人费解了——我们并不能设置一个四维方程来表示状态。原来我们每一行只有一个格子,现在多个格子怎么表示呢?这里就要用到状态压缩了。这里提到的状态压缩的方式只是其中一种,相对比较简单的一种——二进制转换。我们注意到题目有一个特别鬼畜的数据范围——n*m<=80。这意味着什么?9*9=81>80,则min(n,m)的最大值为8。我们将m,n中较小的一个看做行(易得行列转换依旧等价),一行至多8个格子,我们令当前状态下格子若放置了棋子则记为1,未放置记为0,那么我们可以将一行的状态表示为一个二进制数,继而在状态转移的时候再转换为十进制。前文提到了最多8个数,所以数组中数值最大为2^8=256。设f[i][j][k]表示当前第i列,使用了j个棋子,当前行的状态为k(一个由二进制数转换过来的十进制数),则状态转移方程为:
            
                其中k`表示上一行的状态,num(k)表示在当前行状态为k的情况下棋子的总个数。
                当然我们要注意——如何判断当前这一行与上一行是否存在相邻的棋子?利用按位异或即可。见代码。
    ----------------------------------------------------------------------------------------------------
    #include cstdio
    #include cstring
    #include algorithm
    using namespace std;
     
    long long f[81][1<<9][21];
     
    inline int getNum(int x)
    {
            int s=0,tmp=0;
            while (x)
            {
                    if (s && (x & 1)) return -1;
                    if (s=(x & 1)) tmp++;
                    x=x>>1;
            }
            return tmp;
    }
     
    int main()
    {
            int n,m,t;
            while (scanf("%d %d %d",&n,&m,&t)!=EOF)
            {
                    if (n<m)  swap(n,m);
                    memset(f,0,sizeof(f));
                    f[0][0][0]=1;
                    for (int i=1;i<=n;i++)
                            for (int r=0;r<=t;r++)
                                    for (int j=0;j<(1<<m);j++) // 当前的状态 
                                    {
                                            int num=getNum(j); // 棋子个数 
                                            if (num==-1 || num>r) continue;
                                            for (int k=0;k<(1<<m);k++) // 上次的状态转移 
                                            {
                                                    if (getNum(k)==-1 || k&j) continue;
                                                    f[i][j][r]+=f[i-1][k][r-num];
                                            }
                                    }
                    long long ans=0;
                    for (int i=0;i<(1<<m);i++) ans+=f[n][i][t];
                    printf("%lld ",ans);
            }
            return 0;
    }
    -----------------------------------------------------------------------------------------------------
     
    3、例题


    分析:同样地,这道题用f[i][j][k]表示从(1,1)到(i,j)子矩阵中当所走路径状态为k的时候所得的最小权值,那么状态则是根据所走路径转换为二进制在转换为十进制来转移,就不多说了,看代码。
     
    代码:
    ---------------------------------------------------------------------------------------------------
    #include<cstdio>
    #include<cstring>
    #define MAXN 11
    #define INF 0x3f3f3f3f
     
    int min(int a,int b) { return (a<b)?a:b };
     
    const int two[MAXN]={1,2,4,8,16,32,64,128,256,512};
    int map[MAXN][MAXN],f[MAXN][MAXN][(1<<10)+1],ans,ret,tot;
     
    int getNum(int ki,int now)
    {
            int temp=ki;
            for (int i=9;i>=0;i--) if (temp>=two[i]) { temp-=two[i]; if (i==now) return ki; }
            return (ki+two[now]);
    }
     
    int main()
    {
            memset(f,INF,sizeof(f));
            for (int i=1;i<=10;i++)
                    for (int j=1;j<=10;j++) scanf("%d",&map[i][j]);
            int t1=two[map[1][1]],t2=0;
            f[1][1][t1]=map[1][1];
            for (int i=2;i<=10;i++) 
            {
                    t2=getNum(t1,map[1][i]);
                    f[1][i][t2]=f[1][i-1][t1]+map[1][i];
                    t1=t2;
            }
            t1=two[map[1][1]];
            for (int i=2;i<=10;i++) 
            {
                    t2=getNum(t1,map[i][1]);
                    f[i][1][t2]=f[i-1][1][t1]+map[i][1];
                    t1=t2;
            }
            for (int i=2;i<=10;i++)
                    for (int j=2;j<=10;j++)
                    {
                            for (int ki=1;ki<=(1<<10)-1;ki++)
                            {
                                    if (f[i-1][j][ki]==INF) continue;
                                    int k=getNum(ki,map[i][j]);
                                    f[i][j][k]=min(f[i][j][k],f[i-1][j][ki]+map[i][j]);
                            }
                            for (int ki=1;ki<=(1<<10)-1;ki++)
                            {
                                    if (f[i][j-1][ki]==INF) continue;
                                    int k=getNum(ki,map[i][j]);
                                    f[i][j][k]=min(f[i][j][k],f[i][j-1][ki]+map[i][j]);
                            }
             }
            for (int i=1;i<=1023;i++) if (f[3][3][i]!=INF) printf("i=%d %d ",i,f[3][3][i]);
            printf("%d",f[10][10][1023]);
            return 0;
    }
    ---------------------------------------------------------------------------------------------------
  • 相关阅读:
    禁止google浏览器强制跳转为https
    遍历打印文件目录结构
    添加忽略文件
    部署git服务器
    Location, History, Screen, Navigator对象
    Window 对象
    回调函数,setTimeout,Promise
    闭包
    this
    函数内部运作机制—上下文
  • 原文地址:https://www.cnblogs.com/jinkun113/p/4683325.html
Copyright © 2020-2023  润新知