zoukankan      html  css  js  c++  java
  • 状态压缩DP 题目小节 (一)

    最近被状态压缩DP虐得不行,今天终于决定正视自己的弱项,好好把DP练习一下,把今天做的几道状态压缩DP总结一下,一定要想办法摆脱DP弱菜这个标签!!!

    http://poj.org/problem?id=3254

    poj 3254 :

    应该是最基础的状态压缩DP了吧,设dp[i][flag]表示第i行状态为flag时的排放总数,预处理一下dp[1][flag],对于dp[i][flag](i>=2),则dp[i][flag]=dp[i][flag]+dp[i-1][pre]当且仅当pre满足以下几个条件:

    1:flag和pre都不含有相邻的1(二进制)

    2:flag和pre分别满足第i行和第i-1行的约束条件。

    3:flag和pre在同一位上不能同时为1(二进制)。

    还是挺简单的,用位运算可以简单实现。代码如下:


    #include <iostream>
    #include <string.h>
    #include <stdio.h>
    #include <vector>
    #define mod 100000000
    using namespace std;
    vector<int> t;
    int check(int x)
    {
        int i;
        for(i=0;i<=10;i++)
        {
            int tmp=(1<<i)+(1<<(i+1));
            if((x&tmp)==tmp)
            return 0;
        }
        return 1;
    }
    void init()
    {
        int i;
        t.push_back(0);
        for(i=1;i<(1<<12);i++)
        {
            if(check(i))
            t.push_back(i);
        }
    }
    int dp[13][400];
    int num[13];
    int main()
    {
        //freopen("dd.txt","r",stdin);
        init();
        int n,m,i,j;
        scanf("%d%d",&n,&m);
        memset(dp,0,sizeof(dp));
        for(i=1;i<=n;i++)
        {
            int tmp=0;
            for(j=1;j<=m;j++)
            {
                int x;
                scanf("%d",&x);
                tmp=tmp*2+x;
            }
            num[i]=tmp;
        }
        int limit=1<<m,len=t.size();
        long long ans=0;
        for(i=0;i<len;i++)
        {
            if(t[i]>=limit)
            break;
            int now=t[i];
            if((now|num[1])==num[1])
            {
                dp[1][i]=1;
            }
        }
        for(i=2;i<=n;i++)
        {
            for(j=0;j<len;j++)
            {
                if(t[j]>=limit)
                break;
                int now=t[j],s;
                if((now|num[i])==num[i])
                {
                    for(s=0;s<len;s++)
                    {
                        if(t[s]>=limit)
                        break;
                        int pre=t[s];
                        if((num[i-1]|pre)==num[i-1]&&(pre&now)==0)
                        {
                            dp[i][j]=(dp[i][j]+dp[i-1][s])%mod;
                        }
                    }
                }
            }
        }
        for(i=0;i<len;i++)
        {
            if(t[i]>=limit)
            break;
            ans=(ans+dp[n][i])%mod;
        }
        printf("%I64d\n",ans);
        return 0;
    }
    


    http://acm.hdu.edu.cn/showproblem.php?pid=4539

    hdu 4539:

    腾讯编程马拉松复赛的题,话说就是这道题激起了我苦练DP的决心的。

    经典的状态压缩DP,我们设dp[i][pre][now]表示第i行为now状态,第i-1行为pre状态时可以安排的最大士兵数量,这里的状态指的是每一行的士兵安排情况,我们把状态用二进制表示出来后,第i位为1表示在第i列放置一个士兵,为0表示不放,因为m<=10所以我们最多只要1024个状态就可以表示一行的每一个状态,事实上这1024个状态中有大部分是不合法的,(也就是有两个1其距离为2),所以我们可以处理出所有的合法状态,(我们下面所讨论的状态都是合法的),对于dp[i][pre][now]我要怎么计算呢

    首先当然是这两个状态不能和所给的矩阵有冲突(也就是在不能人的地方放置了人),然后相邻两行不能有距离为2的1存在。满足上面的条件后,则dp[i][pre][now]=max(dp[i-1][ppre][pre]),其中ppre也要状态也要满足以上条件。我们最后求合法状态中的最大值即可,以上快速判断是否满足要求可以用位运算来判断,具体如何用自己思考,或者参考我的代码。

    #include <iostream>
    #include <string.h>
    #include <algorithm>
    #include <stdio.h>
    #include <vector>
    using namespace std;
    int dp[200][200][200];
    vector<int> a[11];
    int check(int x,int len)
    {
        int i;
        for(i=0;i<len-2;i++)
        {
            int tmp=(1<<i)+(1<<(i+2));
            if((tmp&x)==tmp)
            {
                return 0;
            }
        }
        return 1;
    }
    int cot[1100];
    int getnum(int x)
    {
        int sum=0;
        while(x)
        {
            if(x%2)
            sum++;
            x/=2;
        }
        return sum;
    }
    void init()
    {
        int i,j,tmp;
        for(i=0;i<=1024;i++)
        cot[i]=getnum(i);
        for(i=1;i<=10;i++)
        {
            tmp=(1<<i)-1;
            a[i].push_back(0);
            for(j=1;j<=tmp;j++)
            {
                if(check(j,i))
                {
                    a[i].push_back(j);
                }
            }
        }
    }
    int max(int a,int b)
    {
        return a>b?a:b;
    }
    int num[110];
    int main()
    {
        init();
        int n,m,x;
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            memset(dp,0,sizeof(dp));
            int i,j,tmp;
            for(i=1;i<=n;i++)
            {
                tmp=0;
                for(j=1;j<=m;j++)
                {
                    scanf("%d",&x);
                    tmp=tmp*2+x;
                }
                num[i]=tmp;
            }
            tmp=a[m].size();
            int ans=0;
            for(i=0;i<tmp;i++)
            {
                if((num[1]|a[m][i])==num[1])//判断是否满足棋盘
                {
                    int now=a[m][i];
                    dp[1][0][i]=cot[now];
                    ans=max(ans,dp[1][0][i]);
                }
            }
            for(i=2;i<=n;i++)
            {
                for(j=0;j<tmp;j++)
                {
                    if((num[i]|a[m][j])==num[i])//状态满足第i行
                    {
                        int now=a[m][j];
                        int s,t;
                        for(s=0;s<tmp;s++)
                        {
                            int pre=a[m][s];
                            if((num[i-1]|pre)==num[i-1])
                            {
                                if(((now<<1)&pre)==0&&((now>>1)&pre)==0)
                               {
                                   if(i==2)
                                   dp[i][s][j]=dp[1][0][s]+cot[now];
                                   else
                                   {
                                       for(t=0;t<tmp;t++)
                                       {
                                           int ppre=a[m][t];
                                           if((num[i-2]|ppre)==num[i-2])
                                           {
                                               if((ppre&now)==0)
                                               {
                                                   if(((ppre<<1)&pre)==0&&((ppre>>1)&pre)==0)
                                                   dp[i][s][j]=max(dp[i][s][j],dp[i-1][t][s]+cot[now]);
                                               }
                                           }
                                       }
                                   }
                                   ans=max(ans,dp[i][s][j]);
                               }
                            }
                        }
                    }
                }
            }
            printf("%d\n",ans);
        }
        return 0;
    }


    http://poj.org/problem?id=1185

    poj 1185

    和上一道题差不多(连名字都一样),只是判断合法状态的方法不同而已,大部分都是一样的。只是变了一点形式而已,这里不多说了。直接看代码。

    #include <iostream>
    #include <string.h>
    #include <algorithm>
    #include <stdio.h>
    #include <vector>
    using namespace std;
    int dp[110][65][65];
    vector<int> a[11];
    int check(int x,int len)
    {
        int i;
        for(i=0;i<len-1;i++)
        {
            int tmp=(1<<i)+(1<<(i+2));
            if((tmp&x)==tmp)
            {
                return 0;
            }
            tmp=(1<<i)+(1<<(i+1));
            if((tmp&x)==tmp)
            return 0;
        }
        return 1;
    }
    int cot[1100];
    int getnum(int x)
    {
        int sum=0;
        while(x)
        {
            if(x%2)
            sum++;
            x/=2;
        }
        return sum;
    }
    void init()
    {
        int i,j,tmp;
        for(i=0;i<=1024;i++)
        cot[i]=getnum(i);
        for(i=1;i<=10;i++)
        {
            tmp=(1<<i)-1;
            a[i].push_back(0);
            for(j=1;j<=tmp;j++)
            {
                if(check(j,i))
                {
                    a[i].push_back(j);
                }
            }
        }
    }
    int max(int a,int b)
    {
        return a>b?a:b;
    }
    int num[110];
    char bo[110][12];
    int main()
    {
        //freopen("dd.txt","r",stdin);
        init();
        int n,m,x;
        scanf("%d%d",&n,&m);
            memset(dp,0,sizeof(dp));
            int i,j,tmp;
            for(i=1;i<=n;i++)
            {
                scanf("%s",bo[i]+1);
            }
            for(i=1;i<=n;i++)
            {
                tmp=0;
                for(j=1;j<=m;j++)
                {
                    if(bo[i][j]=='P')
                    tmp=tmp*2+1;
                    else
                    tmp*=2;
                }
                num[i]=tmp;
            }
            tmp=a[m].size();
            int ans=0;
            for(i=0;i<tmp;i++)
            {
                if((num[1]|a[m][i])==num[1])
                {
                    int now=a[m][i];
                    dp[1][0][i]=cot[now];
                    ans=max(ans,dp[1][0][i]);
                }
            }
            for(i=2;i<=n;i++)
            {
                for(j=0;j<tmp;j++)
                {
                    if((num[i]|a[m][j])==num[i])
                    {
                        int now=a[m][j];
                        int s,t;
                        for(s=0;s<tmp;s++)
                        {
                            int pre=a[m][s];
                            if((num[i-1]|pre)==num[i-1])
                            {
                                if((pre&now)==0)
                               {
                                   if(i==2)
                                   dp[i][s][j]=dp[1][0][s]+cot[now];
                                   else
                                   {
                                       for(t=0;t<tmp;t++)
                                       {
                                           int ppre=a[m][t];
                                           if((num[i-2]|ppre)==num[i-2])
                                           {
                                               if((ppre&now)==0)
                                               {
                                                   dp[i][s][j]=max(dp[i][s][j],dp[i-1][t][s]+cot[now]);
                                               }
                                           }
                                       }
                                   }
                                   ans=max(ans,dp[i][s][j]);
                               }
                            }
                        }
                    }
                }
            }
            printf("%d\n",ans);
    
        return 0;
    }
    


    http://poj.org/problem?id=2411
    poj 2411

    一年前就看过了,当时根本就不敢碰,今天终于鼓起勇气发现并不是很难,没用long longWA一次,然后2Y。

    我们设dp[i][flag]表示第i行为状态flag的排列总数,这里我们设竖着放为1(上面那一段在第i行),其他为0(为0不一定为横着放,以为有可能上一行是竖着放的)。我们先预处理第一行的情况,然后对于dp[i][flag],(2<=i<n),dp[i][flag]=dp[i-1][pre]当且仅当以下条件满足:

    1:flag和pre在同一位上不能同时为1.即(pre&flag==0)

    2 :  我们设合法状态的定义如下:若一个状态中相邻两个1之间0的个数均为为偶数,则称它为合法状态。如长度为4的状态中1001(9) 1100是合法状态,而1010 1101不是。(注意对于同一个状态,有些长度下是合法的,有些长度下是不合法的,如1001和01001,在长度4下为合法状态,在长度5下则不是)

    则pre^now必须为合法状态(这里的^为异或运算)。

    注意到我们不用求dp[n][flag],因为若第n-1行确定了,则最后一行也已近确定了,则我们只要计算dp[n-1][flag]中属于合法状态的flag,求它们的和即可。若h*w为奇数,之间输出0即可,否则若h为1,输出1,不然的话就按上面的方法求。

    代码如下:其实可以加很多优化的,但人懒就没加了。。。

    #include <iostream>
    #include <string.h>
    #include <stdio.h>
    #include <algorithm>
    #include <vector>
    using namespace std;
    vector<int> t[12];
    int check(int x,int len)
    {
        int l=1,i;
        for(i=1;i<=len;i++)
        {
            if(x&(1<<(i-1)))
            {
                if((i-l)%2)
                return 0;
                l=i+1;
            }
        }
        if((len-l+1)%2)
        return 0;
        return 1;
    }
    void init()
    {
        int i,j;
        for(i=1;i<=11;i++)
        {
            int tmp=(1<<i)-1;
            t[i].push_back(0);
            for(j=1;j<=tmp;j++)
            {
                if(check(j,i))
                t[i].push_back(j);
            }
        }
    }
    long long dp[12][1<<11];
    long long solve(int h,int w)
    {
        if((h*w)%2)
        return 0;
        if(h==1)
        return 1;
        memset(dp,0,sizeof(dp));
        int len=t[w].size(),i,j,k;
        for(i=0;i<len;i++)
        {
            int now=t[w][i];
            if(i!=0||w%2==0)
            dp[1][now]=1;
    
        }
        int tt=(1<<w);
        for(i=2;i<=h-1;i++)
        {
            for(j=0;j<tt;j++)
            {
                int now=j;
                for(k=0;k<tt;k++)
                {
                    int pre=k;
                    if((pre&now)==0&&check(pre^now,w))
                    {
                        if(pre!=0||now!=0||w%2==0)
                        dp[i][now]+=dp[i-1][pre];
                    }
                }
            }
        }
        long long ans=0;
        for(i=0;i<len;i++)
        ans+=dp[h-1][t[w][i]];
        return ans;
    }
    int main()
    {
        //freopen("dd.txt","r",stdin);
        init();
        //printf("%d\n",t[11].size());
        int h,w;
        memset(dp,0,sizeof(dp));
        while(scanf("%d%d",&h,&w))
        {
            if(h+w==0)
            break;
            printf("%I64d\n",solve(h,w));
        }
        return 0;
    }
    



  • 相关阅读:
    实验5 编写调试有多个段的程序
    实验四 [bx]和 loop 的使用
    实验三
    实验二
    第一章
    汇编语言第二章知识梳理
    实验一:查看CPU和内存,用机器指令和汇编指令编程
    实验9
    实验5
    实验4:
  • 原文地址:https://www.cnblogs.com/jiangu66/p/2998011.html
Copyright © 2011-2022 走看看