斯坦纳树 学习总结
所谓斯坦纳树,就是一类使关键点联通的最优化问题OwO
考试的时候被考了一发,然后就被炸飞了(然而不知道为什么其他人都会
所以就做了点题目
首先裸的斯坦纳树是这样的
设f(i,S)表示当前在第i个点,关键点联通状态为S的最优代价
转移有两种:
第一种是合并两个联通块,f(i,S)=max{f(i,T)+f(i,S^T)}
在这里注意到T和S^T在i点是联通的,所以可以将其合并
第二种是扩展当前状态,即 f(i,S)=max{f(j,S)+edge(i,j)}
注意到这一步的转移是没有固定顺序的,所以我们要利用SPFA来进行转移
其实也是挺好写的,每次只是先做一遍子集枚举,之后把有用的状态放入队列跑SPFA
BZOJ 4006 管道连接
这里要求相同频道的情报站要连接,但并不要求所有关键情报站都连接
所以我们可以利用斯坦纳树求出形成每个可能的联通块的最优代价
最后在做一次DP即可,即g(S)=max(g(T)+g(S^T))
#include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> #include<iostream> #include<queue> using namespace std; const int maxn=1010; int n,m,p,u,v,w,oo; int h[maxn],cnt=0; struct edge{ int to,next,w; }G[20010]; int g[1050]; int f[maxn][1050]; int c[maxn],d[maxn]; int bin[maxn]; bool vis[maxn]; queue<int>Q; void add(int x,int y,int z){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt; } void read(int &num){ num=0;char ch=getchar(); while(ch<'!')ch=getchar(); while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar(); } void SPFA(int st){ while(!Q.empty()){ int u=Q.front();Q.pop(); for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(f[v][st]>f[u][st]+G[i].w){ f[v][st]=f[u][st]+G[i].w; if(!vis[v])Q.push(v),vis[v]=true; } }vis[u]=false; }return; } bool check(int S){ for(int i=1;i<=p;++i){ int now=(S&bin[i]); if(now==0||now==bin[i])continue; return false; }return true; } int main(){ read(n);read(m);read(p); for(int i=1;i<=m;++i){ read(u);read(v);read(w); add(u,v,w);add(v,u,w); } for(int i=1;i<=p;++i)scanf("%d%d",&c[i],&d[i]); for(int i=1;i<=p;++i){ for(int j=1;j<=p;++j)if(c[j]==c[i])bin[i]|=(1<<(j-1)); } memset(g,0x3f,sizeof(g));memset(f,0x3f,sizeof(f)); for(int i=1;i<=p;++i)f[d[i]][1<<(i-1)]=0;oo=g[0]; for(int S=1;S<(1<<p);++S){ for(int i=1;i<=n;++i){ for(int T=S-1;T;T=(T-1)&S){ f[i][S]=min(f[i][S],f[i][T]+f[i][S^T]); } if(f[i][S]<oo)Q.push(i),vis[i]=true; }SPFA(S); if(!check(S))continue; for(int i=1;i<=n;++i)g[S]=min(g[S],f[i][S]); for(int T=S-1;T;T=(T-1)&S)g[S]=min(g[S],g[T]+g[S^T]); }printf("%d\n",g[(1<<p)-1]); return 0; }
hdu 4085
跟上一道题大同小异,也是有一个多余的限制
同样做DP就可以了
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> #include<queue> using namespace std; const int maxn=52; int T,n,m,d,u,v,w,oo,lim; int h[maxn],cnt=0; struct edge{ int to,next,w; }G[10010]; bool vis[52]; int g[1050]; int f[52][1050]; bool tmp[1050]; queue<int>Q; inline void add(int x,int y,int z){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt; } inline void read(int &num){ num=0;char ch=getchar(); while(ch<'!')ch=getchar(); while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar(); } inline bool check(int S){ int cnt=0; for(int i=1;i<=d;++i)if(S>>(i-1)&1)cnt++; for(int i=d+1;i<=lim;++i)if(S>>(i-1)&1)cnt--; return cnt==0; } inline void SPFA(int st){ while(!Q.empty()){ int u=Q.front();Q.pop(); for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(f[v][st]>f[u][st]+G[i].w){ f[v][st]=f[u][st]+G[i].w; if(!vis[v])Q.push(v),vis[v]=true; } }vis[u]=false; }return; } int main(){ read(T); while(T--){ memset(h,0,sizeof(h));cnt=0; read(n);read(m);read(d);lim=(d<<1); for(int i=1;i<=m;++i){ read(u);read(v);read(w); add(u,v,w);add(v,u,w); } memset(g,0x3f,sizeof(g));oo=g[0]; memset(f,0x3f,sizeof(f)); for(int i=1;i<=d;++i)f[i][1<<(i-1)]=0; for(int i=d+1;i<=lim;++i)f[n-(lim-i)][1<<(i-1)]=0; for(int S=1;S<(1<<lim);++S)tmp[S]=check(S); for(int S=1;S<(1<<lim);++S){ for(int i=1;i<=n;++i){ for(int T=S-1;T;T=(T-1)&S){ f[i][S]=min(f[i][S],f[i][T]+f[i][S^T]); } if(f[i][S]<oo)Q.push(i),vis[i]=true; }SPFA(S); if(!tmp[S])continue; for(int i=1;i<=n;++i)g[S]=min(g[S],f[i][S]); for(int T=S-1;T;T=(T-1)&S)g[S]=min(g[S],g[T]+g[S^T]); } if(g[(1<<lim)-1]==oo)printf("No solution\n"); else printf("%d\n",g[(1<<lim)-1]); }return 0; }
hdu 3331
首先考虑如果所有点都是关键点的话,显然是USACO的灌水OwO
做法就是对于建立虚拟节点,向每个点连边,边权为挖井费用
然后Kruscal就可以了
对应到这道题目上我们建立虚拟节点之后可以看作要使关键点和虚拟节点都联通
就变成斯坦纳树的裸题了
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> #include<queue> using namespace std; const int maxn=1010; int n,m,p,u,v,w,oo; int h[maxn],cnt=0; struct edge{ int to,next,w; }G[50010]; bool vis[maxn]; int f[maxn][72]; queue<int>Q; void add(int x,int y,int z){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt; } void SPFA(int st){ while(!Q.empty()){ int u=Q.front();Q.pop(); for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(f[v][st]>f[u][st]+G[i].w){ f[v][st]=f[u][st]+G[i].w; if(!vis[v])Q.push(v),vis[v]=true; } }vis[u]=false; }return; } int main(){ while(scanf("%d%d%d",&n,&m,&p)==3){ memset(h,0,sizeof(h));cnt=0; for(int i=1;i<=n+m;++i){ scanf("%d",&w); add(0,i,w);add(i,0,w); } for(int i=1;i<=p;++i){ scanf("%d%d%d",&u,&v,&w); add(u,v,w);add(v,u,w); } memset(f,0x3f,sizeof(f));n++;oo=f[0][0]; for(int i=0;i<n;++i)f[i][1<<i]=0; for(int S=1;S<(1<<n);++S){ for(int i=0;i<=n+m;++i){ for(int T=S-1;T;T=(T-1)&S){ f[i][S]=min(f[i][S],f[i][T]+f[i][S^T]); } if(f[i][S]<oo)Q.push(i),vis[i]=true; }SPFA(S); } int ans=oo; for(int i=0;i<=n;++i)ans=min(ans,f[i][(1<<n)-1]); printf("%d\n",ans); }return 0; }
貌似这类题目很容易看出来?
因为基本上给定关键点的不是虚树就是斯坦纳树OwO