【XSY3396】地铁(平面图与对偶图,最小割)

题意:给一张平面图,满足这张平面图的对偶图是一棵树,有若干限制,形如 “若经过点 x x x 则必须要经过点 y y y”,求 1 ∼ n 1\sim n 1n 的最短路。

由于平面图与其对偶图互为对偶,所以平面图最短路等于其对偶图上的最小割。

在这里插入图片描述

注意这里的最小割和普通的最小割有点不一样,这里要求任意一条 S → T S\to T ST 的路径上都只有一条割边(不然就不能对应平面图上的一条最短路),于是我们在求最小割时要给每条边加上容量为 INF 的反边。

例:

在这里插入图片描述

假设对偶图左图长这样(尽管这不是一棵树),那么我们的最小割算法会把 A A A 划分到 T T T 集, B B B 划分到 S S S 集,那么这样割掉的边为 ( S , A ) (S,A) (S,A) ( B , T ) (B,T) (B,T),此时得到最小割 2 2 2,如中图。

但你会发现这样就有一条从 S → T S\to T ST 的路径上有两条边被割掉了(如右图中蓝色路径),是不合法的。

所以我们需要对每一条边都连一条容量为 INF 的反边,以确保任意一条 S → T S\to T ST 的路径上都只有一条割边(否则就必然会割掉某条 INF 反边,显然最小割不是这个)。

而对于题目的限制,我们先考虑 “平面图上经过了点 i i i” 在对偶图上对应着什么:

在这里插入图片描述

如图,设以 i i i 为右端点的最长的线段所对应的节点为 m a x l i maxl_i maxli,以 i i i 为左端点的最长的线段所对应的节点为 m a x r i maxr_i maxri,容易证明 m a x l i maxl_i maxli m a x r i maxr_i maxri 的父亲为同一个点 l c a i lca_i lcai

那么 “平面图上经过了点 i i i” 等价于如图中蓝色的两条链上有任意一条边被割,发现这等价于 l c a lca lca 被分到 S S S 集:因为蓝色的两条链上有任意一条边被割一定使得 l c a lca lca 被分到 S S S 集,而 l c a lca lca 被分到 S S S 集一定使得蓝色的两条链上某一条边被割(因为不可能割叶子到 T T T 的 INF 边)。

那么对于题目中的一条限制 a , b a,b a,b,就等价于 l c a a lca_a lcaa l c a b lca_b lcab 必须同在 S S S 集或同在 T T T 集。这很好处理,直接在 l c a a lca_a lcaa l c a b lca_b lcab 之间连一条双向 INF 边即可。

建树有些细节,注意要特判哪些点是压根不可能经过的。

代码如下:

#include<bits/stdc++.h>

#define N 1010
#define INF 0x7fffffff

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

struct Edge
{
	int u,v,w;
}e[N];

bool operator < (Edge a,Edge b)
{
	return (a.v-a.u)<(b.v-b.u);
}

const int V=N,E=N*10;

int n,m,k,S,T;
int maxr[N],fa[N];
int cnt=1,head[V],cur[V],to[E],nxt[E],c[E];
int num[V];

void adde(int u,int v,int ci)
{
	to[++cnt]=v;
	c[cnt]=ci;
	nxt[cnt]=head[u];
	head[u]=cnt;
	
	to[++cnt]=u;
	c[cnt]=0;
	nxt[cnt]=head[v];
	head[v]=cnt;
}

queue<int>q;

bool bfs()
{
	memcpy(cur,head,sizeof(cur));
	memset(num,-1,sizeof(num));
	q.push(S);
	num[S]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(c[i]&&num[v]==-1)
			{
				num[v]=num[u]+1;
				q.push(v);
			}
		}
	}
	return num[T]!=-1;
}

int dfs(int u,int minflow)
{
	if(!minflow||u==T) return minflow;
	int preflow=0,nowflow;
	for(int i=cur[u];i;i=nxt[i])
	{
		cur[u]=i;
		int v=to[i];
		if(num[v]==num[u]+1&&(nowflow=dfs(v,min(minflow-preflow,c[i]))))
		{
			preflow+=nowflow;
			c[i]-=nowflow;
			c[i^1]+=nowflow;
			if(!(minflow-preflow)) break;
		}
	}
	return preflow;
}

int dinic()
{
	int maxflow=0;
	while(bfs())
		maxflow+=dfs(S,INF);
	return maxflow;
}

bool used[N];

int main()
{
	n=read(),m=read(),k=read();
	for(int i=1;i<=m;i++)
		e[i].u=read(),e[i].v=read(),e[i].w=read();
	sort(e+1,e+m+1);
	e[++m]=(Edge){1,n,114514};
	S=m,T=m+1;
	for(int i=1;i<=m;i++)
	{
		int l=e[i].u,r=e[i].v;
		bool flag=1;
		for(int nl=l;nl<r;)
		{
			if(!maxr[nl])
			{
				flag=0;
				break;
			}
			nl=e[maxr[nl]].v;
			assert(nl<=r);
		}
		if(flag)
		{
			for(int nl=l;nl<r;)
			{
				adde(i,maxr[nl],e[maxr[nl]].w);
				adde(maxr[nl],i,INF);
				fa[maxr[nl]]=i;
				nl=e[maxr[nl]].v;
				assert(nl<=r);
			}
		}
		else adde(i,T,INF);
		maxr[l]=i;
	}
	used[m]=1;
	for(int i=m-1;i>=1;i--) used[i]=used[fa[i]];
	for(int i=1;i<=k;i++)
	{
		int a=read(),b=read();
		int aa=fa[maxr[a]],bb=fa[maxr[b]];
		if(!used[aa]&&!used[bb]) continue;
		else if(used[aa]&&used[bb])
		{
			adde(aa,bb,INF);
			adde(bb,aa,INF);
		}
		else if(used[aa]) adde(aa,T,INF);
		else if(used[bb]) adde(bb,T,INF);
	}
	int ans=dinic();
	if(ans==INF) puts("-1");
	else printf("%d\n",ans);
	return 0;
}
/*
4 4 1
1 2 10
2 3 100
3 4 100
2 4 10
2 3
*/
/*
11 15 1
1 2 2
2 3 2
3 4 2
4 5 2
5 6 2
6 7 2
7 8 2
8 9 2
9 10 2
10 11 2
1 3 5
3 5 3
5 7 5
7 9 3
9 11 5
5 3
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值