题目大意:有 n 座庙,每个庙里有 n 个和尚,有 m 个其它地点,这 n + m n + m n+m 个地点都可以打井,在每个点打井有一个花费 a [ i ] a[i] a[i]。另外还有 p p p 条无向边,每条边有一个花费 w w w,求要使得这 n n n 个和尚都能喝到水的最小花费。
引入一个虚点0,连向其它所有点,边权为这些点的点权,则题目转化为 0号点为根节点,使得 前 n 个点连通的最小花费,套斯坦纳树模板。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
#define pii pair<int,int>
#define fir first
#define sec second
vector<pii> g[maxn];
int n,m,p;
int val[maxn],st[maxn],endst;
queue<int> q;
int vis[maxn][1 << 7 + 1];
int dp[maxn][1 << 7 + 1];
void spfa(int state) {
while(!q.empty()) {
int top = q.front();
q.pop();
vis[top][state] = 0;
for(auto it : g[top]) {
int v = it.fir,w = it.sec;
if(dp[v][st[v] | state] == -1 || dp[v][st[v] | state] > dp[top][state] + w) {
dp[v][st[v] | state] = dp[top][state] + w;
if((st[v] | state) != state || vis[v][state]) continue;
q.push(v);
vis[v][state] = 1;
}
}
}
}
void steniertree() {
for(int i = 0; i <= n + m; i++)
dp[i][st[i]] = 0;
for(int j = 1; j < endst; j++) {
for(int i = 0; i <= n + m; i++) {
if(st[i] && (st[i] & j) == 0) continue;
for(int k = j & (j - 1); k; k = (k - 1) & j) {
int x = st[i] | k,y = st[i] | (j - k);
if(dp[i][x] != -1 && dp[i][y] != -1) {
if(dp[i][j] == -1 || dp[i][x] + dp[i][y] < dp[i][j])
dp[i][j] = dp[i][x] + dp[i][y];
}
}
if(dp[i][j] != -1) {
q.push(i);
vis[i][j] = 1;
}
}
spfa(j);
}
}
int main() {
while(~scanf("%d%d%d",&n,&m,&p)) {
for(int i = 0; i <= n + m; i++)
g[i].clear(),st[i] = 0;
for(int i = 1; i <= n + m; i++)
scanf("%d",&val[i]);
for(int i = 0; i <= n; i++)
st[i] = (1 << i);
endst = 1 << (n + 1);
memset(dp,-1,sizeof dp);
for(int i = 1; i <= p; i++) {
int u,v,w;scanf("%d%d%d",&u,&v,&w);
g[u].push_back(pii(v,w));
g[v].push_back(pii(u,w));
}
for(int i = 1; i <= n + m; i++) {
g[0].push_back(pii(i,val[i]));
g[i].push_back(pii(0,val[i]));
}
steniertree();
printf("%d\n",dp[0][endst - 1]);
}
return 0;
}