bzoj 4006 [JLOI2015]管道连接(斯坦纳树+状压DP)

时间:2025-04-02 18:36:25

【题目链接】

http://www.lydsy.com/JudgeOnline/problem.php?id=4006

【题意】

给定n点m边的图,连接边(u,v)需要花费w,问满足使k个点中同颜色的点都连通的最小费用。

【思路】

题目所求斯坦纳森林。

如果我们知道满足颜色集合S连通的最小值g[S],则有转移式:

G[S]=min{ g[s] , G[S’]+G[S-S’] }

则G[(1<<C)-1]即答案,G[S]定义为使得颜色集合S中所有相同颜色的点都连通的最小值。

这里的g[S],其实就是一棵包含S中所有颜色的斯坦纳树,即求一棵包含所有颜色在S中的点的斯坦纳树,我们设f[i][st]为在i点且包含点集为st的最小花费则有转移式:

f[i][st]=min{ f[i][st’]+f[i][st-st’] }

        f[i][st]=min{ f[i’][st]+weight(i,i’) }

两次状压DP bingo。memset那里可以优化一下,懒得改了 =_=

【代码】

 #include<set>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define trav(u,i) for(int i=front[u];i;i=e[i].nxt)
#define FOR(a,b,c) for(int a=(b);a<=(c);a++)
using namespace std; typedef long long ll;
const int N = 1e3+;
const int M = 4e3+;
const int P = ;
const int inf = 0xf0f0f0f; ll read() {
char c=getchar();
ll f=,x=;
while(!isdigit(c)) {
if(c=='-') f=-; c=getchar();
}
while(isdigit(c))
x=x*+c-'',c=getchar();
return x*f;
}
struct Edge { int v,w,nxt;
}e[M<<];
int en=,front[N];
void adde(int u,int v,int w)
{
e[++en]=(Edge){v,w,front[u]}; front[u]=en;
}
struct Node {
int c,w;
bool operator < (const Node& rhs) const {
return c<rhs.c;
}
}ns[P]; int n,m,K,cnt=;
int f[N][<<P],g[<<P]; queue<int> q; int inq[N]; void spfa(int sta)
{
while(!q.empty()) {
int u=q.front(); q.pop(); inq[u]=;
trav(u,i) {
int v=e[i].v;
if(f[v][sta]>f[u][sta]+e[i].w) {
f[v][sta]=f[u][sta]+e[i].w;
if(!inq[v])
inq[v]=,q.push(v);
}
}
}
}
int solve()
{
int all=<<cnt;
FOR(sta,,all-) {
FOR(i,,n) {
for(int s=(sta-)&sta;s;s=(s-)&sta)
f[i][sta]=min(f[i][sta],f[i][s]+f[i][sta-s]);
if(f[i][sta]!=inf) q.push(i),inq[i]=;
}
spfa(sta);
}
int ans=inf;
FOR(i,,n) ans=min(ans,f[i][all-]);
return ans;
} int main()
{
freopen("channel.in","r",stdin);
freopen("channel.out","w",stdout);
n=read(),m=read(),K=read();
FOR(i,,m) {
int u=read(),v=read(),w=read();
adde(u,v,w); adde(v,u,w);
}
FOR(i,,K) {
ns[i].c=read(),ns[i].w=read();
}
sort(ns+,ns+K+);
int C=;
FOR(i,,K) {
if(ns[i].c!=ns[i-].c) C++;
ns[i].c=C;
}
memset(g,0xf,sizeof(g));
int all=<<C;
FOR(sta,,all-) {
memset(f,0xf,sizeof(f));
cnt=; FOR(i,,K) if((<<ns[i].c-)&sta) f[ns[i].w][<<cnt++]=;
g[sta]=solve();
}
FOR(sta,,all-) {
for(int s=(sta-)&sta;s;s=(s-)&sta)
g[sta]=min(g[sta],g[s]+g[sta-s]);
}
printf("%d\n",g[all-]);
return ;
}