CF1615H Reindeer Games

本文介绍了一种巧妙的思路,通过将问题中的aaa抽象为点,并利用二分查找技巧,结合网络流模型来划分这些点。通过创建源点和汇点,根据点的初始值与中值比较,建立网络流模型并运行Dinic算法。最后,通过残量网络的特性确定点集的划分,从而求解问题。
摘要由CSDN通过智能技术生成

题目

题目

思路

这题思路还是很妙的,考虑把所有的 a a a抽象成n个点,然后进行二分答案。
设现在这个点集为 V V V,我们本质上在二分这个点集所有点的最终取值。
那么接下来,我们要解决的问题就是,如何对点集进行二分。
可以这样做,新建一个源点和一个汇点,然后开始建模。
对于点集中每个点的初始值,我们将其和mid比较,大的与t连容量为1的边,小的与s连容量为1的边。
原图的约束条件在建模时连容量inf的边。
接下来跑一次网络流,就会得到一个残量网络。
现在研究一下这个残量网络的性质。
如果2个非源汇点之间有边,说明该边联通的2点无法在一个大于mid,一个小于等于mid的情况下满足约束。
如果一个点对源点而言可达,必定与汇点不可达,就说明这个点的最优答案比mid更小,反之更大。
因此我们就完成了点集划分,本题完结。
code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
int n,m,s,t,x,y,tot=2,head[2001],cur[2001],dep[2001],l,r,u[2001],r2;
long long w,ans[2001],aa[2001];
long long mn(long long x,long long y)
{
	return (x>y?y:x);
}
struct f{
	int to,net;
	long long w;
} a[1000011];
void add(int x,int y,long long w)
{
	a[tot].to=y,a[tot].w=w,a[tot].net=head[x],head[x]=tot++;
	return;
}
bool bfs()
{
	memset(dep,-1,sizeof(dep));
	queue<int> u;
	u.push(s);
	dep[s]=0;
	while (u.size())
	{
		int x=u.front();
		u.pop();
		for (int j=head[x];j;j=a[j].net)
		{
			if (a[j].w>0&&dep[a[j].to]==-1)
			{
				u.push(a[j].to);
				dep[a[j].to]=dep[x]+1;
			}
		}
	}
	return dep[t]!=-1;
}
long long dfs(int d,long long in)
{
	if (d==t) return in;
	long long out=0;
	int uw=dep[d]+1;
	for (int &j=cur[d];j&&in;j=a[j].net)
	{
		if (a[j].w<=0||dep[a[j].to]!=uw) continue;
		long long s=dfs(a[j].to,mn(in,a[j].w));
		out+=s,in-=s;
		a[j].w-=s,a[j^1].w+=s;
	}
	if (out==0)
	{
		dep[d]=0;
	}
	return out;
}
void Dinic()
{
	while (bfs())
	{
		memcpy(cur,head,sizeof(cur));
		while (1)
		{
			if (!dfs(s,1e9)) break;
		}
	}
	return;
}
int newid[2000];
vector<int> edg[2000];
void solve(vector<int> V,long long l,long long r)//The value of each point in point set V is between L and r
{
	if (l==r)
	{
		for (int i=0;i<V.size();i++) ans[V[i]]=l;
		return;
	}
	if (!V.size()) return;
	for (int i=0;i<V.size();i++) newid[V[i]]=i+1;
	n=V.size()+1,s=0,t=n,tot=2;
	for (int i=0;i<=n;i++) head[i]=0;
	for (int i=0;i<V.size();i++)
	{
		int x=V[i];
		for (int j=0;j<edg[x].size();j++)
		{
			if (!newid[edg[x][j]]) continue;
			add(i+1,newid[edg[x][j]],1e9);
			add(newid[edg[x][j]],i+1,0);
		}
	}//remake 
	long long mid=l+r>>1;
	for (int i=0;i<V.size();i++)
	{
		int u=V[i];
		if (mid<aa[u]) add(s,i+1,1),add(i+1,s,0);
		else add(i+1,t,1),add(t,i+1,0);
	}
	Dinic();
	vector<int> LV,RV;
	for (int i=0;i<V.size();i++)
	{
		if (dep[i+1]!=-1) RV.push_back(V[i]);
		else LV.push_back(V[i]);
	}//divide
    for (int i=0;i<V.size();i++) newid[V[i]]=0;
	solve(LV,l,mid);
	solve(RV,mid+1,r);
	return;
}
int main()
{
	int n,m;
	cin>>n>>m;
	for (int i=1;i<=n;i++) scanf("%lld",&aa[i]);
	for (int i=0;i<m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		edg[x].push_back(y);
	}
	vector<int> U;
	for (int i=1;i<=n;i++) U.push_back(i);
	solve(U,1,1e9);
	for (int i=1;i<=n;i++) printf("%lld ",ans[i]); 
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值