[八省联考2018]劈配

劈配

题解

多简单的一道网络流呀

应该是很容易想到网络流的。
先考虑第一问,由于后面的所有人的选择都是在前面的人做出最优选择的情况下做出的,所以后面人的选择是不会干涉到前面的选择的,而前面的人的选择会决定后面人的选择。
所以考虑先处理前面人的决定,再依次向后处理。

考虑用网络流进行判断。
图应该是非常好建的,由于前面的人所处的阶级是定了的,先将所有的老师与学生建点,再从学生向他所处阶级的老师连边。
至于起终点,从起点向学生连一条流量为1的边,表示学生只有一个选择,从老师向终点连流量为 b i b_{i} bi的边,表示老师最多只能收 b i b_{i} bi的人。
处理学生时就一个阶级一个阶级的处理,由于前面的学生已经处理好了,是不能变动的。对于当前学生,向当前枚举的阶级连边,后跑一遍看是否找得到增广路径。
如果·可以找到增广路径,说明该学生应在这个阶级,否则就将路径删去,枚举下一阶级。
如此一来,每个学生第一个找到的阶级就是他所处的阶级,只要将学生从 1 1 1枚举到 n n n,就可以解决第一问了。

对于第二问,依旧可以延续上面的方法,我们可以先二分这个学生在多少名时可以进入他的理想阶级,再判断是否合法。
由于在他当前二分值之前的学生的阶级时不会变的,向其所在的阶级连边。
对与这个同学,就向不低于其理想阶级的老师们连边,再跑一遍dinic,如果他可以在前面的人阶级不变的情况下找到增广路径,当前二分值就是可行的。
只需要将每个人都用上面方法二分一下即可。

总时间复杂度为 O ( n ( n + m ) 4 l o g   n ) O\left(n(n+m)^4log \,n\right) O(n(n+m)4logn)?这竟然还能过题,应该十分跑不满。

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 405
#define reg register
typedef long long LL;
const int INF=0x7f7f7f7f;
const LL inf=0x7f7f7f7f7f7f;
template<typename _T>
inline void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while('0'>s||'9'<s){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,m,head[MAXN],tot,b[MAXN],bst[MAXN],c,cnt,s[MAXN];
queue<int> q;int dep[MAXN],S,T;
struct edge{int from,to,nxt,flow,op;}e[MAXN*MAXN*10];
struct wish{int tc,ord;}a[MAXN][MAXN];
inline void addEdge(int u,int v,int w){e[++tot]=(edge){u,v,head[u],w};head[u]=tot;}
inline void addedge(int u,int v,int w){addEdge(u,v,w);e[tot].op=tot+1;addEdge(v,u,0);e[tot].op=tot-1;}
void remove(int id){head[e[id].from]=e[id].nxt;e[id]=(edge){0,0,0,0,0};}
bool bfs(){
	while(!q.empty())q.pop();
	for(reg int i=1;i<=cnt;++i)dep[i]=0;
	q.push(S);dep[S]=1;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(reg int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(!dep[v]&&e[i].flow){
				q.push(v);dep[v]=dep[u]+1;;
				if(v==T)return 1;
			}
		}
	}
	return 0;
}
int dfs(int u,int maxf){
	if(u==T||!maxf)return maxf;int res=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(!e[i].flow||dep[v]!=dep[u]+1)continue;
		int f=dfs(v,min(e[i].flow,maxf));
		e[i].flow-=f;e[e[i].op].flow+=f;res+=f;maxf-=f;
	}
	return res;
}
inline int dosaka(){
	int res=0;
	while(bfs())res+=dfs(S,INF);
	return res;
}
bool sakura(int mid,int st){
	for(int i=1;i<=cnt;i++)head[i]=0;tot=0;int summ=0;
	for(int i=1;i<=n;i++)addedge(S,i,1);
	for(int i=1;i<=m;i++)addedge(i+n,T,b[i]);
	for(int i=1;i<mid;i++){
		int j=bst[i];if(!j)continue;summ++;
		while(j<=m&&a[i][j].ord==a[i][bst[i]].ord)
			addedge(i,a[i][j].tc+n,1),j++;
	}
	for(int i=1;i<=m;i++){
		if(a[st][i].ord<=s[st]&&a[st][i].ord<=m)
			addedge(st,a[st][i].tc+n,1);
		if(a[st][i].ord>s[st])break;
	}
	int ans=dosaka();
	return ans==summ+1;
}
bool cmp(wish x,wish y){return x.ord<y.ord;}
signed main(){
	int t;read(t);read(c);
	while(t--){
		for(int i=1;i<=cnt;i++)head[i]=bst[i]=0;tot=0;
		read(n);read(m);S=n+m+1;cnt=T=n+m+2;
		for(int i=1;i<=m;i++)read(b[i]);
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				read(a[i][j].ord),a[i][j].tc=j;
				if(!a[i][j].ord)a[i][j].ord=m+1;	
			}
			sort(a[i]+1,a[i]+m+1,cmp);		
		}
		for(int i=1;i<=n;i++)read(s[i]);
		for(int i=1;i<=n;i++)addedge(S,i,1);
		for(int i=1;i<=m;i++)addedge(i+n,T,b[i]);
		for(int i=1,j,k;i<=n;i++){
			for(j=1;j<=m;j=k+1){
				if(a[i][j].ord>m)break;
				k=j;int las=tot;while(a[i][k+1].ord==a[i][j].ord)k++;
				for(int l=j;l<=k;l++)addedge(i,n+a[i][l].tc,1);
				if(dosaka()){bst[i]=j;break;}
				else while(tot>las)remove(tot--);
			}	
		}
		for(int i=1;i<=n;i++)printf("%d ",bst[i]?a[i][bst[i]].ord:m+1);puts("");
		for(int i=1;i<=n;i++){
			int l=1,r=i,ans=0;
			if(bst[i]&&a[i][bst[i]].ord<=s[i]){printf("0 ");continue;}
			while(l<=r){
				int mid=l+r>>1;
				if(sakura(mid,i))ans=mid,l=mid+1;
				else r=mid-1;
			}
			printf("%d ",i-ans);
		}
		puts("");
	}
	return 0;
}

谢谢!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值