【洛谷P4542】营救皮卡丘

题目

题目链接: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\)个人所经过的道路的长度总和最少。
请你帮助小智设计一个最佳的营救方案吧!
\(n\leq 150,m\leq 20000,k\leq 10,L_i\leq 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\times \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;
}
posted @ 2021-02-21 19:54  stoorz  阅读(44)  评论(0编辑  收藏  举报