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;
}