状压DP入门题集锦


POJ 3254 Corn Fields
题意:
一块n*m的田,1表示这个地方可以种植,0代表这个地方不能种植。植物种植还必须满足两株植物不能相邻(横竖都不行)。问共有几种种植方法,而且当什么都不种时认为是一种方法。
解题思路:
种植用1表示,不种植用0表示。每一行的情况就可以用一个二进制数state来存储。state的范围是 [0 ~ 1<< state).
dp[i][state]表示第i行状态为state的情况下满足条件的数目。
状态转移方程为:dp[i][state] += dp[i-1][pre_state];这个state和pre_state必须满足意义所给的条件,即左右不相邻,上下不相邻。那么 第i行状态为state的情况为第i-1行所有满足条件的状态为pre_state相加而成。
最后的答案为最后一行所有状态的情况和相加而得。

位运算需要注意的地方:
1.注意打括号
2. 1 & (state>>i) 和 state & ( 1<< i ) 还是有差别的。 前者的答案只有0和1,而后者的答案有0和可能的正数。判断的时候还是要注意下写法
3. 当前行是否相邻的判断条件是: state & (state<<1) 是否为 0
4. 上下两行是否相邻的判断条件是: state & pre_state 是否为 0

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int dp[15][1<<15];
bool is[15][15];
int n,m;
const int MOD = 1e8;

bool check(int x,int state)
{
    if( (state & (state<<1)) ) return false;
    for(int i=1;i<=m;i++) 
    {
        if( !is[x][i])
        {
            if( ((1<<(m-i)) & state) !=0 ) return false;
        }

    }
    return true;
}

int main()
{
    while( scanf("%d%d",&n,&m)!=EOF)
    {
          memset(dp,0,sizeof(dp));
          for(int i=1;i<=n;i++) for(int j = 1;j<=m;j++) scanf("%d",&is[i][j]);

          dp[0][0] = 1;
          long long ans = 0;
          for(int i=1;i<=n;i++)
          {
              for(int j = 0; j < (1<<m) ;j++)
              {
                  if(check(i,j)) {//判断当前行满足条件的state
                      for(int k = 0; k < (1<<m); k++ )
                      {//枚举上一行的pre_state进行更新
                           if( !(k&j) ) dp[i][j] += dp[i-1][k];
                      }
                  }
                  if(i==n) ans = (ans + dp[i][j]) % MOD;
              }
          }
         printf("%lld\n",ans); 
    }
    return 0;
}

POJ 1185 炮兵阵地
解题思路:
三维DP数组来表示状态的转移。
dp[i][j][k]表示在第i行状态为j且第x-1行状态为k时的方案数。因为第i行放炮兵与第i-1行和i-2行相关。
这里有个优化:对于每一行的状态都有 1< < m 种可能,如果每个枚举会TLE。其实每一行的最多状态数也只有60种。因为m最大为10,且每3个不能相邻,可枚举出来这60种情况。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <math.h>
using namespace std;

bool is[150][15];
int dp[110][1<<11][1<<11];
int n,m,t=0;
int can[150];
void Init()
{
    for(int i=0;i<(1<<10);i++) 
    {
        if((i&(i<<1)) || (i&(i<<2))) continue;
        can[++t] = i; 
    }
    return;
}
int check(int x,int state)
{
    int cnt = 0;
    if( state >= pow((int)2,m) ) return -1;
    for(int i=1;i<=m;i++)
    {
        if( ((1<<(i-1)) & state )) cnt++;
        if(!is[x][i])
        {
            if( ((1<<(m-i)) & state)) return -1;
        }
    }
    return cnt;
}
int main()
{
    //freopen("duipai.out","r",stdin);
    Init();  scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
          for(int j=1;j<=m;j++)
          {
              char op;  scanf(" %c",&op);
              if( op =='P') is[i][j] = true;
              else is[i][j] = false;
          }

        int ans = 0,now;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=t;j++)
            {
                int cnt = check(i,can[j]);
                if( cnt!=-1 ){
                    //cout<<i<<"  "<<can[j]<<"  "<<cnt<<endl;
                    for(int k = 1; k<=t;k++)
                    {
                        if( !(can[k]&can[j]) ){
                          dp[i][can[j]][can[k]] = cnt;
                          now = 0;
                            for(int q = 1;q<=t;q++)
                            {
                                if(i==1) ;
                                else {
                                   if(!(can[q]&can[j])) now = max(now,dp[i-1][can[k]][can[q]]);
                                  }
                             }
                        }
                        dp[i][can[j]][can[k]] += now;
                    if(i==n) ans = max(ans,dp[i][can[j]][can[k]]);
                    }
                }
            }
        }
        printf("%d\n",ans);
    return 0;
}

POJ 3311 Hie with the Pie
题意:
一个送外卖的人,要将外卖全部送去所有地点再回到店离,求最短路。
解题思路:
旅行商问题。先用floyed得到每两个点之间的距离,然后状压dp解决旅行商问题。
类似题目:http://blog.csdn.net/codeblocksm/article/details/48210671

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int path[15][15];
const int INF = 0x3f3f3f;
int dp[15][1<<15];
int n;
void floyed()
{
      for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
          for(int j=1;j<=n;j++)
              path[i][j] = min(path[i][j],path[i][k]+path[k][j]);
              return ;
}
int main()
{
    while( scanf("%d",&n)!=EOF && n )
    {
        n++;
        for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cin>>path[i][j]; 

        floyed();
        memset(dp,INF,sizeof(dp));
        dp[1][1] = 0;
        for(int i=1; i<(1<<n);i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(((i>>(j-1))&1)!=0  ){
                    for(int k=1;k<=n;k++)
                    {
                        if(((i>>(k-1))&1)==0 ) dp[k][i|(1<<(k-1))] = min(dp[k][i|(1<<(k-1))],dp[j][i]+path[j][k]);
                    }
                }
            }
        }

        int ans = INF;
        for(int i=2;i<=n;i++)
        {
            ans = min(ans,dp[i][(1<<n)-1]+path[i][1]); 
        }
        cout<<ans<<endl;
    }
    return 0;
}

Codeforce 580D Kefa and Dishes

题意:
有n盘菜,每盘菜都有一个美味值。但是你只能选m盘菜。并且这些吃菜的顺序能影响总美味值。如果i当且仅当在j之前吃,那么会额外加一些美味值,这样的组合有k组。求选m盘菜吃能获得的最大的美味值。
解题思路:
dp[i][sta] 表示当前吃的菜是i,状态为sta的情况。
更新总答案的时候在更新sta的时候可以获得。

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
LL dp[20][1<<18];
LL wei[20];
LL add[20][20]; 
int n,m,k;
bool check(int sta) 
{
    int now = 0;
    for(int i=1;i<=n;i++)
    {
        if( sta & (1<<(i-1))) now++;
    }
    if( now == m) return true;
    return false;
}
int main()
{
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++) scanf("%I64d",&wei[i]);
    for(int i=1;i<=k;i++)
    {
        int u,v,w; cin>>u>>v>>w;
        add[u][v] = w;
    }

    memset(dp,0,sizeof(dp));
    LL ans = 0;
    for(int i = 1; i<(1<<n);i++)
    {
        for(int j = 1; j<=n;j++)
        {
            if( i&(1<<(j-1))) {
                if( i == (1<<(j-1))) {
                    dp[j][i] = wei[j];
                    if(check(i)) ans = max(ans,dp[j][i]);//容易忽略的地方
                } 


            for(int k = 1;k<=n;k++)
            {
                if( !( i&(1<<(k-1))))
                {
                    int sta = i|(1<<(k-1));
                    dp[k][sta] = max( dp[k][sta],dp[j][i] + add[j][k] + wei[k]);
                    if(check(sta)) ans = max(ans,dp[k][sta]);//顺便更新当前的值
                 }
            }
          }
        }
    }
    cout<<ans<<endl; 
    return 0;
}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值