这个东西很久以前就学过了,不过前几天看到后又一脸懵逼,于是赶紧滚来复习一下。
简介
斯坦纳树是将图的一个指定点集内的所有点连通的一棵树,常见的问题有最小斯坦纳树(Minimal Steiner Tree)(怎么也叫MST啊喂)。和最小生成树不同的是,斯坦纳树可以包含不在指定点集内的点。
求解方法
斯坦纳树的求解方法类似于状压DP,设(f[S][i])表示以结点(i)为根,连通了点集(S)内的所有点的最小代价。
先在外层枚举连通状态(S)。
然后转移分为两部分:
-
枚举(S)的子集进行转移:$$f[S][i]=min_{T subseteq S}{f[T][i]+f[S-T][i]}$$
-
使用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
",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('
');
}
return 0;
}
[THUSC2017]巧克力
分析
斯坦纳树和二分答案(中位数的套路)的话很显然,但是那个随机化是真有点想不到。
直接枚举是哪(k)种图案然后跑斯坦纳树的复杂度是(O(T log n inom{n imes m}{k} (3^k+2^k imes SPFA))),肯定会直接废掉(虽然好像也有不少分)。
考虑上网搜题解,题解告诉我们可以给每种颜色随机一个(1 sim k)的新颜色然后再跑斯坦纳树,(k=5)时,这样单次的正确率是(frac{5!}{5^5}=0.0384),重复做(100)次的错误率就是(0.01992716266102454756995285049955)。博主向来非酋,所以做了(200)次。
剩下的就是斯坦纳树模板题了,时间复杂度是(O(T log n imes 非酋常数 imes 100 imes (3^k+2^k imes 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
");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
",b[ans]);
}
}