【2023北京集训1】apers 做题记录

有一个 nn 个点 mm 条边有向图,每个点有一个炸毁代价 cic_i

给定 kk、起点 ss 和终点 tt,你需要花费最小的代价使得 sstt 至少要经过 kk 次被炸毁的点。

保证 sstt 联通,无解输出 1-1

1n2001\le n\le 2001m5001\le m\le 5001k51\le k\le 5

考虑最优解中 sstt 经过最少炸毁点的路径上的炸毁点序列 bb。对于一个被炸毁的点 uu,设 ssuu 最少经过 aua_u 个被炸毁的点,那么若 uubb 中出现就一定是第 aua_u 个出现,因为走去别的炸毁点再走回来一定不优。

那么建 kk 层图,第 ii 层的点 uu 拆成入点 ui,0u_{i,0} 和出点 ui,1u_{i,1}

对于一个点 uu

  • 在第 ii 层连边 (ui,0,ui,1,cu)(u_{i,0},u_{i,1},c_u)
  • 在第 ii 层连边 (ui,0,ui+1,1,inf)(u_{i,0},u_{i+1,1},\inf)

对于原图的一条边 uvu\to v

  • 在第 ii 层连边 (ui,1,vi,0,inf)(u_{i,1},v_{i,0},\inf)

s1,0s_{1,0}tk,1t_{k,1} 的最小割即可。

具体的,若边权为 cuc_u 的边未被割掉则可以在同层走,否则一定要向上走一层。而根据前面的结论,同一个点的 cuc_u 边只可能被割掉一条,所以是对的。

参考代码:

#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>

using namespace std;

const int S=10005,MS=500005;
const int inf=1e8;

int n,m,k;
int st,ed;
vector<int> g[S];
int s,t;
int esum=1,to[MS],c[MS],nxt[MS],h[S];
int dep[S],cur[S];

inline void init(int ns,int nt)
{
	for(int i=s;i<=t;i++) h[i]=0;
	s=ns,t=nt;
	esum=1;
}

inline void added(int x,int y,int w)
{
	to[++esum]=y;
	c[esum]=w;
	nxt[esum]=h[x];
	h[x]=esum;
}

inline void add(int x,int y,int w)
{
	added(x,y,w),added(y,x,0);
}

inline bool bfs()
{
	for(int i=s;i<=t;i++) dep[i]=0,cur[i]=h[i];
	queue<int> q;
	dep[s]=1;
	q.push(s);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		if(u==t) return true;
		for(int i=h[u];i;i=nxt[i])
		{
			int v=to[i],w=c[i];
			if(w>0&&dep[v]==0)
			{
				dep[v]=dep[u]+1;
				q.push(v);
			}
		}
	}
	return false;
}

int dfs(int u,int w)
{
	if(u==t) return w;
	int sum=0;
	for(int &i=cur[u];i;i=nxt[i])
	{
		if(c[i]==0) continue;
		int v=to[i];
		if(dep[v]!=dep[u]+1) continue;
		int r=dfs(v,min(c[i],w));
		c[i]-=r;
		c[i^1]+=r;
		w-=r;
		sum+=r;
		if(w==0) break;
	}
	if(sum==0) dep[u]=0;
	return sum;
}

inline int dinic()
{
	int res=0;
	while(bfs()) res+=dfs(s,inf);
	return res;
}

inline int id(int tp,int i,int t)
{
	return (tp-1)*n*2+(i-1)*2+t;
}

int main()
{
	freopen("apers.in","r",stdin);
	freopen("apers.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);
	scanf("%d%d",&st,&ed);
	init(0,k*n*2+1);
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		for(int j=1;j<=k;j++)
		{
			int u=id(j,i,1),v=id(j,i,2);
			add(u,v,x);
		}
		for(int j=1;j<=k-1;j++)
		{
			int u=id(j,i,1),v=id(j+1,i,2);
			add(u,v,inf);
		}
	}
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		g[x].push_back(y); 
		for(int j=1;j<=k;j++)
		{
			int u=id(j,x,2),v=id(j,y,1);
			add(u,v,inf);
		}
	}
	add(s,id(1,st,1),inf);
	add(id(k,ed,2),t,inf);
	queue<int> q;
	dep[st]=1;
	q.push(st);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		if(u==ed)
		{
			if(dep[u]<k) return puts("-1"),0;
			else break;
		}
		for(int v:g[u])
		{
			if(dep[v]==0)
			{
				dep[v]=dep[u]+1;
				q.push(v);
			}
		}
	}
	printf("%d\n",dinic());
	return 0;
}