解题思路:11年北京现场赛的题目,经典的斯坦纳树。
斯坦纳树简称STNT是一种数据结构,是生成树的一个子集。这种数据结构出现的并不多,模型一般是固定的,就是求一个图中至少包含指定k个点的最小生成树。从11年开始,区域赛、浙大月赛已经出现好几道了。今天花了些时间学习这个,倒也略懂一些。
STNT的求法为状态压缩DP+Spfa。用二进制表示集合的状态,有两类转移,一种是集合内的更新类似Spfa的松弛,也就用Spfa来进行这类转移,第二类是集合间的合并更新。就是通过这样的转移最后得到一个最优的至少含k个点的集合。
具体的实现我都参照这个博客: Here,请大家移步这里。
#include <stdio.h> #include <string.h> #include <queue> using namespace std; #define MAX 1200 #define INF (1<<29) #define min(a,b) ((a)<(b)?(a):(b)) struct node { int v,len; node *next; }*head[MAX*2],tree[MAX*2]; queue<int> qu; bool in[100][MAX]; int cost[100][MAX],dp[MAX]; int n,nn,m,k,ptr,ans,st[MAX]; void Initial() { ptr = 0,nn = 1<<(2*k); memset(st,0,sizeof(st)); memset(in,0,sizeof(in)); memset(head,NULL,sizeof(head)); for (int j = 0; j < n; ++j) for (int i = 0; i < nn; ++i) cost[j][i] = INF; for (int i = 0; i < k; ++i) { st[i] = 1<<i,cost[i][st[i]] = 0; st[n-k+i] = 1<<(i+k),cost[n-k+i][st[n-k+i]] = 0;//把n-k+i映射到i+k,这样保证状态不超过2^(2*k) } } void AddEdge(int a,int b,int c) { tree[ptr].v = b,tree[ptr].len = c; tree[ptr].next = head[a],head[a] = &tree[ptr++]; } void Spfa() { while (!qu.empty()) { int j = qu.front() / MAX; int i = qu.front() % MAX; qu.pop(),in[j][i] = false; node *p = head[j]; while (p != NULL) { int v = p->v,nst = i | st[v]; if (cost[j][i] + p->len < cost[v][nst]) { cost[v][nst] = cost[j][i] + p->len; if (nst == i && !in[v][nst]) { in[v][nst] = true; qu.push(v * MAX + nst); } } p = p->next; } } } void Steiner_Tree() { int i,j,t,s; for (i = 0; i < nn; ++i) { for (j = 0; j < n; ++j) { for (t = (i - 1) & i; t; t = (t-1) & i) cost[j][i] = min(cost[j][i],cost[j][t|st[j]]+cost[j][(i-t)|st[j]]); if (cost[j][i] < INF) qu.push(j * MAX + i),in[j][i] = true; } Spfa(); } } int Check(int s) { int i,cnt = 0; for (i = 0; i < k; ++i){ if (s & (1 << i)) cnt++; if (s & (1 << k + i)) cnt--; } return cnt == 0; } int Solve_DP() { int i,j,t; for (i = 0; i < nn; ++i) { dp[i] = INF; for (j = 0; j < n; ++j) dp[i] = min(dp[i],cost[j][i]); } for (i = 1; i < nn; ++i) if (Check(i)) for (j = (i-1)&i; j; j = (j-1) & i) if (Check(j)) dp[i] = min(dp[i],dp[j] + dp[i-j]); return dp[nn-1]; } int main() { int i,j,t,a,b,c; scanf("%d",&t); while (t--) { scanf("%d%d%d",&n,&m,&k); Initial(); for (i = 0; i < m; ++i) { scanf("%d%d%d",&a,&b,&c); a--,b--; AddEdge(a,b,c); AddEdge(b,a,c); } Steiner_Tree(); ans = Solve_DP(); if (ans < INF) printf("%d\n",ans); else printf("No solution\n"); } }
本文ZeroClock原创,但可以转载,因为我们是兄弟。