传送门
题意:给一个
n
n
n个点
m
m
m条边的带权无向图,让你构造一棵最小生成树使得
k
k
k个关键点都在上面
n
≤
1
e
5
,
m
≤
2
e
5
,
k
≤
5
n\le1e5,m\le2e5,k\le5
n≤1e5,m≤2e5,k≤5。
思路:
套上斯坦纳树板子:
f
i
,
j
f_{i,j}
fi,j表示
i
i
i为根子树特殊点状态为
j
j
j的最优值,然后分情况转移即可。
- 枚举 j j j子集
- 枚举不同的根
第二种式子写出来就是最短路的松弛操作因此可以上
d
i
j
k
s
t
r
a
dijkstra
dijkstra
代码:
#include<bits/stdc++.h>
#define ri register int
#define fi first
#define se second
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
static char buf[rlen],*ib,*ob;
(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
return ib==ob?-1:*ib++;
}
inline int read(){
int ans=0;
char ch=gc();
while(!isdigit(ch))ch=gc();
while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
return ans;
}
const int N=1e5+5;
typedef long long ll;
typedef pair<ll,int> pli;
typedef pair<int,int> pii;
const ll inf=1e18;
ll f[N][32],ans=inf;
int n,m,t;
bool vis[N];
vector<pii>e[N];
priority_queue<pli>q;
inline void dijkstra(int sta){
for(ri i=1;i<=n;++i)vis[i]=0;
while(!q.empty()){
int x=q.top().se;
q.pop();
if(vis[x])continue;
vis[x]=1;
for(ri i=0,v;i<e[x].size();++i){
if(f[v=e[x][i].fi][sta]>f[x][sta]+e[x][i].se){
f[v][sta]=f[x][sta]+e[x][i].se;
if(!vis[v])q.push(pli(-f[v][sta],v));
}
}
}
}
int main(){
n=read(),t=read(),m=read();
for(ri i=1,up=1<<t;i<=n;++i)for(ri j=0;j<up;++j)f[i][j]=inf;
for(ri i=1;i<=t;++i)f[read()][1<<(i-1)]=0;
for(ri i=1,a,b,c;i<=m;++i)a=read(),b=read(),c=read(),e[a].push_back(pii(b,c)),e[b].push_back(pii(a,c));
for(ri sta=0,up=1<<t;sta<up;++sta){
for(ri i=1;i<=n;++i){
for(ri j=sta&(sta-1);j;j=sta&(j-1))f[i][sta]=min(f[i][sta],f[i][j]+f[i][sta^j]);
if(f[i][sta]^inf)q.push(pli(-f[i][sta],i));
}
dijkstra(sta);
if(sta==up-1)for(ri i=1;i<=n;++i)ans=min(ans,f[i][sta]);
}
cout<<ans;
return 0;
}