题目
题目链接:https://www.luogu.com.cn/problem/P4542
皮卡丘被火箭队用邪恶的计谋抢走了!这三个坏家伙还给小智留下了赤果果的挑衅!为了皮卡丘,也为了正义,小智和他的朋友们义不容辞的踏上了营救皮卡丘的道路。
火箭队一共有(N)个据点,据点之间存在(M)条双向道路。据点分别从(1)到(N)标号。小智一行(K)人从真新镇出发,营救被困在(N)号据点的皮卡丘。为了方便起见,我们将真新镇视为(0)号据点,一开始(K)个人都在(0)号点。
由于火箭队的重重布防,要想摧毁(K)号据点,必须按照顺序先摧毁(1)到(K-1)号据点,并且,如果(K-1)号据点没有被摧毁,由于防御的连锁性,小智一行任何一个人进入据点(K),都会被发现,并产生严重后果。因此,在(K-1)号据点被摧毁之前,任何人是不能够经过(K)号据点的。
为了简化问题,我们忽略战斗环节,小智一行任何一个人经过(K)号据点即认为(K)号据点被摧毁。被摧毁的据点依然是可以被经过的。
(K)个人是可以分头行动的,只要有任何一个人在(K-1)号据点被摧毁之后,经过(K)号据点,(K)号据点就被摧毁了。显然的,只要(N)号据点被摧毁,皮卡丘就得救了。
野外的道路是不安全的,因此小智一行希望在摧毁(N)号据点救出皮卡丘的同时,使得(K)个人所经过的道路的长度总和最少。
请你帮助小智设计一个最佳的营救方案吧!
(nleq 150,mleq 20000,kleq 10,L_ileq 10000)。
思路
我们可以把题目看作是用 (k) 条路径覆盖 (n) 个点,且要求新的一个点 (i) 被经过时,点 (i-1) 必须有人经过过的最短路径长度和。
那么我们只需要安排每一个人走哪些点(从小到大),每次只能走不超过当前节点编号的点到达下一个要到的点。这样我们一定可以调整所有人走的顺序满足要求。
考虑先用 Floyd 求出任意两个点 (i,j) 之间,只经过编号不超过 (max(i,j)) 的点的最短路。
然后考虑费用流。源点向 (0) 连一条 ((k,0)) 的边,把所有点拆成 (i_1,i_2),从 (i_1) 向 (i_2) 连 ((1,-infty),(+infty,0)) 的边。这样我们就可以保证每一个点至少会被经过一次,而连第二条边是因为经过了一次后可以再经过任意次。最终答案加上 (n imes infty) 即可。
然后对于 (i<j) 且 (i,j) 之间有只经过编号不超过 (j) 的路径,我们从 (i_2) 向 (j_1) 连边 ((+infty,dis_{i}{j}))。最后从所有 (i_2) 向汇点连 ((1,0)) 的边。
不难发现这种模型是符合题意的。跑费用流即可。因为只有编号小的会连向大的,所以不用担心有负环。
时间复杂度 (O(nm^2))。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500,M=60010,Inf=1e9;
int n,m,t,S,T,tot=1,head[N],pre[N],dis1[N][N];
ll cost,dis[N];
bool vis[N];
struct edge
{
int next,to,flow,cost;
}e[M];
void add(int from,int to,int flow,int cost)
{
e[++tot]=(edge){head[from],to,flow,cost};
head[from]=tot;
swap(from,to);
e[++tot]=(edge){head[from],to,0,-cost};
head[from]=tot;
}
bool spfa()
{
memset(vis,0,sizeof(vis));
memset(dis,0x3f3f3f3f,sizeof(dis));
queue<int> q;
q.push(S); dis[S]=0;
while (q.size())
{
int u=q.front(); q.pop();
vis[u]=0;
for (int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
if (e[i].flow && dis[v]>dis[u]+e[i].cost)
{
dis[v]=dis[u]+e[i].cost;
pre[v]=i;
if (!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
return dis[T]<1e18;
}
void addflow()
{
int minf=Inf;
for (int i=T;i!=S;i=e[pre[i]^1].to)
minf=min(minf,e[pre[i]].flow);
for (int i=T;i!=S;i=e[pre[i]^1].to)
{
e[pre[i]].flow-=minf;
e[pre[i]^1].flow+=minf;
}
cost+=1LL*minf*dis[T];
}
void MCMF()
{
while (spfa())
addflow();
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d%d",&n,&m,&t);
memset(dis1,0x3f3f3f3f,sizeof(dis1));
for (int i=1,x,y,z;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
dis1[x][y]=min(dis1[x][y],z);
dis1[y][x]=min(dis1[y][x],z);
}
for (int k=0;k<=n;k++)
for (int i=k;i<=n;i++)
for (int j=k;j<=n;j++)
if (i!=j && j!=k && i!=k)
dis1[i][j]=min(dis1[i][j],dis1[i][k]+dis1[k][j]);
S=N-1; T=N-2;
add(S,0,t,0);
for (int i=0;i<=n;i++)
{
add(i,i+n+1,1,-Inf); add(i,i+n+1,Inf,0);
add(i+n+1,T,1,0);
for (int j=i+1;j<=n;j++)
if (dis1[i][j]<Inf) add(i+n+1,j,Inf,dis1[i][j]);
}
MCMF();
printf("%lld",cost+1LL*(n+1)*Inf);
return 0;
}