[八省联考2018]劈配【动态加边/删边 网络流】【二分答案】

4 篇文章 0 订阅
1 篇文章 0 订阅

传送门

md神题,,

大致题意:

n个人,有m个志愿档,有m个老师。每个老师有最大限额学生。每个学生的每个志愿档可以指定0~c个老师。

从第一个学生开始,选择合理的,最高志愿。

对于第i个人来说,目标仅仅是指1~i个人全都被可能的最高志愿档次录取,跟后面的人无关。

原题是这样说的:如果一种方案满足“前 n 名的录取结果最优”,那么我们可以简称这种方案是最优的

两个子问题。

子问题1:按照志愿录取规则,给出每个人得到的志愿档。

子问题2:给定每个人的期望志愿档,求他的rank至少提升多少,满足他的期望。

 

很明显这是个网络流。

建图思想:首先源点向每个人连流量为1的边,每个老师向汇点连流量为上限学生的边。

但是这个按照rank和志愿从高到低录取的事情就很是令人头秃。我一开始的想法是把邻接表次序反向,但发现复杂度爆了,然后GG。

正解是支持加边和删边,每次在前缀的残量网络上继续增广一次的最大流算法。为什么这是对的呢?

因为上一个人留下的前缀残量网络,意义正是rank靠前的学生选择后的状态。

只要把跟我相关的志愿,从高到低依次加入,每加入完一次,就看是否可以增广。可以说明这个档次就是我的最高档次。break退出。

因为数据很小,只有200,所以我们大可以将每个人所对应的残量网络全部记录下来。空间复杂度2n^3。

 

所以我们解决了第一个问题。

第二个问题:期望上升最小rank。

既然我们已经有每一时刻的残量网络了,那我们大可以二分我最后的rank。在rank-1这个人的残量网络上添加我的所有边。如果能增广,说明这是我能达到的志愿档次。

完了,细节好多,,小心谨慎qwq尤其注意变量名的使用。

#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;ch=getchar();
	}return cnt*f;
}
struct node{
	int st,to,w,nxt,pre;//记录边起点,终点,边权,下和前缀边(用于删边)。 
}edge[10003],se[212][10003];//当前边,记录边 
int TT,c,n,m,S,T,tot;
int ans[212];//第一个询问的答案。 
int maxx[212];//导师的承载人数 。 
int a[212][212];//读入,第i个人来说,j老师是它的第a[i][j]个志愿。 
int aimcnt[212][212];//第i个人的j号志愿已经有aimcnt[i][j]个老师了。
int zy[212][212][12];//第i个人的j号志愿的所有老师编号。
int hoe[212];//第i个学生期望的志愿档数。
int sf[212][403],first[403]; //记录当前first。
int ed[212];//到第i个人时,边号最大值(边的右边界) 
int dep[409];queue<int> q;//网络流bfs 
void add(int a,int b,int c){
	++tot;
	edge[tot].st=a,edge[tot].to=b;edge[tot].w=c;
	edge[tot].nxt=first[a];if(first[a])edge[first[a]].pre=tot;
	first[a]=tot;
	
	++tot;
	edge[tot].st=b;edge[tot].to=a;edge[tot].w=0;
	edge[tot].nxt=first[b];if(first[b])edge[first[b]].pre=tot;
	first[b]=tot;
}
bool bfs(int s,int t){
	memset(dep,0,sizeof(dep));dep[s]=1;q.push(s);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=first[u];i;i=edge[i].nxt){
			int v=edge[i].to;
			if(edge[i].w>0&&!dep[v]){
				dep[v]=dep[u]+1;q.push(v);
			}
		}
	}
	return dep[t]!=0;
}
void del(int x){
	if(edge[x].nxt)edge[edge[x].nxt].pre=edge[x].pre;
	if(edge[x].pre)edge[edge[x].pre].nxt=edge[x].nxt;
	if(x==first[edge[x].st])first[edge[x].st]=edge[x].nxt;
}
int dfs(int now,int t,int limit){
	if(now==t||!limit)return limit;
	int flow=0,f;
	for(int i=first[now];i;i=edge[i].nxt){
		int v=edge[i].to;if(dep[v]==dep[now]+1&&(f=dfs(v,t,min(edge[i].w,limit)))){
			flow+=f;limit-=f;edge[i].w-=f;edge[i^1].w+=f;if(!limit)break;
		}
	}return flow;
} 
int solve1(){
	int tem;
	for(int i=1;i<=n;i++){
		ans[i]=m+1;add(S,i,1);
		for(int j=1;j<=m;j++){
			if(!aimcnt[i][j])continue;
			tem=tot;
			for(int k=1;k<=aimcnt[i][j];k++)add(i,zy[i][j][k]+n,1);
			if(bfs(S,T)&&dfs(S,T,0x3f3f3f3f)){
				ans[i]=j;break;
			}
			for(int i=tem;i<=tot;i++)del(i);
			tot=tem;
		}
		for(int j=2;j<=tot;j++)se[i][j]=edge[j];
		for(int j=1;j<=T;j++)sf[i][j]=first[j];
		ed[i]=tot;
	}
} 
void add2(int a,int b,int c){
	++tot;
	edge[tot].st=a;edge[tot].to=b;edge[tot].w=c;edge[tot].nxt=first[a];first[a]=tot;
	++tot;
	edge[tot].st=b;edge[tot].to=a;edge[tot].w=0;edge[tot].nxt=first[b];first[b]=tot;
}
bool check(int x,int kth){
	tot=ed[kth-1];
	for(int i=1;i<=T;i++)first[i]=sf[kth-1][i];
	for(int i=2;i<=tot;i++)edge[i]=se[kth-1][i];
	
	add(S,x,1);
	for(int i=1;i<=hoe[x];i++){
		if(!aimcnt[x][i])continue;
		for(int j=1;j<=aimcnt[x][i];j++){
			add(x,zy[x][i][j]+n,1);
		}
	}return bfs(S,T)&&dfs(S,T,0x3f3f3f3f);
}
signed main(){
	TT=in;c=in;
	while(TT--){
		n=in;m=in;S=n+m+1,T=n+m+2;
		memset(first,0,sizeof(first));
		memset(aimcnt,0,sizeof(sf));tot=1;
		for(int i=1;i<=m;i++)
			maxx[i]=in,add(i+n,T,maxx[i]);
		ed[0]=tot;
		for(int i=1;i<=T;i++)sf[0][i]=first[i];
		for(int i=2;i<=tot;i++)se[0][i]=edge[i]; 
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				a[i][j]=in;if(!a[i][j])continue;
				zy[i][a[i][j]][++aimcnt[i][a[i][j]]]=j;
			}
		}
		for(int i=1;i<=n;i++)hoe[i]=in;
		solve1();
		for(int i=1;i<=n;i++)cout<<ans[i]<<" ";cout<<'\n';
//---------------------------问题1解决------------------------------------------// 
		for(int i=1;i<=n;i++){
			if(ans[i]<=hoe[i]){cout<<"0 ";continue;}
			int l=1,r=i-1,gu=0;
			while(l<=r){
				int mid=(l+r)>>1;
				if(check(i,mid)){//假设第i个人rank为mid 
					gu=mid;l=mid+1;
				}else r=mid-1;
			}cout<<i-gu<<" ";
		}
		cout<<'\n';
	}
	return 0;
}

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值