Luogu P4784 [BalticOI 2016 Day2] 城市
题意
给定一个带权无向图,求联通 \(k\) 个关键点的最小代价。
思路
可以较为容易的看出,这就是最小斯坦纳树的板子!所以对于这个题,我们可以考虑用状压的思想来解决。
若令 \(dp_{i,s}\) 为以 \(i\) 为根节点的一棵树包含关键点集合 \(s\) 时的最小代价,则可以根据根节点 \(i\) 的度数分成两种情况。
-
\(i\) 的度数为 \(1\),那么设有点 \(j\) 与之相连,则有 $$dp_{i,s}+e_i.w \to dp_{j,s}$$,不难发现可以跑最短路求出。(这道题好像卡 SPFA,
至少我的被卡了,所以我使用的 Dijkstra 求的) -
\(i\) 的度数不为 \(1\),直接暴力将关键点集 \(j\) 拆成两个子集,有 $$dp_{i,A}+dp_{i,\complement_sA} \to dp_{i,s}$$
跑完整个之后,答案也就是 \(dp_{x,(1<<k)-1}\) 就出来了,完结撒花!
最后还有几点需要注意的:
-
请注意数据范围,给 \(dp\) 数组设的初始值一定不能只是 \(2^{31}-1\),需要开大点。
-
在枚举 \(s\) 的子集时有一个小小的优化,如果当前枚举的子集 \(A\) 已经小于 \(\complement_sA \operatorname{xor} A\) 时,可以直接退出循环,因为后面的组合已经枚举过了。
-
剩下的注意点就看代码注释吧。完结撒花(真)!
代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=2e5+5;
const int inf=1e18; //注意这里的inf不能开0x7fffffff
int n,m,k,x,tot;
int h[N],vis[N],dp[N][50];
struct pq{
int id,w;
bool operator<(const pq &T)const{
return w>T.w;
}
};
priority_queue<pq>q;
struct edge{
int to,nxt,w;
}e[N<<2];
inline void add(int x,int y,int w){
e[++tot].to=y,e[tot].nxt=h[x],e[tot].w=w,h[x]=tot;
return;
}
inline void dijkstra(int s){
memset(vis,0,sizeof vis);
while(!q.empty()){
int u=q.top().id;
q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].to,w=e[i].w;
if(dp[u][s]+w<dp[v][s]){
dp[v][s]=dp[u][s]+w;
q.push({v,dp[v][s]});
}
}
}
return;
}
signed main(){
// freopen("P6192.in","r",stdin);
// freopen("mamba.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>k>>m;
memset(dp,127,sizeof dp);
for(int i=1;i<=k;i++){
cin>>x;
dp[x][1<<(i-1)]=0; //
}
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
add(u,v,w),add(v,u,w);
}
for(int i=1;i<(1<<k);i++){ //最小斯坦纳树模板
for(int j=1;j<=n;j++){
for(int u=i&(i-1);u;u=i&(u-1)){
if(i<(u^i))break;
dp[j][i]=min(dp[j][i],dp[j][u]+dp[j][i^u]); //枚举子集
}
if(dp[j][i]<inf){
q.push({j,dp[j][i]});
//以此点为出发点
}
}
dijkstra(i);
}
cout<<dp[x][(1<<k)-1]<<endl; //这里dp数组的第一维可以是k个重要城市中的任意一个
}
/*
10 4 8
2 3 5 7
3 4 10
1 2 7
2 6 9
5 4 9
7 9 10
8 7 6
3 6 10
9 3 9
*/

浙公网安备 33010602011771号