/*
斯坦那树:一个无向图,n个点m条边,有k个关键点,求联通这k个点的最小代价
由于最后的结果一定是一棵树,所以dp[i][s]表示以i为根,
当前连通状态为s的代价,注意每个点都可能为根.
dp[i][s]可以由s的子状态转移过来,也可以由别的点j通过i,j这条边与i相连形成以i为根的s
即dp[i][s]=min(dp[i][j]+dp[i][s^j]) j为s的子集
dp[i][s]=min(dp[i][s],dp[j][s]+val[i][j])
那么这个式子本质上就是最短路了,对于每次的s,更新起点i了以后,都跑一次dij松弛其它点的s
思考点:为什么连了i,j这条边,而j的状态还是s呢,新加入的i点难道不会对状态产生影响吗
如果s这个状态i已经存在了,那么说明dp[j][s]中已经有一条边连向i了,
这条边是多余的,并不会更新dp[i][s]
如果s这个状态i不存在,那么可能会更新到dp[i][s],这是i并没有表示连通,
但是dp[i][1<<(i-1)]的代价是0,它可以通过转移1来实现把i加入连通中
复杂度为3^k*n+2^k*ElogE
*/
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int maxn = 105;
struct node{
int id;
ll v;
node(int a,ll b)
{
id = a;
v = b;
}
bool operator<(const node&n)const
{
return v > n.v;
}
};
vector<node> g[maxn];
int a[maxn],vis[maxn];
ll dp[maxn][1<<10];
void dij(int begin,int s,int n)
{
priority_queue<node> q;
for (int i = 1; i <= n; i++) vis[i] = 0;
q.push(node(begin,dp[begin][s]));
while( !q.empty() )
{
int x = q.top().id;
q.pop();
vis[x] = 1;
for (int i = 0; i < g[x].size(); i++)
{
int t = g[x][i].id;
if( dp[t][s] > dp[x][s] + g[x][i].v ) //松弛相邻点对应的s
{
dp[t][s] = dp[x][s] + g[x][i].v;
q.push(node(t,dp[t][s]));
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n,m,k;
cin >> n >> m >> k;
for (int i = 1; i <= m; i++)
{
int x,y;
ll v;
cin >> x >> y >> v;
g[x].push_back(node(y,v));
g[y].push_back(node(x,v));
}
for (int i = 1; i <= n; i++) //初始化代价无穷大
{
for (int j = 0; j < (1<<k); j++) dp[i][j] = 1e18;
}
for (int i = 1; i <= k; i++)
{
cin >> a[i];
dp[a[i]][1<<(i-1)] = 0; //对于关键点,它存在对应的状态代价为0
}
for (int s = 1; s < (1<<k); s++) //枚举状态
{
for (int i = 1; i <= n; i++) //枚举根
{
for (int sub = s&(s-1); sub; sub=s&(sub-1)) //子集枚举
dp[i][s] = min(dp[i][s],dp[i][sub]+dp[i][s^sub]);
if( dp[i][s] != 1e18 ) dij(i,s,n); //最短路更新
}
}
ll ans = 1e18;
for (int i = 1; i <= k; i++) ans = min(ans,dp[a[i]][(1<<k)-1]);
cout << ans << '\n';
return 0;
}
斯坦那树
最新推荐文章于 2022-09-26 10:59:55 发布