1. 程式人生 > >bzoj 4006 管道連線 —— 斯坦納樹+狀壓DP

bzoj 4006 管道連線 —— 斯坦納樹+狀壓DP

題目:https://www.lydsy.com/JudgeOnline/problem.php?id=4006

用斯坦納樹求出所有關鍵點的各種連通情況的代價,把這個作為狀壓(壓的是集合選擇情況)的初值DP即可。

程式碼如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define pb push_back
using namespace std;
int rd()
{
  int ret=0,f=1; char
ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=0; ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return f?ret:-ret; } int const xn=1005,xm=6005,xxn=(1<<10)+5,inf=0x3f3f3f3f; int n,m,hd[xn],ct,to[xm],nxt[xm],w[xm],f[xn][xxn],g[xxn]; int c[15],bin[15],cnt;
bool vis[xn]; queue<int>q; vector<int>v[15]; int Min(int x,int y){return x<y?x:y;} void add(int x,int y,int z){to[++ct]=y; nxt[ct]=hd[x]; hd[x]=ct; w[ct]=z;} void spfa(int sta) { memset(vis,0,sizeof vis); for(int i=1;i<=n;i++)if(f[i][sta]<inf)q.push(i),vis[i]=1; while(q.size()) {
int x=q.front(); q.pop(); vis[x]=0; for(int i=hd[x],u;i;i=nxt[i]) if(f[u=to[i]][sta]>f[x][sta]+w[i]) { f[u][sta]=f[x][sta]+w[i]; if(!vis[u])vis[u]=1,q.push(u); } } } int main() { bin[0]=1; for(int i=1;i<=10;i++)bin[i]=bin[i-1]*2; n=rd(); m=rd(); int K=rd(); for(int i=1,x,y,z;i<=m;i++) x=rd(),y=rd(),z=rd(),add(x,y,z),add(y,x,z); memset(f,0x3f,sizeof f); for(int i=1,cr,x;i<=K;i++) { cr=rd(); x=rd(); if(!c[cr])c[cr]=++cnt; v[c[cr]].pb(i); f[x][bin[i-1]]=0;//i! } int mx=bin[K]; for(int sta=1;sta<mx;sta++) { for(int i=1;i<=n;i++) for(int s=(sta&(sta-1));s;s=(sta&(s-1))) f[i][sta]=Min(f[i][sta],f[i][s]+f[i][sta^s]); spfa(sta); } memset(g,0x3f,sizeof g); mx=bin[cnt]; for(int sta=1;sta<mx;sta++) { int s=0; for(int j=1;j<=cnt;j++) if(sta&bin[j-1]){for(int k=0;k<v[j].size();k++)s|=bin[v[j][k]-1];} for(int j=1;j<=n;j++)g[sta]=Min(g[sta],f[j][s]); } for(int sta=1;sta<mx;sta++) for(int s=(sta&(sta-1));s;s=(sta&(s-1))) g[sta]=Min(g[sta],g[s]+g[sta^s]); printf("%d\n",g[mx-1]); return 0; }