斯坦纳树

斯坦纳树

就是一个很暴力的东西。考虑要做最小生成树,其中一些点必须选,一些点可选可不选。必选点比较少,可以用状压维护。

按照状压状态从小到大更新,每次先枚举子集更新自己,再跑最短路更新全局。

复杂度\(O(n 3^n)\),感觉特弱智。

WC2008 游览计划

img

对于100%的数据,N,M,K≤10,其中K为景点的数目。输入的所有整数均在[0,216]的范围内

题解

此题就是斯坦纳树板子题,没什么好说的。重点在于理解更新顺序。

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
	T x=0,w=1;char c=getchar();
	for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
	return x*w;
}
template<class T> il T read(T&x){
	return x=read<T>();
}
using namespace std;
typedef long long LL;

co int N=11,S=1025,INF=0x3f3f3f3f;
int n,m,tot;
int a[N][N],f[N][N][S];
co int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
int vis[N][N];
struct node{int x,y,s;}pre[N][N][S];

queue<pair<int,int> > q;
void spfa(int cur){
	while(q.size()){
		int x=q.front().first,y=q.front().second;
		q.pop(),vis[x][y]=0;
		for(int i=0;i<4;++i){
			int nx=x+dx[i],ny=y+dy[i]; // edit 1:dy
			if(nx<1||nx>n||ny<1||ny>m) continue;
			if(f[nx][ny][cur]>f[x][y][cur]+a[nx][ny]){
				f[nx][ny][cur]=f[x][y][cur]+a[nx][ny];
				pre[nx][ny][cur]=(node){x,y,cur};
				if(!vis[nx][ny]) q.push(make_pair(nx,ny)),vis[nx][ny]=1;
			}
		}
	}
}
void dfs(int x,int y,int now){
	vis[x][y]=1;
	node t=pre[x][y][now];
	if(!t.x&&!t.y) return;
	dfs(t.x,t.y,t.s);
	if(t.x==x&&t.y==y) dfs(t.x,t.y,now-t.s);
}
int main(){
	read(n),read(m);
	memset(f,0x3f,sizeof f);
	for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)
		if(!read(a[i][j])) f[i][j][1<<tot]=0,++tot;
	int lim=(1<<tot)-1;
	for(int sta=0;sta<=lim;++sta){
		for(int i=1;i<=n;++i)for(int j=1;j<=m;++j){
			for(int s=sta;s;s=(s-1)&sta)
				if(f[i][j][s]+f[i][j][sta-s]-a[i][j]<f[i][j][sta]){
					f[i][j][sta]=f[i][j][s]+f[i][j][sta-s]-a[i][j];
					pre[i][j][sta]=(node){i,j,s};
				}
			if(f[i][j][sta]<INF) q.push(make_pair(i,j)),vis[i][j]=1;
		}
		spfa(sta);
	}
	int ansx,ansy;
	for(int i=1,flag=0;i<=n&&!flag;++i)
		for(int j=1;j<=m;++j)if(!a[i][j]){
			ansx=i,ansy=j,flag=1;break;
		}
	printf("%d\n",f[ansx][ansy][lim]);
	memset(vis,0,sizeof vis);
	dfs(ansx,ansy,lim);
	for(int i=1;i<=n;++i,puts(""))
		for(int j=1;j<=m;++j){
			if(!a[i][j]) putchar('x');
			else vis[i][j]?putchar('o'):putchar('_');;
		}
	return 0;
}

BZOJ4774 修路

村子间的小路年久失修,为了保障村子之间的往来,法珞决定带领大家修路。

对于边带权的无向图 G = (V, E),请选择一些边,使得1 <= i <= d, i号节点和 n - i + 1 号节点可以通过选中的边连通,最小化选中的所有边的权值和。

1 <= d <= 4,2d <= n <= 104,0 <= m <= 104

题解

这题的特殊点不需要两两连通,所以求的是最小森林。

还是考虑状压,\(f(S,i)\)表示状态为\(S\)的点集连成的生成树的根为\(i\)的最小代价,用斯坦纳树做法转移即可。

每次用\(\min_i f(S,i)\)的值更新\(g(S)\),最后把合法状态的\(g\)进行枚举子集转移即可。

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
	T x=0,w=1;char c=getchar();
	for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
	return x*w;
}
template<class T> il T read(T&x){
	return x=read<T>();
}
using namespace std;
typedef long long LL;

co int N=10000+1,S=1<<8,INF=0x3f3f3f3f;
int n,m,D;
vector<int> to[N],we[N];
int f[S][N],g[S];
bool vis[N];
queue<int> q;
void spfa(int f[]){
	while(q.size()){
		int x=q.front();
		q.pop(),vis[x]=0;
		for(int i=0;i<(int)to[x].size();++i){
			int y=to[x][i],w=we[x][i];
			if(f[y]>f[x]+w){
				f[y]=f[x]+w;
				if(!vis[y]) q.push(y),vis[y]=1;
			}
		}
	}
}
il bool check(int s){
	return (s&((1<<D)-1))==(s>>D);
}
int main(){
	read(n),read(m),read(D);
	while(m--){
		int x=read<int>(),y=read<int>(),w=read<int>();
		to[x].push_back(y),we[x].push_back(w);
		to[y].push_back(x),we[y].push_back(w);
	}
	memset(f,0x3f,sizeof f),memset(g,0x3f,sizeof g);
	for(int i=1;i<=D;++i)
		f[1<<(i-1)][i]=f[1<<(D+i-1)][n-i+1]=0;
	int lim=(1<<(D<<1))-1;
	for(int i=0;i<=lim;++i){
		for(int j=1;j<=n;++j){
			for(int k=i&(i-1);k;k=(k-1)&i)
				f[i][j]=min(f[i][j],f[k][j]+f[i^k][j]);
			if(f[i][j]<INF) q.push(j),vis[j]=1;
		}
		spfa(f[i]);
		for(int j=1;j<=n;++j) g[i]=min(g[i],f[i][j]);
	}
	for(int i=0;i<=lim;++i)
		for(int t=(i-1)&i;t;t=(t-1)&i)
			if(check(t)&&check(i^t)) g[i]=min(g[i],g[t]+g[i^t]);
	printf("%d\n",g[lim]<INF?g[lim]:-1);
	return 0;
}

posted on 2019-08-20 21:33  autoint  阅读(308)  评论(2编辑  收藏  举报

导航