状态压缩dp题目

Corn Fields

题意:一个矩阵里有很多格子,每个格子有两种状态,可以放牧和不可以放牧,可以放牧用1表示,否则用0表示,在这块牧场放牛,要求两个相邻的方格不能同时放牛,即牛与牛不能相邻。问有多少种放牛方案(一头牛都不放也是一种方案)

#include<stdio.h>
#include<string.h>

int map[15];
int st[1<<13];
int dp[15][1<<13];

int judge(int x,int y)
{
    return map[x]&st[y];
}
int main()
{
    int m,n;
    while(scanf("%d %d",&m,&n)!=EOF)
    {
        int i,j;
        memset(map,0,sizeof(map));
        memset(st,0,sizeof(st));
        memset(dp,0,sizeof(dp));
        for(i=1;i<=m;i++)
        {
            for(j=1;j<=n;j++)
            {
                int x;
                scanf("%d",&x);
                if(x==0) map[i]+=1<<(j-1);
            }
        }

        int k=0;
        for(i=0;i<1<<n;i++)
        {
            if(i&(i<<1)) continue;
            st[k++]=i;
        }

        int l=k;
        for(i=0;i<l;i++)
        {
            if(!judge(1,i))
                dp[1][i]=1;
        }

        for(i=2;i<=m;i++)
        {
            for(j=0;j<l;j++)
            {
                if(!judge(i,j))
                {
                    for(k=0;k<l;k++)
                    {
                        if(!judge(i-1,k))
                        {
                            if(!(st[j]&st[k]))
                            {
                                dp[i][j]+=dp[i-1][k];
                            }
                        }
                    }
                }
            }
        }
        int ans=0;
        for(i=0;i<l;i++)
        {
            ans+=dp[m][i];
            ans%=1000000000;
        }
        printf("%d\n",ans);
    }
    return 0;
}


炮兵阵地

题意:一个方格组成的矩阵,每个方格可以放大炮用0表示,不可以放大炮用1表示(原题用字母),让放最多的大炮,大炮与大炮间不会互相攻击。

#include<stdio.h>
#include<string.h>

int map[110];    //压缩后的地图
int st[70];      //符合条件的状态
int v[70];       //相对于st[i]存在阵地的个数
int dp[110][70][70];   //dp[i][j][k]:第i行其状态为j,第i-1行的状态为k的最大值

int max(int x,int y)
{
    return x<y?y:x;
}

int judge1(int i,int j)   //第i行和第j种状态是否匹配
{
    return map[i]&st[j];
}

int judge2(int i,int j)   //两种状态是否匹配
{
    return st[i]&st[j];
}

int fun(int k)    //根据st[k]求v[k]
{
    int i=0;
    while(k)
    {
        if(k%2) i++;
        k/=2;
    }
    return i;
}

int main()
{
    int n,m;
    while(scanf("%d %d",&n,&m)!=EOF)
    {
        getchar();
        int i,j,k,f;
        char c;
        memset(map,0,sizeof(map));
        memset(st,0,sizeof(st));
        memset(dp,0,sizeof(dp));
        memset(v,0,sizeof(v));
        for(i=0;i<n;i++)    //压缩地图
        {
            for(j=0;j<m;j++)
            {
                scanf("%c",&c);
                if(c=='H') map[i]+=1<<j;
            }
            getchar();
        }

        int l=0;
        for(i=0;i<1<<m;i++)     //求状态和其相对应的个数
        {
            if(i&i<<1) continue;
            if(i&i<<2) continue;
            st[l++]=i;
            v[l-1]=fun(i);
        }

        for(i=0;i<l;i++)       //初始化第一行
        {
            if(judge1(0,i)) continue;
            for(j=0;j<l;j++)
            {
                dp[0][i][j]=v[i];
            }
        }

        for(i=0;i<l;i++)       //初始化第二行
        {
            if(judge1(1,i)) continue;  
            for(j=0;j<l;j++)
            {  
                if(judge2(i,j)) continue;  
                dp[1][i][j] = max(dp[1][i][j],dp[0][j][0]+v[i]);  
            }  
        }  

        for(i=2;i<n;i++)       //dp求解
        {
            for(j=0;j<l;j++)
            {
                if(judge1(i,j)) continue;
                for(k=0;k<l;k++)
                {
                    if(judge1(i-1,k)) continue;
                    if(judge2(k,j)) continue;
                    for(f=0;f<l;f++)
                    {
                        if(judge1(i-2,f)) continue;
                        if(judge2(j,f)) continue;
                        if(judge2(k,f)) continue;
                        dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][f]+v[j]);
                    }
                }
            }
        }

        int ans=0;
        for(i=0;i<l;i++)   //求最大值ans
        {
            for(j=0;j<l;j++)
            {
                if(ans<dp[n-1][i][j]) ans=dp[n-1][i][j];
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}


Hie with the Pie

原来路径不是对称的!sad

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

#include<stdio.h>
#include<string.h>

int dis[12][12];    //图
int dp[12][1<<12];  //dp[i][j]:在第i个点的时候j状态最优解

int min(int x,int y)
{
    return x<y?x:y;
}

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF&&n)
    {
        int i,j,k;
        memset(dp,0,sizeof(dp));
        for(i=0;i<=n;i++)
        {
            for(j=0;j<=n;j++)
            {
                scanf("%d",&dis[i][j]);
            }
        }
        for(k=0;k<=n;k++)    //flody重新算两点之间的最短距离
        {
            for(i=0;i<=n;i++)
            {
                for(j=0;j<=n;j++)
                {
                    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
                }
            }
        }
        int s;
        /*其实我还是想不通为什么要s打头,
        可能s递增表明了到达的点的个数递增,
        直到最后完成遍历*/
        for(s=0;s<(1<<n);s++)      //枚举所有状态,用位运算表示  
        {
            for(i=1;i<=n;i++)       
            {
                if(s&(1<<(i-1)))  //状态S中已经过城市i 
                {
                    if(s==(1<<(i-1)))//状态S只经过城市I,最优解自然是从0出发到i的dis,这也是DP的边界
                        dp[i][s]=dis[0][i];
                    else             //如果S有经过多个城市
                    {
                        dp[i][s]=1<<30;
                        for(j=1;j<=n;j++)
                        {
                            if(s&(1<<(j-1))&&i!=j)  //枚举不是城市I的其他城市 
                                dp[i][s]=min(dp[i][s],dp[j][s^(1<<(i-1))]+dis[j][i]);  //在没经过城市I的状态中,寻找合适的中间点J使得距离更短
                        }
                    }
                }
            }
        }
        int ans=dp[1][(1<<n)-1]+dis[1][0];
        for(i=2;i<=n;i++)
        {
            ans=min(ans,dp[i][(1<<n)-1]+dis[i][0]);
        }
        printf("%d\n",ans);
    }
    return 0;
}


Travelling

三进制,但是做法和上面一题一样

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

#include<stdio.h>
#include<string.h>

int tri[12]={1,3,9,27,81,243,729,2187,6561,19683,59049};  //3^i
int map[15][15];
int dig[59049][11];      //dig[s][i]表示对于状态s而言第i位是多少
int dp[59049][11];       //dp[s][i]表示在第i个点的时候s状态最优解


int min(int x,int y)
{
    return x<y?x:y;
}

int main()
{
    int n,m;
    int i,j,k,l;
    memset(dig,0,sizeof(dig));
    for(i=0;i<59049;i++)           //求dig
    {
        int k=i;
        for(j=0;j<11;j++)
        {
            dig[i][j]=k%3;
            k/=3;
            if(k==0) break;
        }
    }

    while(scanf("%d %d",&n,&m)!=EOF)
    {
        memset(map,-1,sizeof(map));
        for(i=0;i<m;i++)
        {
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            if(map[x][y]==-1)
                map[x][y]=map[y][x]=z;
            else if(map[x][y]>z)
                map[x][y]=map[y][x]=z;
        }
        for(i=1;i<=n;i++) map[i][i]=0;

        memset(dp,-1,sizeof(dp));

        int s;
        for(s=0;s<tri[n];s++)           //一共3^n种
        {
            for(i=1;i<=n;i++)
            {
                if(!dig[s][i-1]) continue;   //判断第i个点是否在状态s中
                for(j=1;j<=n;j++)            //遍历s,判断s是否就i一个点
                {
                    if(j==i) continue;
                    if(dig[s][j-1]) break;
                }
                if(j==n+1)                  //就一个点就初始化为0
                {
                    dp[s][i]=0;
                    continue;
                }
                dp[s][i]=1<<30;             //还有很多点,就初始化为最大值
                int w=0;                    //用w来表示s状态中第i个点少经过一次的状态
                for(j=0;j<11;j++)
                {
                    if(j==i-1) w+=tri[j]*(dig[s][j]-1);
                    else w+=tri[j]*dig[s][j];
                }
                for(j=1;j<=n;j++)
                {
                    if(dp[w][j]!=-1&&map[j][i]!=-1)
                    dp[s][i]=min(dp[s][i],dp[w][j]+map[j][i]);     //状态转移方程
                }
            }
        }
        int ans=1<<30;
        for(i=0;i<tri[n];i++)
        {
            int min=1<<30;
            for(j=0;j<n;j++)
            {
                if(!dig[i][j]) break;
                if(dp[i][j+1]!=-1&&min>dp[i][j+1]) min=dp[i][j+1];
            }
            if(j==n&&ans>min) ans=min;              //保证所有点都经过
        }
        if(ans==1<<30) puts("-1");
        else printf("%d\n",ans);
    }
    return 0;
}


Islands and Bridges

做法和前面的一样......

#include<stdio.h>
#include<string.h>

int v[15];
int map[15][15];
int dp[1<<13][15][15];
__int64 way[1<<13][15][15];

int max(int x,int y)
{
    return x<y?y:x;
}

int main()
{
    int q;
    while(scanf("%d",&q)!=EOF)
    {
        while(q--)
        {
            int n,m;
            int i,j,k;
            scanf("%d %d",&n,&m);
            for(i=1;i<=n;i++) scanf("%d",&v[i]);
            memset(map,0,sizeof(map));
            for(i=0;i<m;i++)
            {
                int u,v;
                scanf("%d %d",&u,&v);
                map[u][v]=map[v][u]=1;
            }
            if(n==1)                             //只有一个点的时候,特判
            {
                printf("%d 1\n",v[1]);
                continue;
            }
            int s;
            memset(dp,-1,sizeof(dp));
            memset(way,0,sizeof(way));

            for(s=0;s<(1<<n);s++)
            {
                for(i=1;i<=n;i++)
                {
                    if(!(s&1<<(i-1))) continue;
                    for(j=1;j<=n;j++)
                    {
                        if(i==j) continue;
                        if(!map[i][j]) continue;
                        if(!(s&1<<(j-1))) continue;
                        if(dp[s][i][j]==-1&&s==((1<<(i-1))+(1<<(j-1))))     //初始化
                        {
                            dp[s][i][j]=v[i]+v[j]+v[i]*v[j];
                            way[s][i][j]=1;
                            continue;
                        }
                        for(k=1;k<=n;k++)
                        {
                            if(i==k||k==j) continue;
                            if(!map[j][k]) continue;
                            if(!(s&1<<(k-1))) continue;
                            if(dp[s^1<<(i-1)][j][k]==-1) continue;
                            int temp=dp[s^1<<(i-1)][j][k]+v[i]+v[i]*v[j];
                            if(map[i][k]) temp+=v[i]*v[j]*v[k];        //没看到这个条件wa到死啊
                            if(dp[s][i][j]<temp)
                            {
                                way[s][i][j]=way[s^1<<(i-1)][j][k];
                                dp[s][i][j]=temp;
                            }
                            else if(dp[s][i][j]==temp)
                                way[s][i][j]+=way[s^1<<(i-1)][j][k];
                        }
                    }
                }
            }
            int ans=-1;
            __int64 num=0;
            for(i=1;i<=n;i++)
            {
                for(j=1;j<=n;j++)
                {
                    if(map[i][j])
                    {
                        if(ans<dp[(1<<n)-1][i][j])
                        {
                            ans=dp[(1<<n)-1][i][j];
                            num=way[(1<<n)-1][i][j];
                        }
                        else if(ans==dp[(1<<n)-1][i][j])
                            num+=way[(1<<n)-1][i][j];
                    }
                }
            }
            if(ans==-1)puts("0 0");       //走不了的时候
            else printf("%d %I64d\n",ans,num/2);
        }
    }
    return 0;
}


Most Powerful

这道题敲繁了,只要一维dp就可以了.....
不想写注释了,感觉都差不多

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

#include<stdio.h>
#include<string.h>

int map[15][15];
int dp[1<<10][15];     //dp[s][i]在状态为s的情况下,i是最后一个消失的最优值

int max(int x,int y)
{
    return x<y?y:x;
}

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF&&n)
    {
        int i,j,k;
        for(i=1;i<=n;i++)
            for(j=1;j<=n;j++)
                scanf("%d",&map[i][j]);
        memset(dp,0,sizeof(dp));
        for(i=1;i<=n;i++) dp[1<<10-1][i]=0;
        int s;
        for(s=(1<<n)-1;s>0;s--)
        {
            for(i=1;i<=n;i++)
            {
                if(s&1<<(i-1)) continue;
                for(j=1;j<=n;j++)
                {
                    if(!(s&1<<(j-1))) continue;
                    dp[s][i]=max(dp[s][i],map[j][i]);
                    for(k=1;k<=n;k++)
                    {
                        if(s&1<<(k-1)||k==i) continue;
                        dp[s][i]=max(dp[s][i],dp[s+(1<<(i-1))][k]+map[j][i]);
                    }
                }
            }
        }
        int ans=-1;
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
            {
                if(i==j) continue;
                ans=max(ans,dp[1<<(i-1)][j]);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

Mondriaan's Dream

dp思想倒不是很繁,但是转化为0和1的|和&深深的发现对于运算符的运用还是不够啊

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

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

#include<stdio.h>
#include<string.h>

int n,m;
int st[1<<11];
__int64 dp[1<<11][15];  //dp[s][i]表示在第i行时状态为s的时候有多少个

int check(int s)     //连续的1出现为偶数个(横着放)?
{
    int i;
    int k=0;
    while(s)
    {
        if(s%2==0)
        {
            if(k%2==1) return 0;
            k=0;
        }
        else k++;
        s>>=1;
    }
    if(k%2) return 0;
    return 1;
}

int match(int s,int t)     //前一行的状态t和当前行的状态s是否匹配
{
    if((s|t)!=(1<<m)-1) return 0;   //出现竖着00的情况
    return check(s&t);          //s&t能得到横着的1的情况
}

int main()
{

    int i,j,k,s,t;
    for(s=0;s<(1<<11);s++)
    {
        if(check(s)) st[s]=1;
        else st[s]=0;
    }
    while(scanf("%d %d",&n,&m)!=EOF&&n+m)
    {
        memset(dp,0,sizeof(dp));
        for(s=0;s<(1<<m);s++)                //第一行初始化
            if(st[s])                        //优化,减少运算
                dp[s][1]=1;
        for(i=2;i<=n;i++)
        {
            for(s=0;s<(1<<m);s++)
            {
                for(t=0;t<(1<<m);t++)
                {
                    if(!match(s,t)) continue;
                    dp[s][i]+=dp[t][i-1];
                }
            }
        }
        printf("%I64d\n",dp[(1<<m)-1][n]);
    }
    return 0;
}

摘自:http://blog.csdn.net/accry/article/details/6607703

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值