题意:给你n个寺庙,m个村庄,p条路,现在你要在这n+m个位置中选出若干个位置打井,每个位置打井的费用会告诉你,同时p条路也有修建费用,现在每个寺庙都住着一个和尚,问你最小的费用让这n个和尚都能喝上水。
思路:可以对照之前做的MST题目(https://www.cnblogs.com/hua-dong/p/11164702.html)。 之前那个题是让所有点都喝上水,让后新建一个0号节点,向所有点连边,边权是打井的费用,然后跑最小生成树。 而本题是让所有寺庙有水,不关心村庄,所以应该是斯坦纳树模型。
斯坦纳树:使得关键点连通的最小代价。 dp[i][j]代表以i为根,连通状态为j的最小代价。 不停地子集DP+dijkstra....
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=2010; const int inf=1e9; int Laxt[maxn*7],Next[maxn*7],To[maxn*7],Len[maxn*7],cnt; int N,M,dis[maxn][maxn],dp[maxn][maxn],vis[maxn],V; void add(int u,int v,int len) { Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; Len[cnt]=len; } void SPFA() { rep(i,0,V) rep(j,0,V) dis[i][j]=inf; rep(i,0,V) { queue<int>q; dis[i][i]=0; q.push(i); while(!q.empty()){ int u=q.front(); q.pop(); vis[u]=0; for(int j=Laxt[u];j;j=Next[j]){ int v=To[j]; if(dis[i][v]>dis[i][u]+Len[j]){ dis[i][v]=dis[i][u]+Len[j]; if(!vis[v]){vis[v]=1; q.push(v);} } } } } } void solve() { rep(i,0,N) rep(j,0,V) dp[j][1<<i]=dis[i][j]; rep(i,1,(1<<(N+1))-1){ if(!((i-1)&i)) continue; //只有一位,不管,上面已经给出。 rep(j,0,V){ dp[j][i]=inf; for(int k=i;k;k=(k-1)&i) dp[j][i]=min(dp[j][i],dp[j][k]+dp[j][i^k]); } rep(k,0,V){ if(dp[k][i]==inf) continue; rep(j,0,V) dp[j][i]=min(dp[j][i],dp[k][i]+dis[k][j]); } } } int main() { int P,u,v,x; while(~scanf("%d%d%d",&N,&M,&P)){ V=N+M; rep(i,0,V) Laxt[i]=0; cnt=0; rep(i,1,V){ scanf("%d",&x); add(0,i,x); add(i,0,x); } rep(i,1,P){ scanf("%d%d%d",&u,&v,&x); add(u,v,x); add(v,u,x); } SPFA(); solve(); printf("%d ",dp[0][(1<<(N+1))-1]); } return 0; }