状压dp

位运算技巧:

去掉最后一位             x>>1
在最后加一个0           x<<1
在最后加一个1           x<<1|1
把最后一位变成1       x|1
把最后一位变成0       x|1-1
把最后一位取反         x^1
把右数第k位变成1     x|(1<<(k-1))
把右数第k位变成0     x&~(1<<(k-1))
右数第k位取反           x^(1<<(k-1))
取末三位                    x&7
取末k位                     x&(1<<k-1)
取右数第k位              x>>(k-1)&1
把末k位变成1            x|(1<<k-1)
末k位取反                  x^(1<<k-1)
把右边连续的1变成0   x&(x+1)

把右起第一个1变成0   x&(x-1)
把右起第一个0变成1   x|(x+1)
把右边连续的0变成1   x|(x-1)
取右边连续的1            (x^(x+1))>>1
去掉右起第一个1的左边  x&(x^(x-1))
判断x是否是y的子集   (x|y)==y?1:0 你有我也有

                                   (x&y)==x?1:0

两个式子应当是等价的,但是洛谷P2074用下面的语句过不了

判断x的第i位是不是1  (((1<<(i-1))&x)>0)?1:0

bitset相关函数:

bitset<10> bit(8);
    bit.count();  //count函数用来求bitset中1的位数,bit中共有1个1
    bit.size();  //size函数用来求bitset的大小,一共有10位
    bit.test(0);  //test函数用来查下标处的元素是0还是1,并返回false或true,此处为1返回true为0返回false 
    bit.any();  //any函数检查bitset中是否有1
    bit.none();  //none函数检查bitset中是否没有1
    bit.all();  //all函数检查bitset中是全部为1

 

数据量不超过20的方案统计问题,考虑用状压dp

二维状压dp,一般由行作为动态规划的阶段,枚举状态

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

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 100000000
int mp[20];
bool sta[(1<<16)];//用来预处理出所有的有效行 
int dp[20][(1<<16)];
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			int t;
			scanf("%d",&t);
			mp[i]=(mp[i]<<1)+t;
		}
	}
	
	for(int i=0;i<(1<<m);i++){
		if(!(i&(i<<1)))sta[i]=true;
	}
	
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<(1<<m);j++){
			if(sta[j]&&((j&mp[i])==j)){//枚举一个合法的行状态 且有效行状态为第i行实际状态的子集 
				for(int k=0;k<(1<<m);k++){
					if((k&j)==0){//枚举上一行的状态 若无相邻 则进行转移 
						dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
					}
				}
			}
		}
	}
	
	ll ans=0;
	for(int i=0;i<(1<<m);i++){
		if(sta[i]){
			ans+=dp[n][i];
			ans%=mod;
		}
	}
	
	printf("%lld\n",ans);
	return 0;
}

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

状压dp数组记得用long long虽然n只有9但累加起来值很大。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
bool sta[1<<9];
ll dp[10][1<<9][85];

int main(){
	int n,k;
	scanf("%d%d",&n,&k);
	
	for(int i=0;i<(1<<9);i++){
		if(!(i&(i<<1)))sta[i]=true;
	}

	dp[0][0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<(1<<n);j++){
			if(sta[j]){
				for(int l=0;l<(1<<n);l++){
					if(((j&l)==0)&&(((j<<1)&l)==0)&&((j&(l<<1))==0)){
						bitset<(10)> bit(j);
						int cnt=bit.count();
						
						for(int m=cnt;m<=k;m++){
							dp[i][j][m]=dp[i][j][m]+dp[i-1][l][m-cnt];
						}
					}
				}
			}
		}
	}
	
	ll ans=0;
	for(int i=0;i<(1<<n);i++){
        if(sta[i])
		ans+=(long long)dp[n][i][k];
	}

	printf("%lld\n",ans);
	return 0;
} 

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

因为涉及到了前两行,所以要加维才能正确表示本行与上一行的状态

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

bool sta[(1<<10)];
char tmp[105][15];
int mp[105][15];
ll dp[2][(1<<10)][(1<<10)];
int state[105];

ll max(ll a,ll b){
	if(a>b)return a;
	return b;
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	
	for(int i=0;i<(1<<10);i++){
		if(((i&(i<<1))==0)&&((i&(i<<2))==0)){
			sta[i]=true;
		}
	}
	
	for(int i=1;i<=n;i++){
		scanf("%s",tmp[i]);
		for(int j=0;j<m;j++){
			if(tmp[i][j]=='P'){
				mp[i][j+1]=1;
			}
			else mp[i][j+1]=0;
		}
	}
	
	for(int i=1;i<=n;i++){
		int cur=0;
		for(int j=1;j<=m;j++){
			cur=(cur<<1)+mp[i][j];
		}
		state[i]=cur;
		
		for(int j=0;j<(1<<m);j++){
			if(!sta[j])continue;
			
		//	if(!((cur&j)==j))continue;//如果枚举的当前行占用山地 
			if(((cur|j)!=cur))continue;
			
			bitset<10> bit1(j);
			int cnt1=bit1.count();
			
			for(int k=0;k<(1<<m);k++){
				if(!sta[k])continue;
				if(j&k)continue; 

				//if(!((sta[i-1]&k)==k))continue;
				if((k|state[i-1])!=state[i-1])continue;//k为state[i-1]的子集 用上面的语句会错 但是自认为两种是等价的 不知道是什么原因 
				
				bitset<10> bit2(k);
				int cnt2=bit2.count();
			
				if(i==1){
					dp[i%2][j][k]=cnt1;
					continue;
				}
				else if(i==2){
					dp[i%2][j][k]=max(dp[i%2][j][k],cnt1+cnt2);
					continue;
				}
				
				for(int l=0;l<(1<<m);l++){
					if(!sta[l])continue;
					if((j&l)||(l&k))continue;
					
					//if((l&(~sta[i-2])))continue;
					if(! ((sta[i-2]&l) ==l) )continue;
					if((l|state[i-2])!=state[i-2])continue;
					
					dp[i%2][j][k]=max(dp[i%2][j][k],dp[(i-1)%2][k][l]+cnt1);
				}
			}
		}
	}
	
	ll ans=0;
	for(int i=0;i<(1<<m);i++){
		for(int j=0;j<(1<<m);j++){
			ans=max(ans,dp[n%2][i][j]);
		}
	}
	printf("%lld\n",ans);
	
	return 0;
}

洛谷https://www.luogu.org/problem/P2915

一维状压dp,一般枚举所有可能的state,每个state作为一个阶段,通过点的关系进行转移

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

ll dp[20][(1<<18)];
int num[20];

int main(){
	int n,t;
	scanf("%d%d",&n,&t);
	for(int i=1;i<=n;i++){
		scanf("%d",num+i);
		dp[i][(1<<i-1)]=1;
	}
	
	for(int j=0;j<(1<<n);j++){//每个状态都是一个阶段 
		for(int i=1;i<=n;i++){//枚举尾部的编号   
			
			if(!((1<<i-1)&j))continue; //如果枚举的状态中不含枚举的尾部编号 直接跳过
			for(int k=1;k<=n;k++){
				//if(((1<<k-1)&j))continue;//如果枚举的状态中含有枚举的后一位的编号 直接跳过
				if(((1<<k-1)|j)==j)continue;
				
				if(abs(num[i]-num[k])>t){
					dp[k][(j|(1<<k-1))]+=dp[i][j];
				}
			}
		}
	}
	
	ll ans=0;
	for(int i=1;i<=n;i++){
		ans+=dp[i][(1<<n)-1];
	}
	printf("%lld\n",ans);
	
	return 0;
}

Vijoshttps://vijos.org/p/1456

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

int mp[17][17];
ll dp[1<<16][17];

int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			int t;
			scanf("%d",&mp[i][j]);
		}
	}
	
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=n;i++){
		dp[1<<i-1][i]=0;
	}
	
	for(int i=0;i<(1<<n);i++){
		for(int j=1;j<=n;j++){
			if(!(i&(1<<j-1)))continue;//前一个人 
			for(int k=1;k<=n;k++){
				if((i&(1<<k-1)))continue;//当前即将要被传的那个人 
				
				dp[i|(1<<k-1)][k]=min(dp[i|(1<<k-1)][k],dp[i][j]+mp[j][k]);
			}
		}
	}
	
	ll ans=0x3f3f3f3f;
	for(int i=1;i<=n;i++){
		ans=min(ans,dp[(1<<n)-1][i]);
	}
	printf("%lld\n",ans);
	
	return 0;
}

洛谷https://www.luogu.org/problem/P3052

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

ll num[20];
ll les[(1<<18)],dp[(1<<18)];

int main(){
	int n,w;
	scanf("%d%d",&n,&w);
	for(int i=1;i<=n;i++){
		scanf("%lld",&num[i]);
	}
	
	memset(dp,0x3f,sizeof(dp));
	dp[0]=1;
	les[0]=w;
	for(int i=0;i<(1<<n);i++){
		for(int j=1;j<=n;j++){
			if((1<<j-1)&i)continue;
			
			if(les[i]>=num[j]){//如果能装下的话 
				les[(i|(1<<j-1))]=max(les[(i|(1<<j-1))],les[i]-num[j]);
				dp[(i|(1<<j-1))]=min(dp[(i|(1<<j-1))],dp[i]);
			}
			else {
				les[(i|(1<<j-1))]=max(les[(i|(1<<j-1))],w-num[j]);
				dp[(i|(1<<j-1))]=min(dp[(i|(1<<j-1))],dp[i]+1);
			}
		}
	}
	
	printf("%lld\n",dp[(1<<n)-1]);
	return 0;
}

洛谷https://www.luogu.org/problem/P3092

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

ll c[100005],p[20],sum[100005];
ll dp[1<<16];
ll pre[1<<16];
ll S;
int main(){
	int k,n;
	scanf("%d%d",&k,&n);
	for(int i=1;i<=k;i++){
		scanf("%lld",&p[i]);
		S+=p[i];
	}
	for(int i=1;i<=n;i++){
		scanf("%lld",&c[i]);
		sum[i]=sum[i-1]+c[i];//因为花费都大于1 所以c一定是递增的  
	}
	
	for(int i=0;i<(1<<k);i++){
		for(int j=1;j<=k;j++){
			if(((1<<j-1)&i))continue;
			
			int last=dp[i];
			ll pos=upper_bound(sum+1,sum+n+1,sum[last]+p[j])-sum-1;
			dp[(i|(1<<j-1))]=max(dp[(i|(1<<j-1))],pos);
		}
	}
	
	for(int i=0;i<(1<<k);i++){
		for(int j=0;i>>j;j++){
			if((i>>j)&1)pre[i]+=p[j+1];
		}
	}
	
	ll ans=999999999999;
	for(int i=0;i<(1<<k);i++){
		if(dp[i]>=n){
			ans=min(ans,pre[i]);
		}
	}
	if(ans==999999999999){
		printf("-1\n");
	}
	else {
		printf("%lld\n",S-ans);
	}
	
	
	return 0;
}

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

每格方块只有三种情况:

1.上半方块,上方一定是0,本身一定是1

2.下半方块,上方一定是1,本身一定是0

3.左右方块,上方可能是0或1,本身一定是0

两个上半方块不能相接,也就是j&k一定为0才能转移

将本行的上半与下半方块都消去后,剩下的都是一块横着的方块,横着的方块一定是偶数个,也就是j|k的结果连续的0一定是偶数才能转移

#include<iostream>
#include<cstring>

using namespace std;
#define ll long long

ll dp[12][(1<<11)];
bool isEven[(1<<11)];

int main(){
	int n,m;
	while(scanf("%d%d",&n,&m),n&&m){
		memset(isEven,0,sizeof(isEven));
		for(int i=0;i<(1<<m);i++){//预处理所有连续0为偶数的情况 
			int is=0,flag=0;
			for(int j=0;j<m;j++){
				if(!(i&(1<<j))){//此位为0 
					is^=1;
				}
				else {
					//is为1表示奇数 为0表示偶数
					flag|=is;is=0;
				}
			}
			flag|=is;//最后一次别忘了 
			if(!flag)isEven[i]=true;
		}
		
		dp[0][0]=1;
		for(int i=1;i<=n;i++){
			for(int j=0;j<(1<<m);j++){//当前行 
				dp[i][j]=0;
				for(int k=0;k<(1<<m);k++){//上一行 
					if(j&k)continue;//不能上方块与上方块相接
					 
					if(!isEven[j|k])continue;
					dp[i][j]+=dp[i-1][k];
				}
			}
		}

		printf("%lld\n",dp[n][0]);
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值