大致题意: 一张无向图,节点编号(0sim n)。在(0)号点有(k)个人,要求有人经过了(1sim i-1)的所有节点,才能经过节点(i)。求经过全部(n)个节点(k)个人走过的最小路径总长。
网络流
又是无向图,又是这么稀奇古怪的限制,这道题怎么看都不像是网络流啊。。。(不对,好像稀奇古怪的限制正是网络流的精神所在)
实际上,我们考虑对于某一个人,由TA首次经过的节点一定是一个递增序列。(是不是一下就有了方向?)
而且,所有人首次经过的节点一定是不重复的(废话,不然还叫首次经过。。。),且所有人首次经过的节点拼在一起一定是(1sim n)的所有节点(这应该也显然)。
考虑一个人当前首次经过了节点(x),接下来TA需要首次经过节点(y)。
因为在这道题中,一个人可以保持不动,而且题目也没有限制时间。所以,我们完全可以让TA傻站在那里,一直等到节点(x+1sim y-1)都被其他人首次经过。
好,然后这个人就开始往节点(y)进军了。TA怎样走才是最优的呢?当然是走最短路啦!
而此时节点(y+1sim n)依然不能行走,也就是说,TA只能经过节点(1sim y),从节点(x)走最短路到达节点(y)。
令这个最短路为(f_{x,y}),则它显然是可以用(Floyd)搞出来的。
总结一下,我们对于每个点(x),向比它更大的点(y)连一条流量为(1),费用为(f_{x,y})的边,就把这个问题转化成网络流了。
更具体的建图
刚才我们已经确定了点与点之间的连边,现在我们要进一步讨论。
显然根据先前提到的性质,每个点只能经过一次(不重复),且至少要经过一次(拼起来是(1sim n))。
按照套路,把每个点拆成入点和出点,那么就相当于从入点向出点连一条上下界均为(1)的边。
而这可以转化为,建立超级源/超级汇,然后从超级源向出点连一条流量(1)的边,从入点向超级汇连一条流量(1)的边。
显然,之前点与点的连边就要变成出点与入点的连边。
而对于节点(0)这个特殊点,我们实际上不需要把它分成两个(因为它并没有下界限制),直接从超级源向它连一条流量(k)的边就可以了。
具体实现详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 150
#define M 20000
#define INF (int)1e9
using namespace std;
int n,m,t,f[N+5][N+5];
class MinCostMaxlow//最小费用最大流
{
private:
#define E(x) ((((x)-1)^1)+1)
int ee,lnk[2*N+5];struct edge {int to,nxt,F,C;}e[2*M+5];
int lst[2*N+5],IQ[2*N+5],F[2*N+5],C[2*N+5];queue<int> q;
I bool SPFA()
{
RI i,k;for(i=0;i<=2*n+2;++i) F[i]=C[i]=INF;q.push(S),C[S]=0;
W(!q.empty()) for(i=lnk[k=q.front()],q.pop(),IQ[k]=0;i;i=e[i].nxt)
{
if(!e[i].F||C[k]+e[i].C>=C[e[i].to]) continue;
C[e[i].to]=C[k]+e[lst[e[i].to]=i].C,F[e[i].to]=min(F[k],e[i].F),
!IQ[e[i].to]&&(q.push(e[i].to),IQ[e[i].to]=1);
}return F[T]^INF;
}
public:
I void Add(CI x,CI y,CI f,CI c)
{
e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f,e[ee].C=c,
e[++ee].nxt=lnk[y],e[lnk[y]=ee].to=x,e[ee].F=0,e[ee].C=-c;
}
int S,T;I void MCMF()
{
RI x,res=0;W(SPFA())
{
res+=F[T]*C[T],x=T;
W(x^S) e[lst[x]].F-=F[T],e[E(lst[x])].F+=F[T],x=e[E(lst[x])].to;
}printf("%d",res);
}
}F;
int main()
{
RI i,j,k,x,y,z;scanf("%d%d%d",&n,&m,&t);
for(i=0;i<=n;++i) for(j=0;j<=n;++j) f[i][j]=INF;for(i=0;i<=n;++i) f[i][i]=0;//初始化
for(i=1;i<=m;++i) scanf("%d%d%d",&x,&y,&z),f[x][y]>z&&(f[x][y]=f[y][x]=z);//对于给定边修改距离
for(k=0;k<=n;++k) for(i=0;i<=n;++i) for(j=0;j<=n;++j)//Floyd
(i>k||j>k)&&f[i][k]+f[k][j]<f[i][j]&&(f[i][j]=f[i][k]+f[k][j]);//注意不能经过编号更大的点
for(i=0;i<=n;++i) for(j=i+1;j<=n;++j) F.Add(i?n+i:0,j,1,f[i][j]);//出点与入点间的连边
F.Add(F.S=2*n+1,0,t,0),F.T=2*n+2;//从超级源向0连边
for(i=1;i<=n;++i) F.Add(i,F.T,1,0),F.Add(F.S,n+i,1,0);//1~n节点和超级源汇的连边
return F.MCMF(),0;
}