[LOJ 6058]百步穿杨(最小割)

在这里插入图片描述

n , m n,m n,m 很小,再加上是图上求最值,考虑网络流。
注意到题中说弹道不能相交,说明每一个格子要么被横向穿过,要么被纵向穿过。这是具有选择性质的,考虑最小割。为了跑最小割,先不管条件,贪心的求出最大值(即每个箭塔都选自己弹道上的最大权值的靶子),然后用最小的代价割掉不合法的情况。
不合法的情况也就是一个格子既被横向穿过,又被纵向穿过,为了割掉这种情况,我们把 S S S 集合当成横向集合, T T T 集合当成纵向集合,那么一个格子不满足条件就说明他既属于 S S S 集合,又属于 T T T 集合,最小割一定会割掉其中一条边保证合法的。
考虑如何建图,对于纵向箭塔,我们让源点连向它,然后这个箭塔找到他射击轨道中最大的点权,之后在这条道路上串联,连的边权是出点减去最大点权,表示选这个点比选最大点少了多少值;对于横向箭塔,我们让它连向汇点,之后找到最大点权,也在这条路上串联,但方向是指向箭塔方向,边权是选出点比选最大点少了多少值。
建完图之后跑最小割就可以了…吗?
观察下面这组数据:

5 4
..V.
..11
>1..
...A
.>.1

肉眼可见答案是 4 4 4 ,但按照刚才的思路会发现最后跑出的答案是 3 3 3 ,为什么呢?
手推一下就会发现,因为在第三列割的时候,会残留下下面的路径,而这个路径又与第四列以及第五行的路径相连,导致凭空多出了一条增广路径。在之前的思路里,因为题中保证箭塔之间互不在攻击范围内,所以我们认为一旦割掉了这条纵向路径中的任意一条边,其他边因为没有源点,所以放着不管也可以,但是我们发现因为图中既存在纵向路径又存在横向路径,所以剩余的边可以通过其他路径来继续发挥他不该发挥的作用,怎么办呢?
我们考虑把每个点拆成横纵两个点,有关横向的路径连向横点,有关纵向的路径连向纵点,纵点再向横点连一条正无穷的边,保证不会被割。这样做完我们会发现,所有路径不会再出现“纵->横->纵”的情况了,这样纵与纵之间就不会产生影响了,就可以放心的跑最小割啦。

#include<bits/stdc++.h>
using namespace std;
int n,m,head[6000],tot=-1,sum,d[6000],cur[6000],S,T;
char a[60][60];
struct node
{
   
	int to,nex,v;
} edge[600000];
void add(int x,int y,int z)
{
   
	edge[++tot].nex=head[x];
	edge[tot].to=y;
	edge[tot].v=z;
	head[x]=tot;
}
inline int id(int x,int y,bool z)
{
   
	return (z==1)*n*m+(x-1)*m+y;
}
bool bfs()
{
   
	queue<int> q;
	q.push(S);
	memset(d,-1,sizeof d);
	d[S]=0,cur[S]=head[S];
	while(!q.empty())
	{
   
		int x=q.front();
		q.pop();
		for(int i=head[x];~i;i=edge[i].nex)
		{
   
			if(d[edge[i].to]==-1&&edge[i].v)
			{
   
				d[edge[i].to]=d[x]+1;
				cur[edge[i].to]=head[edge[i].to];
				if(edge[i].to==T)
				return 1;
				q.push(edge[i].to);
			}
		}
	}
	return 0;
}
int find(int x,int limit)
{
   
	if(x==T)
	return limit;
	int flow=0;
	for(int i=cur[x];~i&&flow<limit;i=edge[i].nex)
	{
   
		cur[x]=i;
		if(d[edge[i].to]==d[x]+1&&edge[i].v
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值