题目大意:给N个点,M条边,边有长度,构成一个图。前K个点住的有居民,最后K个点是避难所。
要求你从这M条边中选出一些边,使得每个居民都能到达一个避难所而且选的边的总长度最少。
参考资料:《SPFA算法的优化及应用》
http://endlesscount.blog.163.com/blog/static/821197872012525113427573/
从题意能推断出,最终的解是一棵棵树组成的森林。
我们先对每棵树的模型进行求解,因为知道每个状态情况对应的树的最优解,最后压缩DP一下就能求森林的最优解了。
而对于每棵树,最直接的模型就是《SPFA算法的优化及应用》里游览计划那道题。
SPFA的思想就是用当前的最优解来松弛更新和当前相关的状态的最优解。
近似于DP,不过在有的时候避免了一些不必要的更新。
这题就是如此,DP[ i ][ j ] 表示以i根的时候,状态为j的当前的最优解,它能更新的状态有两个
枚举子树的形态:dp[ i ][ j ]=min{ dp[ i ][ j ],dp[ i ][ k ]+dp[ i ][ l ] },其中k和l是对j的一个划分。
按照边进行松弛:dp[ i ][ j ]=min{ dp[ i ][ j ],dp[ i' ][ j ]+w[ i ][ i' ] },其中i和i'之间有边相连。
第一个形态直接更新到一个状态,用for循环一下就好。
第二个更新用spfa。
理论就这么简单,具体实现上有许多技巧就是。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <stack>
#include <set>
#include <map>
using namespace std;
#define REP(i,n) for(int i=0;i<(n);++i)
#define FOR(i,l,r) for(int i=(l);i<=(r);++i)
#define DSC(i,r,l) for(int i=(r);i>=(l);--i)
#define N 52
#define NN (1<<10)
#define M 2010
#define INF 1e9
int pos[N];
int dp[N][NN];
bool visit[N][NN];
int ans[NN];
int n,nn,m,K;
int head[N],ip;
struct Edge
{
int to,next,c;
}edge[M];
void add(int u,int v,int c)
{
edge[ip].to=v;edge[ip].c=c;edge[ip].next=head[u];head[u]=ip++;
}
struct Node
{
int x,y;
Node(){}
Node(int x,int y):x(x),y(y){}
};
queue<Node>q;
void init()
{
FOR(i,1,n)
REP(j,nn)
dp[i][j]=INF;
memset(pos,0,sizeof(pos));
FOR(i,1,K)
{
pos[i]=1<<(i-1);
dp[i][ pos[i] ]=0;
pos[n-i+1]=1<<(K+i-1);
dp[n-i+1][ pos[n-i+1] ]=0;
//状态压缩时只压缩前K个点和最后K个点就好
//最后在求解时有一个check()函数,保证了每棵树都是合法的
}
}
void spfa()//仅对第二个松弛进行spfa
{
while(!q.empty())
{
int x=q.front().x,y=q.front().y;
q.pop();
visit[x][y]=0;
for(int p=head[x];p!=-1;p=edge[p].next)
{
int xx=edge[p].to,yy=y|pos[xx];
int temp=dp[x][y]+edge[p].c;
if(temp<dp[xx][yy])
{
dp[xx][yy]=temp;
if(y==yy && !visit[xx][yy])
{
visit[xx][yy]=1;
q.push(Node(xx,yy));
}
}
}
}
}
bool check(int y)//保证当前状态中,存在的前K个点的数目和最后K个点存在的数量相同
{ //由此当前状态中每个居民必定和一个避难所相连
int num1=0,num2=0;
REP(i,K<<1)
{
if(y&(1<<i))
{
if(i<K) num1++;
else num2++;
}
}
return num1==num2;
}
void solve()
{
REP(y,nn)//把第一个松弛完全剥离出来了,按每个状态进行更新
{
FOR(x,1,n)
{
for(int i=y&(y-1);i;i=y&(i-1))//这句话枚举出了当前y状态包含的所有状态,自己在纸上模拟一下
dp[x][y]=min(dp[x][y],dp[x][ i|pos[x] ] + dp[x][ (y-i)|pos[x] ]);
if(dp[x][y]<INF) q.push(Node(x,y));
}
spfa();
}
REP(y,nn)
{
ans[y]=INF;
FOR(i,1,n) ans[y]=min(ans[y],dp[i][y]);
}
//此时状态已经求解完了,最后DP出答案就好
REP(y,nn)
{
if(check(y))
{
for(int i=(y-1)&y;i;i=(i-1)&y)
{
if(check(i))
{
ans[y]=min(ans[y],ans[i]+ans[y-i]);
}
}
}
}
}
int main()
{
//freopen("in","r",stdin);
int cas;
cin>>cas;
int x,y,c;
while(cas--)
{
memset(head,-1,sizeof(head)); ip=0;
cin>>n>>m>>K;
nn=(1<<(K<<1));
init();
while(m--)
{
scanf("%d%d%d",&x,&y,&c);
add(x,y,c);
add(y,x,c);
}
solve();
if(ans[nn-1]>=INF) puts("No solution");
else cout<<ans[nn-1]<<endl;
}
return 0;
}