zoukankan      html  css  js  c++  java
  • 状压DP

    状压DP

    描述状态的维数,维度

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

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

    难点

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

    初始化

    常见操作

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

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

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

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

    取出x最靠右的0:xx = (~x & 位数), 取xx的lowbit

    依次取出一个数的所有的 1 : 取一个lowbit, 减一个lowbit

    依次取出一个数的所有的 0 : 同理,取一个0,减一个0(对xx操作

    枚举非空子集: 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
    ",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
    ",ans);
    	return 0;
    }
    
    

    这里要注意的一个细节

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

    [NOI2001]炮兵阵地

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

    题目大意
    给出一张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
    ", 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个合法情况
    ",i, can[i].num);
    //			for(int j = 1; j <= can[i].num; j++) printf("士兵数:%d ; 状态: %d 
    ", 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
    ",ans);
    	//为什么只用前一个和当前的:我们只需要这样就可以表示出所有要用的状态了 
    	return 0; 
    }
    

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

  • 相关阅读:
    2.创建第一个启用了服务和数据驱动的silverlight5应用程序
    1.搭建siverlight 5开发环境
    读《C程序设计语言》笔记1
    读《C程序设计语言》笔记2
    郑州轻工业大学OJ 2834.小凯的书架 题解 线段树二分
    洛谷P3834 【模板】可持久化线段树 2/POJ2104 Kth Number 题解 主席树
    洛谷P6883 [COCI20162017#3] Kroničan 题解 状压DP入门题
    CF1586D. Omkar and the Meaning of Life 题解 纪念一下第一道AC的交互题
    冒泡事件
    JavaScript 对象是词典
  • 原文地址:https://www.cnblogs.com/tyner/p/11391500.html
Copyright © 2011-2022 走看看