并不难的一道题,甚至比一双木棋还容易一些
关键点就是一个:怎么处理“最优结果”,即总体字典序最小
显然要反悔。匹配问题反悔套路就多了
法一:
变形匈牙利算法
记录右部点导师的战队情况
枚举每个人,枚举每一层进行尝试匹配
最多把前面的人的边都遍历到,总共O(n*n*c)
第二问
显然二分
第一问时候存图即可
技巧:
n,m很小,充分利用桶和计数器,可以不带set等logn结构,也不用vector(防止卡常)
#include<bits/stdc++.h> #define reg register int #define il inline #define fi first #define se second #define mk(a,b) make_pair(a,b) #define numb (ch^'0') using namespace std; typedef long long ll; template<class T>il void rd(T &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } template<class T>il void output(T x){if(x/10)output(x/10);putchar(x%10+'0');} template<class T>il void ot(T x){if(x<0) putchar('-'),x=-x;output(x);putchar(' ');} template<class T>il void prt(T a[],int st,int nd){for(reg i=st;i<=nd;++i) ot(a[i]);putchar(' ');} namespace Miracle{ const int N=201; int n,m,C; int vis[N]; int con[N][N]; int to[N][N][N],cnt[N][N]; int b[N],mem[N][N],p[N];//teacher int s[N]; int c[N];//ans1 struct node{ int mem[N][N],p[N]; }tim[N]; bool dfs(int x,int d,int st){ for(reg i=1;i<=cnt[x][d];++i){ int y=to[x][d][i]; if(vis[y]==st) continue; vis[y]=st; if(p[y]<b[y]){ mem[y][++p[y]]=x; return true; }else{ for(reg j=1;j<=b[y];++j){ if(dfs(mem[y][j],c[mem[y][j]],st)){ mem[y][j]=x; return true; } } } } return false; } void wrk1(){ for(reg i=1;i<=n;++i){ c[i]=m+1; memset(vis,0,sizeof vis); for(reg j=1;j<=m;++j){ if(cnt[i][j]==0) continue; if(dfs(i,j,j)){ c[i]=j;break; } } memcpy(tim[i].mem,mem,sizeof mem); memcpy(tim[i].p,p,sizeof p); } } bool fin(int x,int d,int t,int st){ for(reg i=1;i<=cnt[x][d];++i){ int y=to[x][d][i]; if(vis[y]==st) continue; vis[y]=st; if(tim[t].p[y]<b[y]){ return true; }else{ for(reg j=1;j<=b[y];++j){ if(fin(tim[t].mem[y][j],c[tim[t].mem[y][j]],t,st)){ return true; } } } } return false; } bool che(int x,int t){ memset(vis,0,sizeof vis); for(reg j=1;j<=s[x];++j){ if(cnt[x][j]==0) continue; if(fin(x,j,t,j)) return true; } return false; } int ans[N]; void wrk2(){ for(reg i=1;i<=n;++i){ if(c[i]<=s[i]) { ans[i]=0;continue; } int l=0,r=i-2; int ok=-1; while(l<=r){ int mid=(l+r)/2; if(che(i,mid)){ ok=mid; l=mid+1; }else r=mid-1; } ans[i]=i-ok-1; } } void clear(){ memset(ans,0,sizeof ans); memset(cnt,0,sizeof cnt); memset(p,0,sizeof p); } int main(){ int t;rd(t);rd(C); while(t--){ clear(); rd(n);rd(m); for(reg i=1;i<=m;++i) rd(b[i]); for(reg i=1;i<=n;++i){ for(reg j=1;j<=m;++j){ rd(con[i][j]); to[i][con[i][j]][++cnt[i][con[i][j]]]=j; } } for(reg i=1;i<=n;++i){ rd(s[i]); } wrk1(); // cout<<"hahaha "<<endl; for(reg i=1;i<=n;++i){ printf("%d ",c[i]); } puts(""); wrk2(); for(reg i=1;i<=n;++i){ printf("%d ",ans[i]); } puts(""); // cout<<" hahaah"<<endl; } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/3/25 17:52:55 */
法二:
动态加边dinic最大流
第一问就是把匈牙利变成dinic,据说要把匹配失败的边删除防止TLE
第二问:
二分。用vector存第一问每次图的边
一次性把前s[i]个边都加入,跑dinic
技巧:
删除的话,只用vector.pop_back即可。其他的边,由于找不到增广路,所以依然流量守恒的
题面很长,实际不难
就是考察一个匹配的反悔机制了
暴力分还很多、、、