洛谷4382 BZOJ5251 2018八省联考 劈配 最大流 二分答案

17 篇文章 0 订阅
14 篇文章 0 订阅

题目链接

题意:
题意比较复杂,我尽可能的用简练的语言描述清楚。有 n n n个学生和 m m m个导师,每个导师最多收 b i b_i bi个学生。每个学生会有一个志愿表,表中有 m m m档志愿,写着第 i i i个志愿有哪些老师。一个志愿可以有多个老师,但是不能超过 c c c个(保证输入的时候不超过 c c c个),一个学生只能把同一个老师写进志愿一次,也可以不把某个老师写入志愿,一档志愿也可以没有任何老师,甚至整个志愿表都可以不选任何一个老师。我们规定编号为 i i i的选手的排名是 i i i,我们要找一个最优的录取方案,最优的含义是对于第 i i i个选手,在前 i − 1 i-1 i1个选手录取到可能的最优志愿的前提下,第 i i i个选手也录取到最优的志愿。有两问,第一问是让你求在最优录取情况下每个选手会被第几志愿录取(或者根本无法被录取),第二问是每个选手有一个期望被第几志愿之前的志愿录取,在其他选手相对排名不变的情况下,每个选手至少提高多少名才能达到自己的期望志愿之前。多组数据。数据组数 &lt; = 5 &lt;=5 <=5 m , n &lt; = 200 m,n&lt;=200 m,n<=200

题解:
2018年8省联考的Day2T1,当场的时候会50分但是忘了什么原因爆零了,好像是写错了什么东西,导致只对了第一问。现在再来做,自己yy了半天,发现只能过70分,后来看了一眼liuzhangfeiabc神仙的题解,优化了一下,就过了。

这种题不难发现可能是个网络流题,于是我们去尝试建模。由于有两问,我们可考虑一问一问来做,我们先来考虑第一问。首先的一个想法是,看起来像是个最大流题目,而不是个费用流或最小割。那么用最大流的话,如果所有的选手的边都加进去,那么你只能检验一个是不是每个人都能被某个志愿录取之类的事,所以应该要考虑动态加边,用最大流来检验合法。我们源点向每个学生连 1 1 1的流量,每个老师向汇点连流量为 b i b_i bi的边,剩下的就是学生与导师之间的连边了。我们肯定是从第一个人开始连,那么对于同一个人,肯定也是先连他第一个志愿的那些老师,然后再连第二志愿的老师,直到合法了,就不用再连了,就可以连下一个选手的边了。这样我们在动态加边跑最大流的过程中就能确定每个人在最优录取情况下会被第几志愿录取了。

然后是第二问,这个第二问一开始有好几种想法,写出来发现都错了。然后想了一下,发现可以二分答案再跑最大流检验就可以了。这样就能算出每个人最少提高到第几名才行,这样与原来的名次相减就可以得到至少提高多少名了。

但是这个样子就只能过70分,有3个点会T掉。我们考虑优化一下,其实我们可以发现,对于一个选手,他与导师之间连的有用的边只有那些最终被录取的志愿的那些老师的边。那么我在做第一问的时候,我们可以在没被录取的时候,把那些之前连的边删掉。在第二问的时候,二分检验的时候只连最终被录取的志愿的那些老师的边。这样就可以通过此题。

这个复杂度看起来还是有点乱的,我们来尝试分析一下。我们对于一个选手,只保留了被录取的那个志愿所有导师的边,也就是不超过 c c c条,这样图上的边数最多是 n ∗ c n*c nc量级的。这个图是一个二分图,于是跑dinic是带根号的。于是对于第一问,我们要枚举每个选手和每个志愿来跑,复杂度上限是 O ( n ∗ m ∗ n ∗ c ∗ n ) O(n*m*n*c*\sqrt{n}) O(nmncn ),但是那个 n ∗ c n*c nc是远远跑不满的。对于第二问,我们对于每一个人要二分答案,然后加边检验,复杂度上限是 O ( n ∗ l o g n ∗ n ∗ c ∗ n ) O(n*logn*n*c*\sqrt{n}) O(nlognncn )。于是是可以通过这道题的。

update:无敌的马队告诉我们,第一问其实可以单路增广,那么复杂度其实应该是不带那个根号的。

代码:

#include <bits/stdc++.h>
using namespace std;

int T,c,n,m,b[210],s[210],hed[410],cnt,ans1[410],ans2[410];
int st=402,ed=403,q[810],h,t,dep[410],res,hed1[410];
vector<int> v[210][210];
struct node
{
	int from,to,next,c; 
}a[400010];
inline int read()
{
	int x=0;
	char s=getchar();
	while(s>'9'||s<'0')
	s=getchar();
	while(s>='0'&&s<='9')
	{
		x=x*10+s-'0';
		s=getchar();
	}
	return x;
}
inline void add(int from,int to,int c)
{
	a[++cnt].from=from;
	a[cnt].to=to;
	a[cnt].c=c;
	a[cnt].next=hed[from];
	hed[from]=cnt;
	a[++cnt].from=to;
	a[cnt].to=from;
	a[cnt].c=0;
	a[cnt].next=hed[to];
	hed[to]=cnt;
}
inline int bfs()
{
	memset(dep,0,sizeof(dep));
	dep[st]=1;
	h=1;
	t=1;
	q[h]=st;
	while(h<=t)
	{
		int x=q[h];
		for(int i=hed[x];i;i=a[i].next)
		{
			int y=a[i].to;
			if(dep[y]==0&&a[i].c)
			{
				dep[y]=dep[x]+1;
				q[++t]=y;
			}
		}
		++h;
	}
	if(dep[ed]>0)
	return 1;
	else
	return 0;
} 
inline int flow(int x,int f)
{
	if(x==ed)
	return f;
	int s=0,t;
	for(int i=hed[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(dep[y]==dep[x]+1&&a[i].c&&f>s)
		{
			s+=(t=flow(y,min(a[i].c,f-s)));
			a[i].c-=t;
			a[i^1].c+=t;
		}
	}
	if(s==0)
	dep[x]=0;
	return s;
}
inline int check(int mid,int x)
{
	memset(hed,0,sizeof(hed));
	for(int i=1;i<=cnt;++i)
	{
		a[i].from=0;
		a[i].to=0;
		a[i].next=0;
		a[i].c=0;
	}
	cnt=1;
	for(int i=1;i<=m;++i)
	add(n+i,ed,b[i]);
	for(int i=1;i<mid;++i)
	{
		add(st,i,1);
		for(int j=ans1[i];j<=ans1[i];++j)
		{
			int sz=v[i][j].size();
			for(int k=0;k<sz;++k)
			add(i,n+v[i][j][k],1);								
		}
	}
	res=0;
	while(bfs())
	res+=flow(st,2e9);
	res=0;
	add(st,x,1);
	for(int j=1;j<=s[x];++j)
	{
		int sz=v[x][j].size();
		for(int k=0;k<sz;++k)
		add(x,n+v[x][j][k],1);								
		res=0;
		while(bfs())
		res+=flow(st,2e9);
		if(res)
		break;
	}
	if(res)
	return 1;
	else
	return 0;
}
int main()
{
	T=read();
	c=read();
	while(T--)
	{
		memset(hed,0,sizeof(hed));
		for(int i=1;i<=cnt;++i)
		{
			a[i].from=0;
			a[i].to=0;
			a[i].next=0;
			a[i].c=0;
		}
		cnt=1;
		n=read();
		m=read();
		for(int i=1;i<=m;++i)
		b[i]=read();
		for(int i=1;i<=n;++i)
		{
			for(int j=1;j<=m;++j)
			{
				int x=read();
				v[i][x].push_back(j);
			}
		}
		for(int i=1;i<=n;++i)
		s[i]=read();
		for(int i=1;i<=m;++i)
		add(n+i,ed,b[i]);		
		for(int i=1;i<=n;++i)
		{
			add(st,i,1);
			int pd=0;
			for(int j=1;j<=m;++j)
			{
				int sz=v[i][j].size(),gg=cnt;
				for(int k=1;k<=ed;++k)
				hed1[k]=hed[k];
				for(int k=0;k<sz;++k)
				add(i,n+v[i][j][k],1);								
				res=0;
				while(bfs())
				res+=flow(st,2e9);
				if(res)
				{
					pd=1;
					res=j;
					break;
				}
				else
				{
					for(int k=1;k<=ed;++k)
					hed[k]=hed1[k];
					for(int k=gg+1;k<=cnt;++k)
					{
						a[k].from=0;
						a[k].to=0;
						a[k].next=0;
						a[k].c=0;
					}
					cnt=gg;
				}				
			}
			if(pd==1)
			ans1[i]=res;
			else
			ans1[i]=m+1;
		}
		for(int i=1;i<=n;++i)
		{
			if(s[i]>=ans1[i])
			{
				ans2[i]=0;
				continue;
			}
			int l=1,r=i-1,mid,ji=0;
			while(l<=r)
			{
				mid=(l+r)>>1;
				if(check(mid,i))
				{
					ji=mid;
					l=mid+1;
				}
				else
				r=mid-1;
			}
			ans2[i]=i-ji;
		}
		for(int i=1;i<=n;++i)
		printf("%d ",ans1[i]);
		printf("\n");
		for(int i=1;i<=n;++i)
		printf("%d ",ans2[i]);
		printf("\n");
		for(int i=1;i<=n;++i)
		{
			for(int j=1;j<=m;++j)
			v[i][j].clear();
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值