状压DP

状压DP

描述状态的维数,维度

首先找题目中在10~20范围内的参数(如果有这样的范围,那么这题就hin可能是状压),它很有可能是一个。

再看题目中的限制条件,它可能也要加进去

难点

状态转移 : 用二进制(或三进制或更⑥的)表示状态,用位运算状态的修改

初始化

常见操作

取出x第i位: (1<<(i-1))&x

将x第i位变成1: x |= (1<<(i-1))

将x第i位变成0 : x &= (~(1<<(i-1)))

取出x最靠右的1: lowbit(x) = x&(-x)

将x最靠右的1去掉:x = x&(x-1)x -= lowbit(x)

枚举子集: for(int x = sta ; x ; x = ( ( x - 1 )&sta) )

栗提(我没有状压基础,所以觉得题都挺好的

[SCOI2005]互不侵犯

https://www.luogu.org/problem/P1896

这种求合法的方案的题目,一般需要对与当前状态相契合的合法方案数求和

首先这题肯定是压N, 然后有国王数这个限制,而且前一行的状态也是个限制,之后综合起来,考虑状态的转移,注意怎么初始化和统计答案。

#include<cstdio>
using namespace std;
const int MAXN = 20;
#define ll long long

int n, KK;
//f[i][s][k]表示前i行(包括第i行),第i行的状态为s,已选k个国王的方案 
//f[i][s][k] = Σf[i-1][s'][k - k_num[s]] ,即上一行与当前行相契合的方案都要加进去 
ll f[20][1024][40]; 
int MAX, cnt, can[100], k_num[100];

int main() {
    scanf("%d%d",&n, &KK);
    int MAX = (1<<n)-1;
    int sum;
    int tmp;
    for(int i = 0; i <= MAX; ++i) {//预处理每一行的合法方案和它对应的国王个数 //预处理要全面!!从0开始 
        if(i&(i<<1)) continue;//把同行不合法的去掉
        can[++cnt] = i;
        sum = 0;
        tmp = i;
        while(tmp) sum += (tmp&1), tmp = tmp>>1;
        k_num[cnt] = sum;
    }
    for(int j = 1; j <= cnt; j++) f[1][j][k_num[j]] = 1;//初始化? 
    for(int i = 2; i <= n; i++) {//枚举i,s,s'(这里的j),求和 
        for(int s = 1; s <= cnt; s++) {
            for(int j = 1; j <= cnt; j++) {
                if((can[s]&can[j]) || ((can[s]<<1)&can[j]) || ((can[s]>>1)&can[j])) continue;//判断是否和前一行冲突(考虑国王的影响范围后易得)
                for(int k = k_num[s]; k <= KK; k++) f[i][s][k] += f[i-1][j][k - k_num[s]];//枚举k 
            }
        }
    }
    long long ans = 0;
    for(int j = 1; j <= cnt; j++) ans += f[n][j][KK];//统计答案 
    printf("%lld",ans);
    return 0;
}

JDOJ 1009 五子棋

https://neooj.com:8082/oldoj/problem.php?id=1099

题目大意
有N个人在进行五子棋比赛,每个人都有一 个初始经验值st_exp_i。若i,j比赛一场,i会获 得exp_i,j的经验值,j会获得exp_j,i的经验值, 当前经验值高的人获胜,获胜者会得到sco_i,j 的分数,现在1号队员可以安排比赛顺序,他 希望自己得分最高,请问他最高可以得多少 分。

数据范围 : 1<=N<=13

首先一个贪心,只需要求1号的最高得分,所以我们只需要考虑1号和剩下n-1个人的比赛,并且还要先考虑,因为他们在变 强。这样我们就可以只压一维的N了。

因为经验与顺序无关,所以可以根据状态直接算出1号的经验,所以不用再开一维,当状态不好表示时,试着增加维度。(即与顺序有关那就hin难了)

#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN = 14;

int n, T;
int st_exp[MAXN], exp[MAXN][MAXN], sco[MAXN][MAXN];
int f[1<<MAXN];
int MAX;

void init() {
    for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= n; j++) scanf("%d",&exp[i][j]);
    for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= n; j++) scanf("%d",&sco[i][j]);
    for(int i = 1; i <= n; i++) scanf("%d",&st_exp[i]);
}

int getexp(int s) {
    int exp1 = st_exp[1];
    for(int i = 1; i <= n; i++) if(s&(1<<(i-1))) exp1 += exp[1][i+1];
    return exp1;
}

int main() {
    scanf("%d",&T);
    while(T--) {
        memset(f, 0, sizeof(f));
        scanf("%d",&n);
        init();
        n--;
        //考虑安排先把前n-1个人安排着和1号vs 
        MAX = (1<<n)-1;
        int exp1;//根据状态直接算出1号的经验
        for(int s = 0; s <= MAX; s++) {
            exp1 = getexp(s);
            for(int j = 1; j <= n; j++) {
                if(s&(1<<(j-1))) continue;
                if(exp1 > st_exp[j+1]) f[s|(1<<(j-1))] = max(f[s|(1<<(j-1))], f[s] + sco[1][j+1]);
                else f[s|(1<<(j-1))] = max(f[s|(1<<(j-1))], f[s]);
            }
        }
        printf("%d\n",f[(1<<n)-1]);
    }
    return 0; 
}

[USACO06NOV]玉米田Corn Fields

https://www.luogu.org/problem/P1879

和上面的互不侵犯一样

#include<cstdio>
#include<algorithm>
using namespace std;
const int mod = 100000000;

int n,m, ans;
int f[13][1000];//f[i][j]: 第i行选用状态can[i].st[j]这个状态时,方案数 
struct node{
    int num;
    int st[5000];
}can[13];//can[i]:第i行所有合法的状态,及状态数 

void getstate(int hi, int t) {
    int num = 0;
    for(int i = 0; i < (1<<m); i++) {//i==0 ->可以不种草 
        if((i&(i<<1)) || (i&(i>>1)) || (t&i)) continue;//t&i==1 -> 非法种草 
        can[hi].st[++num] = i;
    }
    can[hi].num = num;
} 

void init() {//初始化每一行合法方案  
    int t, x;
    for(int i = 1; i <= n; i++) {
        t = 0;
        for(int j = 1; j <= m; j++) {
            scanf("%d",&x);
            t = (t<<1)+1-x;//把这一行的状态弄成十进制,注:1表示不能种草(这样方便判断种草的位置是否合法)
        }
        getstate(i, t);
    }
}

int main() {
    scanf("%d%d",&n,&m);
    init();
    for(int j = 1; j <= can[1].num; j++) f[1][j] = 1;//初始化第一行 
    for(int i = 1; i < n; i++) 
        for(int j = 1; j <= can[i].num; j++)
            for(int k = 1; k <= can[i+1].num; k++) {//刷表法 
                if(can[i+1].st[k] & can[i].st[j]) continue;
                else f[i+1][k] += f[i][j];
            }
//  for(int i = 2; i <= n; i++) 
//      for(int j = 1; j <= can[i].num; j++) {
//          f[i][j] = 0;//填表法比上面的多个初始化 
//          for(int k = 1; k <= can[i-1].num; k++) {
//              if(can[i].st[j]&can[i-1].st[k]) continue;
//              else f[i][j] += f[i-1][k];
//          }
//      }
    for(int j = 1; j <= can[n].num; j++) ans = (ans+f[n][j]) %mod;
    printf("%d\n",ans);
    return 0;
}

这里要注意的一个细节

我们在做这种有的位置是障碍的题目(如上题的坏田,如下题的高地)时,我们可以把障碍处填1可以放东西的地方填0,这样,我们一个&就可以判断当前的位置是否可以放东西进而求出每行合法的状态。要注意操作是<<左移,想想我们判断的数 的二进制

[NOI2001]炮兵阵地

https://www.luogu.org/problem/P2704

1881.png

题目大意
给出一张n*m的地图,其中有一些是平原,一些是高地,现在有一些炮兵要在这块地图上进行部署,炮兵无法部署在高地上,同时他们不能够互相攻击
问地图上最多能部署多少炮兵部队。
右图黑块为灰块可
数据范围
N≤100,M≤10

错误代码:

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

int n,m,debug;
int f[3][60][60];//f[i][st1][st2]:当前为第i行,第i行状态为st[st1], 第i-1行状态为st[st2]时的最大方案数 
//影响答案的只有三行 

struct node{
    int num, st[60];
    int num_s[60];//状态st[i]所拥有的士兵个数为num_s[i] 
}can[100+9];

int getone(int x) {
    int num = 0;
    while(x) x -= x & -x, num++;
    return num;
}
void getstate(int hang, int st) {
    int num = 0;
    for(int i = 0; i < (1<<m); i++) {
        if( (i&(i<<1)) || (i&(i<<2)) || (i&(i>>1)) || (i&(i>>2)) || (i&st)) continue;
        can[hang].st[++num] = i;
        can[hang].num_s[num] = getone(i);
    }
    can[hang].num = num;
}
void init() {//get每行合法的情况 
    string s;
    int t, tmp;
    for(int i = 1; i <= n; i++) {
        cin>>s;
        t = 0;
        for(int j = 0; j < m; j++) {
            tmp = s[j]=='H' ? 1 : 0;
            t = (t<<1)+tmp;//山地不放
        }
//        if(debug == 2) printf("第%d行的t值: %d\n", i, t);
        getstate(i, t);
    }
}

void pre() {//初始化一二行 
    for(int j = 1; j <= can[1].num; j++) f[1][j][0] = can[1].num_s[j];
    for(int j = 1; j <= can[2].num; j++) 
        for(int k = 1; k <= can[1].num; k++) {
            if(can[2].st[j]&can[1].st[k]) continue;
            f[2][j][k] = can[2].num_s[j] + can[1].num_s[k];
        }
}

int main() {
//  freopen("02.in", "r", stdin);
//  freopen("02.out", "w", stdout); 
    debug = 1;
    scanf("%d%d",&n,&m);
    init();
    pre();
//    if(debug == 1) { 
//      for(int i = 1; i <= n; i++) {
//          printf("第%d行的合法情况如下, 共%d个合法情况\n",i, can[i].num);
//          for(int j = 1; j <= can[i].num; j++) printf("士兵数:%d ; 状态: %d \n", can[i].num_s[j] ,can[i].st[j]);
//      } 
//  }
    for(int i = 3; i <= n; i++) 
        for(int j = 1; j <= can[i].num; j++) 
            for(int k = 1; k <= can[i-1].num; k++) {//士兵数从上往下只增不减,所以不用初始化f[i][j][k]
                if(can[i].st[j]&can[i-1].st[k]) continue;//填表法 
                for(int up2 = 1; up2 <= can[i-2].num; up2++) {
                    if(can[i-2].st[up2]&can[i-1].st[k] || can[i-2].st[up2]&can[i].st[j]) continue;
                    f[i%3][j][k] = max(f[i%3][j][k], f[(i-1)%3][k][up2]+can[i].num_s[j]);
                }
            }
    int ans = 0;
    for(int j = 1; j <= can[n].num; j++)
        for(int k = 1; k <= can[n-1].num; k++) {
            if(can[n].st[j]&can[n-1].st[k]) continue;//??? 
            ans = max(ans, f[n%3][j][k]);
        }
    printf("%d\n",ans);
    //为什么只用前一个和当前的:我们只需要这样就可以表示出所有要用的状态了 
    return 0; 
}

例题就到这里哈,有兴趣的可以看看标签

转载于:https://www.cnblogs.com/tyner/p/11391500.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值