LINK

题意

给定一个 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][ssub]

其中 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(n3k)

#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] );
}