【POI】POI2005 KOS-Dicing | 二分、网络流

110 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情

【洛谷】P3425 [POI2005]KOS-Dicing | 二分、网络流

题目链接

P3425 [POI2005]KOS-Dicing - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目大意

Dicing 是一个两人玩的游戏,这个游戏在 Byteotia 非常流行,甚至人们专门成立了这个游戏的一个俱乐部。俱乐部的人时常在一起玩这个游戏然后评选出玩得最好的人。现在有一个非常不走运的家伙,他想成为那个玩的最好的人。

他现在知道了所有比赛的安排,第 ii 次比赛将在玩家 xix_i 和玩家 yiy_i 之间展开。他想知道,在 mm 场比赛全部完成后,胜利场次最多的玩家最少胜利多少次。

并构造一组每场游戏的输赢方案,使得胜利场次最多的玩家成立场次最少。

思路

对于第一问,直接求很难求,我们倒过来想。即:

是否存在一种每场游戏的输赢方案,使得胜利场次最多的玩家胜利了不超过 kk 次。

这个东西显然具有二分性,即 kk 越大该条件越容易满足,若 kk 合法则 k+1k+1 必定合法。所以如果我们可以较为快速的 check 上面那个东西,就可以二分求解 kk。怎么 check 呢?

可以使用网络流。每次游戏、每个玩家都视为网络中的点。我们从源点向每个游戏连容量为 11 的边,每个游戏向本次游戏参与的两个玩家分别连容量为 11 的边,每个玩家向汇点连容量为 kk 的边。这样每一个到达汇点的流量就描述了某一场游戏中某个玩家取得了胜利。若最大流为 mm 则说明存在合法的构造方案。

ii 次比赛将在玩家 xix_i 和玩家 yiy_i 之间展开,则:

  • ii 次游戏指向玩家 xix_i 的边容量为 00,说明 xix_i 取得了第 ii 场游戏的胜利,输出 11
  • ii 次游戏指向玩家 xix_i 的边容量为 11,说明 yiy_i 取得了第 ii 场游戏的胜利,输出 00

注意我们输出方案前,最后一次 check 可能并不是对最终输出的答案进行的(与我的二分写法有关),所以需要再对最终的答案进行一次 check,来构造方案。

代码

#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
using LL=long long;
const int N=20005;
struct asdf{
	int to,nxt,val;
}e[N*10];
int que[N],dep[N],fir[N],tot=1,h,t;
int n,m,st,ed;
int eid[N];
void add(int x,int y,int z)
{
	e[++tot].nxt=fir[x];
	fir[x]=tot;
	e[tot].to=y;
	e[tot].val=z;
}

bool bfs()
{
	for (int i=1;i<=ed;++i) dep[i]=0;
	h=t=0;
	que[++t]=st;
	dep[st]=1;
	while (h!=t)
		for (int i=fir[que[++h]];i;i=e[i].nxt)
		{
			if (e[i].val==0||dep[e[i].to]) continue;
			dep[e[i].to]=dep[que[h]]+1;
			que[++t]=e[i].to;
		}
	return dep[ed];
}

int dfs(int u,int flow)
{
	if (u==ed||flow==0) return flow;
	int rest=flow,x;
	for (int i=fir[u];i&&rest;i=e[i].nxt)
	{
		if (e[i].val==0||dep[e[i].to]!=dep[u]+1) continue;
		x=dfs(e[i].to,min(e[i].val,rest));
		if (x<min(e[i].val,rest)) dep[e[i].to]=0;
		rest-=x;
		e[i].val-=x;
		e[i^1].val+=x;
	}
	return flow-rest;
}

void init(int val)
{
	for (int i=fir[st];i;i=e[i].nxt) e[i].val=1;
	for (int i=1;i<=m;++i)
		for (int j=fir[i];j;j=e[j].nxt) 
			if (e[j].to==st) e[j].val=0;
			else e[j].val=1;
	for (int i=m+1;i<=m+n;++i)
		for (int j=fir[i];j;j=e[j].nxt)
			if (e[j].to==ed) e[j].val=val;
			else e[j].val=0;
	for (int i=fir[ed];i;i=e[i].nxt) e[i].val=0;
}

bool check(int val)
{
	init(val);
	int ans=0;
	while (bfs())
		ans+=dfs(st,m);
	return ans==m;
}

int main()
{
	scanf("%d%d",&n,&m);
	st=n+m+1;
	ed=st+1;
	for (int i=1;i<=m;++i) add(st,i,0),add(i,st,0);
	for (int x,y,i=1;i<=m;++i)
	{
		scanf("%d%d",&x,&y);
		add(i,x+m,0);
		add(x+m,i,0);
		eid[i]=tot;
		add(i,y+m,0);
		add(y+m,i,0);
	}
	for (int i=m+1;i<=m+n;++i) add(i,ed,0),add(ed,i,0);
	int l=1,r=m,mid;
	while (l!=r)
	{
		mid=l+r>>1;
		if (check(mid)) r=mid;
		else l=mid+1;
	}
	check(l);
	printf("%d\n",l);
	for (int i=1;i<=m;++i) printf("%d\n",e[fir[i]].val);
	return 0;
}