状压 DP 总结

指挥使珂爱qwq CSG 咕咕咕

状压dp总结

在很多情况下,我们需要记录的状态很复杂。

我们就把这些dp统一称为状压dp

这类问题的一个经典应用是在棋盘上。

我们往往记录一整行信息,或者记录之前的一些网格/线/列/斜线的信息。

在一些其他的问题中,我们可能需要记录每一项的选择状态

我们通常用 \(0\) 表示一个物品没被选中,\(1\) 表被选中。

剩下的物体最优解,只与前面少量的信息有关。

在更复杂的情况下,一个项目可以有更复杂的状态。

方法

一般用一个二进制数表示压缩后的状态。

然后即可暴力跑状态dp。

因此状压dp复杂度一般是指数级的。

所以 \(n\) 一般\(10\)\(18\) 之间。

愤怒的小鸟

题目传送门

简述题意

  • \(n\) 个猪,你可以从原点引出抛物线,问最少用多少次可以打到所有猪。

简述做法

  • 预处理所有有效抛物线。

  • 以猪有没有被打下来作为状态进行状压DP。

具体阐述

我们对每个猪进行一次遍历,每次对序号在他后面的猪进行遍历,对于之前没有的抛物线我们新保存一次。

对于抛物线 \(y=ax^2+bx+c\)。已知经过原点,所以 \(c=0\)

我们又另知抛物线经过两点 \((x_i,y_i)\) \((x_j,y_j)\)

可算出 \(a=\dfrac{x_jy_i-x_iy_j}{x_ix_j(x_i-x_j)}\)\(b=\dfrac{x_ix_iy_j-x_jx_jy_i}{x_ix_j(x_i-x_j)}\)

		for(ll i=0;i<n;i++){
			line[cnt++]=(1<<i);
			for(ll j=i+1,use=0;j<n;j++)
				if((use>>j)&1) continue;
				else{
					ld a=(x[j]*y[i]-x[i]*y[j])/(x[i]*x[j]*(x[i]-x[j])),b=(x[i]*x[i]*y[j]-x[j]*x[j]*y[i])/(x[i]*x[j]*(x[i]-x[j]));
					if(a>=0) continue;
					line[cnt]=(1<<i);
					for(ll k=j;k<n;k++) 
						if(ab(a*x[k]*x[k]+b*x[k]-y[k])<=eps) use|=(1<<k),line[cnt]|=(1<<k);
					cnt++;
				}
		}

因为 \(n \le 18\),所以很容易想到状压DP。

我们定义 \(f_s\) 为打到 \(s\) 的状态时最少的次数。

对于每个抛物线,可易得转移方程 \(f_{i|line_j}=min{f_{i|line_j},f_i+1}\)

不要忘记 \(f_0=1\)

		for(ll i=0;i<(1<<n);i++)
			for(ll j=0;j<cnt;j++)
				f[i|line[j]]=min(f[i|line[j]],f[i]+1);

本题运用基本数学知识和基本状压DP,偏模板型。

/*
***
还要继续努力
成为一名烤咕学家哦
***
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
const ll N=20;
ll n,T,line[200],f[1<<N],cnt,op;
ld x[N],y[N],eps=1e-6;
template <typename T> inline void rd(T &x){
	ll fl=1;x=0;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') fl=-fl;
	for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
	x*=fl;
}
void wr(ll x){
	if(x<0) x=-x,putchar('-');
	if(x<10) putchar(x+'0');
	if(x>9) wr(x/10),putchar(x%10+'0');
}
inline ld ab(ld x){
	return x>=0.00?x:-x;
}
int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	rd(T);
	while(T--){
		memset(f,0x3f3f3f3f,sizeof(f));cnt=0;
		rd(n);rd(op);
		for(ll i=0;i<n;i++) scanf("%llf %llf",&x[i],&y[i]);
		for(ll i=0;i<n;i++){
			line[cnt++]=(1<<i);
			for(ll j=i+1,use=0;j<n;j++)
				if((use>>j)&1) continue;
				else{
					ld a=(x[j]*y[i]-x[i]*y[j])/(x[i]*x[j]*(x[i]-x[j])),b=(x[i]*x[i]*y[j]-x[j]*x[j]*y[i])/(x[i]*x[j]*(x[i]-x[j]));
					if(a>=0) continue;
					line[cnt]=(1<<i);
					for(ll k=j;k<n;k++) 
						if(ab(a*x[k]*x[k]+b*x[k]-y[k])<=eps) use|=(1<<k),line[cnt]|=(1<<k);
					cnt++;
				}
		}
		f[0]=0;
		for(ll i=0;i<(1<<n);i++)
			for(ll j=0;j<cnt;j++)
				f[i|line[j]]=min(f[i|line[j]],f[i]+1);
		wr(f[(1<<n)-1]);puts(""); 
	}
	return 0;
}

时间复杂度 \(O(2^n \times n^2)\)

管道连接

题目传送门

题意

  • 给出一张 \(n\) 个点,\(m\) 条边带有 \(p\) 的特殊点的图,每个特殊点有一个颜色。

  • 要求选出若干条边,使得颜色相同的在同一个连通块内。

  • 输出最小边权和。


先简单介绍一个问题类型。

最小斯坦纳树:在一张给定的带权无向图中,将其中 \(k\) 个点变成连通块最少需要花费的代价。

因为最后连起来会是一棵树,所以就叫最小斯坦纳树。

\(f_{i,S}\) 表示当前 \(i\) 在的连通块中,点集状态为 \(S\) 需要花费的最小费用。

转移方法

在一个点上汇合

我们需要合并不同的若干子集。

\(f_{i,S1|S2}=min(f_{i,S1|S2})\)

往一个方向走

需要一个SPFA实现。

\(f_{j,S|w_j}=min(f_{i,S}+w_{i,j})\)

回归本题。

最优解->斯坦纳森林。

不妨先用上述方法求出每个子集汇合需要的代价。

最后的解是若干个子集的并。

每个子集都是若干种完整的颜色

利用一个子集dp完成。

/*
***
还要继续努力
成为一名烤咕学家哦
***
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=11,M=1005;
ll m,n,p,f[M][1<<N],g[1<<N],w[1<<N],k1,hd[M],ID[M],num[N],Q[M*M],vis[M];
struct Node{ll t,nxt,val;}s[M*10];
template <typename T> void rd(T &x){
	ll fl=1;x=0;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') fl=-fl;
	for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
	x*=fl;
}
void wr(ll x){
	if(x<0) x=-x,putchar('-');
	if(x<10) putchar(x+'0');
	if(x>9) wr(x/10),putchar(x%10+'0');
}
void add(ll x,ll y,ll w){s[++k1].t=y; s[k1].nxt=hd[x]; s[k1].val=w; hd[x]=k1;}
void spfa(ll ID){
	Q[0]=0;
	for(ll i=1;i<=n;++i) if(f[i][ID]<(ll)1e9) Q[++Q[0]]=i,vis[i]=1;
	for(ll l=1;l<=Q[0];++l){
		ll p=Q[l];
		for(ll i=hd[p];i;i=s[i].nxt){
			ll k=s[i].t;
			if(f[k][ID]>f[p][ID]+s[i].val){
				f[k][ID]=f[p][ID]+s[i].val;
				if(!vis[k]){
					vis[k]=1;
					Q[++Q[0]]=k;
				}
			}
		}
		vis[p]=0;
	}
}
int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	rd(n);rd(m);rd(p);
	for(ll i=1,u,v,val;i<=m;i++){
		rd(u);rd(v);rd(val);
		add(u,v,val);add(v,u,val); 
	}
	memset(ID,-1,sizeof(ID));
	for(ll i=1,x,y;i<=p;++i){
		rd(x);rd(y);
		ID[y]=i-1;num[x]|=(1<<(i-1));
	}
	memset(f,60,sizeof(f));
	for(ll i=1;i<=n;++i)
		if(ID[i]==-1) f[i][0]=0;
		else f[i][1<<ID[i]]=0;
	for(ll i=0;i<(1<<p);++i){
		if(i)
			for(ll k=1;k<=n;++k)
				for(ll j=(i-1)&i;j;j=(j-1)&i)
					f[k][i]=min(f[k][i],f[k][j]+f[k][i^j]);
		spfa(i);
	}
	memset(w,60,sizeof(w));
	for(ll i=0;i<(1<<p);++i)
		for(ll j=1;j<=n;++j) w[i]=min(w[i],f[j][i]);
	memset(g,60,sizeof(g));
	g[0]=0;
	for(ll i=1;i<(1<<p);++i)
		for(ll j=i;j;j=(j-1)&i){
			ll Num=0;
			for(ll k=0;k<p;++k) if(j&(1<<k)) Num|=num[k];
			g[i]=min(g[i],g[i^j]+w[Num]);
		}
	wr(g[(1<<p)-1]);puts("");
	return 0;
}

吃货 JYY

题目传送门

注:这题没写代码,且偏口胡,应该思路什么都没锅

代码不咕了 它有了/cy/qiang

题意

  • 给一张有权无向图,标记其中某些边。

  • \(1\) 号点出发要求我们找一条回路,且回路经过所有标记边。

  • 最小化边权和。


我们找到一些边,使得构成欧拉回路

那么就产生两个条件。

  1. 所有点度数都是偶数

  2. 选出来的边构成的图,必须只有一个联通分量

Sol \(1\)

\(f_{i,S}\) 表示当前每个点的状态,以及当前最后停留在 \(i\) 的位置。

\(S\) 状态每个点分成三种情况。

\(0\):不与 \(1\) 连通

\(1\):和 \(1\) 连通且度数为奇数

\(2\):和 \(1\) 连通且度数为偶数

然而这样无法处理强制选边

因此我们继续分析题目性质。

Sol \(2\)

观察到所有边可以分成两部分。

一部分是到过的到没到过的点,另一部分是连接两个到过的点。

对于两个到过的点,连边即可改变他们的奇偶性。

且若走最短路径,一定会得到最好的效果。

我们猜想,可dp出所有点连通性的状态,然后我们再用最短路把奇数点之间两两连上。

\(f_S\) 表示每个点与1号点连通状态确定时最小代价。

每次我们枚举边 \((i,j)\) 其中 \(i\)\(S\) 中不为0,\(j\) 为0。

第一次加进一个点的时候,附加上它使用必须边的连通性,即可把必须取边条件放上。

\(w_i\) 表示只用 \(k\) 条边,\(i\) 和哪些点连通,且这些点奇偶状态如何。

我们在第一次加入连通块中的一个点时,我们就把所有点拖进去。

再令 \(g_S\) 表示当 \(S\) 中点为奇数时,变成偶数的最优解。

每次枚举点转移即可qwq。

时间复杂度 \(O(3^n \times m)\),可能需要一些常数优化(¿)。

子集枚举

子集枚举的复杂度进行一点分析。

这里的子集枚举为,在一个二进制数中,枚举所有为1的子集。

我们有一种通用写法。

这个写法看似很暴力实际上也很暴力,复杂度 \(O(3^n)\)

实际上就是 \(\sum^n_{i=0}C(n,i) \times 2^i\) 展开后同 \((1+2)^n\)

即为 \(O(3^n)\)

/*
***
还要继续努力
成为一名烤咕学家哦
***
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=15,K=80,INF=0x3f3f3f3f;
ll n,m,sum,ans=INF;
ll hd[N],pw[N]={1},g[1<<N],dis[N][N],f[1600000],de[N],a[N];
struct edge{
	ll t,nxt,l;
}es[K<<2];
queue<ll> q;
template <typename T> void rd(T &x){
	ll fl=1;x=0;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') fl=-fl;
	for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
	x*=fl;
}
void wr(ll x){
	if(x<0) x=-x,putchar('-');
	if(x<10) putchar(x+'0');
	if(x>9) wr(x/10),putchar(x%10+'0');
}
void add(ll u,ll v,ll w){es[++sum]=(edge){v,hd[u],w};hd[u]=sum;}
int main(){
	rd(n);rd(m);
	memset(dis,INF,sizeof(dis));memset(g,INF,sizeof(g));memset(f,INF,sizeof(f));
	for(ll i=1;i<=n;i++) dis[i][i]=0;
	for(ll i=1;i<=n;i++) pw[i]=pw[i-1]*3;
	for(ll i=0,u,v,w;i<m;i++){
		rd(u);rd(v);rd(w);
		dis[u][v]=dis[v][u]=min(dis[u][v],w);
		de[u]++;de[v]++;
		add(u,v,w);add(v,u,w);
	}
	rd(m);
	for(ll i=0,u,v,w;i<m;i++){rd(u);rd(v);rd(w);dis[u][v]=dis[v][u]=min(dis[u][v],w);}
	for(ll i=1;i<=n;i++)
		for(ll j=1;j<=n;j++)
			for(ll k=1;k<=n;k++) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	g[0]=0;
	for(ll i=0;i<(1<<n);i++)
		for(ll j=1;j<=n;j++)
			if(!(i&(1<<(j-1))))
				for(ll k=j+1;k<=n;k++)
					if(!(i&(1<<(k-1)))) g[i^(1<<(j-1))^(1<<(k-1))]=min(g[i^(1<<(j-1))^(1<<(k-1))],g[i]+dis[j][k]);
	f[2]=0;q.push(2);
	while(!q.empty()){
		ll qwq=q.front(),cnt=0;q.pop();
		for(ll i=1;i<=n;i++) if(qwq/pw[i-1]%3>0) a[++cnt]=i;
		for(ll i=1;i<=n;i++)
			if(qwq/pw[i-1]%3==0){
				for(ll j=hd[i];j;j=es[j].nxt)
					if(qwq/pw[es[j].t-1]%3>0){
						ll s=qwq+pw[i-1]*2;
						if(f[qwq]>=f[s]) continue;
						if(f[s]>=INF) q.push(s);
						f[s]=f[qwq];
					}
				for(ll j=1;j<=cnt;j++){
					ll s=qwq+pw[i-1];
					s+=(qwq/pw[a[j]-1]%3==1)?pw[a[j]-1]:-pw[a[j]-1];
					if(f[qwq]+dis[i][a[j]]>=f[s]) continue;
					if(f[s]>=INF) q.push(s);
					f[s]=f[qwq]+dis[i][a[j]];
				}
			}
	}
	for(ll k=0;k<pw[n];k++){
		ll fl=0,nw=k,s=0;
		for(ll i=1;i<=n;i++) if(de[i]&&k/pw[i-1]%3==0){fl=1;break;}
		if(fl) continue;
		for(ll i=1;i<=n;i++) if(de[i]&1) nw+=(k/pw[i-1]%3==1)?pw[i-1]:-pw[i-1];
		for(ll i=1;i<=n;i++) if(nw/pw[i-1]%3==1) s^=1<<(i-1);
		ans=min(ans,f[k]+g[s]);
	}
	for(ll i=1;i<=sum;i+=2) ans+=es[i].l; 
	wr(ans);puts("");
	return 0;
}
posted @ 2020-10-18 10:32  Demoe  阅读(218)  评论(0编辑  收藏  举报