状压dp

状态压缩

即将十进制,字符串,数组等转化为二进制,应用于搜索,动态规划。

P1225 黑白棋游戏

#include<bits/stdc++.h>
using namespace std;
void write(int k){
	if(k/10==0){
		putchar((char)(k+'0'));
		return;
	}
	write(k/10);
	putchar((char)(k%10+'0'));
	return;
}
const int N=1e5+10;
int S,T,last[N];
bool visited[N];
queue<int>q;
void scan(int &k){
	for(int i=1;i<=16;i++){
		char ch;cin>>ch;
		k=(k<<1)+ch-'0';
	}
	return;
}
void solve(){
	q.push(S);
	visited[S]=1;last[S]=-1;
	while(!q.empty()){
		int k=q.front(),z;q.pop();
		for(int i=2;i<=16;i++){
			if(i%4!=1&&((k>>(i-1))&1)!=((k>>(i-2))&1)){
				z=k^(3<<(i-2));
				if(visited[z]) continue;
				visited[z]=1;
				last[z]=k;
				if(z==T) return;
				q.push(z);
			}
			if(i>=5&&((k>>(i-1))&1)!=((k>>(i-5))&1)){
				z=k^(1<<(i-1))^(1<<(i-5));
				if(visited[z]) continue;
				visited[z]=1;
				last[z]=k;
				if(z==T) return;
				q.push(z);
			}
		}
	}
	return;
}
void print(int k,int cnt){
	if(k==S){
		write(cnt);
		putchar('\n');
		return;
	}
	print(last[k],cnt+1);
	int z=last[k]^k,num[2];
	bool f=0;
	for(int i=0;i<16;i++)
		if(z&(1<<i))
			num[f]=i+1,f^=1;
	num[0]=16-num[0]+1;num[1]=16-num[1]+1;
	write((num[0]-1)/4+1);
	write((num[0]-1)%4+1);
	write((num[1]-1)/4+1);
	write((num[1]-1)%4+1);
	putchar('\n');
	return;
}
signed main(){
	scan(S);scan(T);
	if(S==T){
		cout<<0<<'\n';
		return 0;
	}
	solve();
	print(T,0);
	return 0;
}

一些基本操作

· 取s的第k位:(s>>(k-1))&1
. 将s的第k位取反:s^=1<<(k-1)
. 枚举s的子集:for(int t=s;t;t=s&(t-1)){...}
· lowbit技术:int lowbit(int k){return k&-k}

P5911 [POI 2004] PRZ

#include<bits/stdc++.h>
using namespace std;
const int INF=1e5+10;
int W,n,t[20],w[20],dp[INF];
signed main(){
	ios::sync_with_stdio(0);
	cin>>W>>n;
	for(int i=1;i<=n;i++)
		cin>>t[i]>>w[i];
	for(int i=1;i<=(1<<n)-1;i++){
		int sum=0;
		for(int j=1;j<=n;j++)
			if((i>>(j-1))&1)
				sum+=w[j],dp[i]=max(dp[i],t[j]);
		if(sum>W) dp[i]=INF;
		for(int s=i&(i-1);s;s=i&(s-1))
			dp[i]=min(dp[i],dp[s]+dp[i-s]);
	}
	cout<<dp[(1<<n)-1]<<'\n';
	return 0;
}

时间复杂度为O(n^3),浅浅证明一下。

对于每个包含k个1的s枚举子集时,枚举次数为:2^k
而含k个1的集合的个数为:C(n,k),运行次数为:2^k*C(n,k)
故此代码的总运行次数为:

2^0 *C(n,0) +2^1 *C(n,1) +2^2 *C(n,2) +...+2^(n-1) *C(n,n-1) +2^n C(n,n)
=C(n,0)
2^0 1^n+ C(n,1)2^1 1^n+ C(n,2)2^2 1^n +... +C(n,n-1)2^(n-1) 1^n+ C(n,n)2^n *1^n
=(1+2)^n
=3^n
得证。

P2831 [NOIP 2016 提高组] 愤怒的小鸟

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-6;
int T,n,m,dp[(1<<18)+10],min0[(1<<18)+10],line[20][20];
double x[20],y[20];
void Get_ab(double &a,double &b,int _1,int _2){
	if(x[_1]==x[_2]) return;
	a=(y[_1]*x[_2]-y[_2]*x[_1])/(x[_1]*x[_1]*x[_2]-x[_2]*x[_2]*x[_1]);
	b=(y[_1]*x[_2]*x[_2]-y[_2]*x[_1]*x[_1])/(x[_1]*x[_2]*x[_2]-x[_1]*x[_1]*x[_2]);
	return;
}
bool check(double a,double b,int k){
	return abs(a*x[k]*x[k]+b*x[k]-y[k])<eps;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	for(int i=0;i<(1<<18)+10;i++){
		int k=1;
		while(i&(1<<(k-1))) k++;
		min0[i]=k;
	}
	cin>>T;
	while(T--){
		cin>>n>>m;
		for(int i=1;i<=n;i++)
			cin>>x[i]>>y[i];
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++){
				if(i==j){
					line[i][j]=1<<(i-1);
					continue;
				}
				line[i][j]=0;
				double a=0,b;
				Get_ab(a,b,i,j);
				if(a>-eps) continue;
				for(int k=1;k<=n;k++)
					if(check(a,b,k))
						line[i][j]|=1<<(k-1);
			}
		for(int s=1;s<=(1<<n)-1;s++)
			dp[s]=100;
		for(int s=0;s<=(1<<n)-1;s++)
			for(int i=min0[s];i<=n;i++)
				dp[s|line[min0[s]][i]]=min(dp[s|line[min0[s]][i]],dp[s]+1);
		cout<<dp[(1<<n)-1]<<'\n';
	}
	return 0;
}

时间复杂度O(T * n * 2^n)
主要在于dp的推导,细节在于一些实数误差。

posted @ 2025-08-18 19:22  yuhui666  阅读(7)  评论(0)    收藏  举报