bzoj4774: 修路
Description
村子间的小路年久失修,为了保障村子之间的往来,法珞决定带领大家修路。对于边带权的无向图 G = (V, E),请选择一些边,使得1 <= i <= d, i号节点和 n - i + 1 号节点可以通过选中的边连通,最小化选中的所有边的权值和。
Input
第一行两个整数 n, m,表示图的点数和边数。接下来的 m行,每行三个整数 ui, vi, wi,表示有一条 ui 与 vi 之间,权值为 wi 的无向边。
1 <= d <= 4
2d <= n <= 10^4
0 <= m <= 10^4
1 <= ui, vi <= n
1 <= wi <= 1000
Output
一行一个整数,表示答案,如果无解输出-1.
Sample Input
10 20 1
6 5 1
6 9 4
9 4 2
9 4 10
6 1 2
2 3 6
7 6 10
5 7 1
9 7 2
5 9 10
1 6 8
4 7 4
5 7 1
2 6 9
10 10 6
8 7 2
10 9 10
1 2 4
10 1 8
9 9 7
Sample Output
8
题解
斯坦纳树模板题。
设状态\(f[i][j]\)表示:以\(i\)为根且至少包含了集合\(j\)中的点的某个连通子树的最小边权和,其中\(j\)是\(S\)的一个子集。
\(g[j]\)表示考虑了点集\(j\)中的点的最优选边方案的边权和,其中\(j\)是\(S\)的一个子集。
我们判定\(g[j]\)合法当且仅当要连通的点对同时出现或同时不出现在状态\(j\)中。
那么\(g[j]=\mathrm{min}\{g[j],g[s]+g[j-s]\}\),且\(s\)是合法状态。
这样我们先用斯坦纳树的方法求出\(f\),从而得到初始的\(g\).
显然\(g[j]=\mathrm{min}\{f[i][j]\}\)
然后枚举当前状态\(j\)以及当前状态的合法子集更新\(g\).
最终\(g[S]\)即为答案(\(S\)是题中给出的点集)。
代码
#include<bits/stdc++.h>
#define MAXN 20010
#define INF 0x3f3f3f3f
namespace IO{
char buf[1<<15],*fs,*ft;
inline char gc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;}
inline int qr(){
int x=0,rev=0,ch=gc();
while(ch<'0'||ch>'9'){if(ch=='-')rev=1;ch=gc();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=gc();}
return rev?-x:x;}
}using namespace IO;
using namespace std;
struct Edge{int t,next,v;}e[MAXN<<1];
int N,M,D,f[MAXN][1<<8],g[1<<8],head[MAXN],cnt;
inline void Add_Edge(int from,int to,int val){
e[++cnt].t=to;e[cnt].next=head[from];head[from]=cnt;e[cnt].v=val;
e[++cnt].t=from;e[cnt].next=head[to];head[to]=cnt;e[cnt].v=val;
}
queue<int>q;
bool vis[MAXN];
inline void Spfa(int S){
for(int i=1;i<=N;i++)if(f[i][S]<INF)q.push(i),vis[i]=1;
while(!q.empty()){
int u=q.front();q.pop();vis[u]=0;
for(int i=head[u];i;i=e[i].next){
int v=e[i].t;
if(f[u][S]+e[i].v<f[v][S]){
f[v][S]=f[u][S]+e[i].v;
if(!vis[v])q.push(v),vis[v]=1;
}
}
}
}
inline bool Check(int S){
for(int i=0;i<D;i++){
if(((S>>i)&1)&&!((S>>(D+i))&1))return 0;
}
return 1;
}
int x,y,z;
int main(){
#ifndef ONLINE_JUDGE
freopen("bzoj4774.in","r",stdin);
freopen("bzoj4774.out","w",stdout);
#endif
N=qr();M=qr();D=qr();
for(int i=1;i<=M;i++){
x=qr();y=qr();z=qr();
Add_Edge(x,y,z);
}
memset(f,INF,sizeof(f));memset(g,INF,sizeof(g));
for(int i=1;i<=D;i++)f[i][(1<<(i-1))]=f[N-i+1][(1<<(D+i-1))]=0;
for(int i=0,ed=(1<<(D+D));i<ed;i++){
for(int j=1;j<=N;j++){
for(int s=i&(i-1);s;s=i&(s-1)){
f[j][i]=min(f[j][i],f[j][s]+f[j][i-s]);
}
}
Spfa(i);
for(int j=1;j<=N;j++)g[i]=min(g[i],f[j][i]);
}
for(int i=0,ed=(1<<(D+D));i<ed;i++){
for(int s=i&(i-1);s;s=i&(s-1)){
if(Check(s)&&Check(i-s))g[i]=min(g[i],g[s]+g[i-s]);
}
}
printf("%d\n",g[(1<<(D+D))-1]==INF?-1:g[(1<<(D+D))-1]);
return 0;
}