题意
给定一个 n n n个点 m m m条边的无向图,其中有 k k k个关键点
求一个权值最小的连通子图,使得子图包含所有的关键点。
n < = 100 , m < = 500 , k < = 10 n<=100,m<=500,k<=10 n<=100,m<=500,k<=10
因为 k k k不大,考虑状态压缩当前包含的关键点状态
最后的连通子图一定是一棵树,成环的话,环上的边没有意义,答案反而增大
所以我们枚举这颗树的中心点 i i i,相当于变成一颗以 i i i为根的树
定义 f [ i ] [ s ] f[i][s] f[i][s]表示以 i i i为根包含关键点状态为 s s s的最小花费
首先有转移①,扩张联通性
f [ i ] [ s ] = f [ j ] [ s ] + w ( j , i ) f[i][s]=f[j][s]+w(j,i) f[i][s]=f[j][s]+w(j,i)
可以看作是选择 ( j , i ) (j,i) (j,i)这条边,那么 j j j的状态可以转移到 i i i去
然后有转移②,合并自身状态
f [ i ] [ s ] = f [ i ] [ s u b ] + f [ i ] [ s ⊕ s u b ] f[i][s]=f[i][sub]+f[i][s\oplus sub] f[i][s]=f[i][sub]+f[i][s⊕sub]
其中 s u b sub sub是 s s s的子集
这个方程的意思就是把 s s s的所有分支合并起来
对于确定的状态 s s s,转移 ① ① ①的方程可以用最短路算法来优化 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))
转移②的方程可以通过枚举子集达到 O ( n ∗ 3 k ) O(n*3^k) O(n∗3k)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+20;
typedef pair<int,int>p;
priority_queue<p,vector<p>,greater<p> >q;
int n,m,k,vis[maxn],x[maxn];
struct edge{
int to,nxt,w;
}d[maxn]; int head[maxn],cnt=1;
void add(int u,int v,int w)
{
d[++cnt] = ( edge ){v,head[u],w},head[u] = cnt;
}
int f[109][1<<13];
void dijkstra(int s)
{
for(int i=0;i<=n;i++) vis[i] = 0;
while( !q.empty() )
{
int u = q.top().second; q.pop();
if( vis[u] ) continue;
vis[u] = 1;
for(int i=head[u];i;i=d[i].nxt )
{
int v = d[i].to;
if( f[v][s]>f[u][s]+d[i].w )
{
f[v][s] = f[u][s]+d[i].w;
q.push( p(f[v][s],v) );
}
}
}
}
int main()
{
memset( f,0x3f,sizeof f );
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++)
{
int l,r,w; scanf("%d%d%d",&l,&r,&w);
add(l,r,w); add(r,l,w);
}
for(int i=1;i<=k;i++)
scanf("%d",&x[i] ),f[x[i]][1<<(i-1) ] = 0;
int mx = ( 1<<k );
for(int s=1;s<mx;s++)
{
for(int i=1;i<=n;i++)
{
for(int j=s&(s-1);j;j=s&(j-1) )//自己合并子树
f[i][s] = min( f[i][s],f[i][j]+f[i][s^j] );
if( f[i][s]!=0x3f3f3f3f ) q.push( p(f[i][s],i) );
}
dijkstra( s );//扩张联通性
}
printf("%d",f[x[1]][mx-1] );
}