斯坦纳树总结

这个东西很久以前就学过了,不过前几天看到后又一脸懵逼,于是赶紧滚来复习一下。

简介

斯坦纳树是将图的一个指定点集内的所有点连通的一棵树,常见的问题有最小斯坦纳树(Minimal Steiner Tree)(怎么也叫MST啊喂)。和最小生成树不同的是,斯坦纳树可以包含不在指定点集内的点。

求解方法

斯坦纳树的求解方法类似于状压DP,设\(f[S][i]\)表示以结点\(i\)为根,连通了点集\(S\)内的所有点的最小代价。

先在外层枚举连通状态\(S\)

然后转移分为两部分:

  1. 枚举\(S\)的子集进行转移:$$f[S][i]=\min_{T \subseteq S}{f[T][i]+f[S-T][i]}$$

  2. 使用SPFA松弛连通状态为\(S\)的的所有状态:$$f[S][i]=\min(f[S][i],f[S][j]+e[j][i])$$

例题:[BZOJ2595][WC2008]游览计划

分析

斯坦纳树输出方案,DP的时候顺便记个\(pre[S][i][j]\)就好了。

代码

#include <bits/stdc++.h>
#define rin(i,a,b) for(int i=(a);i<=(b);i++)
#define irin(i,a,b) for(int i=(a);i>=(b);i--)
#define trav(i,a) for(int i=head[(a)];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;

inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}

int n,m,tot,val[15][15],pos[15][2],f[1<<10][15][15],pre[1<<10][15][15][3];
int dx[5]={0,-1,1,0,0},dy[5]={0,0,0,-1,1};
bool book[15][15],ans[15][15];
std::queue<int> q;

void spfa(int s){
	while(!q.empty()){
		int x=q.front()/20,y=q.front()%20;q.pop();
		rin(i,1,4){
			int xx=x+dx[i],yy=y+dy[i];
			if(xx<1||xx>n||yy<1||yy>m) continue;
			if(f[s][xx][yy]>f[s][x][y]+val[xx][yy]){
				f[s][xx][yy]=f[s][x][y]+val[xx][yy];
				pre[s][xx][yy][0]=s;
				pre[s][xx][yy][1]=x;
				pre[s][xx][yy][2]=y;
				if(!book[xx][yy]){
					q.push(xx*20+yy);
					book[xx][yy]=1;
				}
			}
		}
		book[x][y]=0;
	}
}

void getsteiner(){
	memset(f,0x3f,sizeof f);
	rin(i,1,tot) f[1<<(i-1)][pos[i][0]][pos[i][1]]=0;
	rin(s,0,(1<<tot)-1){
		rin(i,1,n) rin(j,1,m){
			for(int ss=s;ss;ss=((ss-1)&s)){
				if(f[s][i][j]>f[ss][i][j]+f[s^ss][i][j]-val[i][j]){
					f[s][i][j]=f[ss][i][j]+f[s^ss][i][j]-val[i][j];
					pre[s][i][j][0]=ss;
					pre[s][i][j][1]=i;
					pre[s][i][j][2]=j;
				}
			}
			if(f[s][i][j]<1e9){
				q.push(i*20+j);
				book[i][j]=1;
			}
		}
		spfa(s);
	}
}

void getmap(int s,int x,int y){
	if(!s||!x) return;
	ans[x][y]=1;
	getmap(pre[s][x][y][0],pre[s][x][y][1],pre[s][x][y][2]);
	getmap(s^pre[s][x][y][0],pre[s][x][y][1],pre[s][x][y][2]);
}

int main(){
	n=read(),m=read();
	rin(i,1,n) rin(j,1,m){
		val[i][j]=read();
		if(!val[i][j]){
			tot++;
			pos[tot][0]=i;
			pos[tot][1]=j;
		}
	}
	getsteiner();
	printf("%d\n",f[(1<<tot)-1][pos[1][0]][pos[1][1]]);
	getmap((1<<tot)-1,pos[1][0],pos[1][1]);
	rin(i,1,n){
		rin(j,1,m){
			if(ans[i][j]){
				if(val[i][j]) putchar('o');
				else putchar('x');
			}
			else putchar('_');
		}
		putchar('\n');
	}
	return 0;
}

[THUSC2017]巧克力

分析

斯坦纳树和二分答案(中位数的套路)的话很显然,但是那个随机化是真有点想不到。

直接枚举是哪\(k\)种图案然后跑斯坦纳树的复杂度是\(O(T \log n \binom{n \times m}{k} (3^k+2^k \times SPFA))\),肯定会直接废掉(虽然好像也有不少分)。

考虑上网搜题解,题解告诉我们可以给每种颜色随机一个\(1 \sim k\)的新颜色然后再跑斯坦纳树,\(k=5\)时,这样单次的正确率是\(\frac{5!}{5^5}=0.0384\),重复做\(100\)次的错误率就是\(0.01992716266102454756995285049955\)。博主向来非酋,所以做了\(200\)次。

剩下的就是斯坦纳树模板题了,时间复杂度是\(O(T \log n \times 非酋常数 \times 100 \times (3^k+2^k \times SPFA))\)

代码

#include <bits/stdc++.h>
#define rin(i,a,b) for(register int i=(a);i<=(b);++i)
#define irin(i,a,b) for(register int i=(a);i>=(b);--i)
#define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;

inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

const int AREA=240;
int n,m,k,siz,cnt,c[AREA][AREA],a[AREA][AREA],b[AREA];
int w[AREA][AREA],g[AREA],f[32][AREA][AREA];
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
bool book[AREA][AREA],flag;
std::queue<int> q;

void spfa(int s){
	while(!q.empty()){
		int x=q.front()/1000,y=q.front()%1000;q.pop();
		rin(i,0,3){
			int xx=x+dx[i],yy=y+dy[i];
			if(xx<1||xx>n||yy<1||yy>m) continue;
			if(f[s][xx][yy]>f[s][x][y]+w[xx][yy]){
				f[s][xx][yy]=f[s][x][y]+w[xx][yy];
				if(!book[xx][yy]) q.push(xx*1000+yy),book[xx][yy]=true;
			}
		}
		book[x][y]=false;
	}
}

int getsteiner(){
	rin(s,0,(1<<k)-1) rin(i,1,n) rin(j,1,m) f[s][i][j]=1e9;
	rin(i,1,n) rin(j,1,m) if(c[i][j]>0) f[1<<(g[c[i][j]]-1)][i][j]=w[i][j];
	rin(s,1,(1<<k)-1){
		rin(i,1,n) rin(j,1,m){
			for(register int ss=s;ss;ss=((ss-1)&s))
				f[s][i][j]=std::min(f[s][i][j],f[ss][i][j]+f[s^ss][i][j]-w[i][j]);
			if(f[s][i][j]<1e9) q.push(i*1000+j),book[i][j]=true;
		}
		spfa(s);
	}
	int ret=1e9;
	rin(i,1,n) rin(j,1,m) ret=std::min(ret,f[(1<<k)-1][i][j]);
	return ret;
}

bool check(int mid){
	int temp=1e9;
	rin(i,1,200){
		rin(i,1,cnt) g[i]=rand()%k+1;
		rin(i,1,n) rin(j,1,m) w[i][j]=c[i][j]>0?(a[i][j]>mid?2001:1999):1e9;
		temp=std::min(temp,getsteiner());
	}
	if(temp>=1e9){flag=true;printf("-1 -1\n");return 0;}
	bool ret=((temp+n*m)/2000)*2000>=temp;
	if(mid==((1+siz)>>1)) printf("%d ",(temp+n*m)/2000);
	return ret;
}
// if return value is true, the answer will be equal to or less than mid
// else the answer will be greater than mid

int main(){
	srand((int)19260817);
	int T=read();
	while(T--){
		n=read(),m=read(),k=read();siz=0;flag=false;
		rin(i,1,n) rin(j,1,m) (c[i][j]=read())>0?(b[++siz]=c[i][j]):0;
		rin(i,1,n) rin(j,1,m) a[i][j]=read();
		std::sort(b+1,b+siz+1);siz=std::unique(b+1,b+siz+1)-b-1;
		rin(i,1,n) rin(j,1,m) if(c[i][j]>0) c[i][j]=std::lower_bound(b+1,b+siz+1,c[i][j])-b;
		cnt=siz,siz=0;
		rin(i,1,n) rin(j,1,m) b[++siz]=a[i][j];
		std::sort(b+1,b+siz+1);siz=std::unique(b+1,b+siz+1)-b-1;
		rin(i,1,n) rin(j,1,m) a[i][j]=std::lower_bound(b+1,b+siz+1,a[i][j])-b;
		int l=1,r=siz,ans;
		while(l<=r){
			int mid=((l+r)>>1);
			if(check(mid)) ans=mid,r=mid-1;
			else l=mid+1;
			if(flag) break;
		}
		if(!flag) printf("%d\n",b[ans]);
	}
}

posted on 2019-01-18 22:15  ErkkiErkko  阅读(790)  评论(1编辑  收藏  举报