1 条题解
-
0
自动搬运
来自洛谷,原作者为

liuzhangfeiabc
**搬运于
2025-08-24 21:59:35,当前版本为作者最后更新于2018-04-08 11:57:33,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
蒟蒻强行发一波题解
据说这个题做法多种多样,花样跑匈牙利、dinic,甚至有人跑费用流都过了?
我写的是一种“变形匈牙利”,可能是sdoi全场跑得最快的之一。
大体思路就是,正常跑匈牙利时每个点只能匹配一个点,这里我们强行让每个导师的点可以匹配多个选手。
我们按照序号依次加入一个选手,对于每位选手枚举每个志愿的每个导师。
如果当前导师的名额还没用完,直接匹配成功。
否则,对这位导师已匹配的选手,再在同一志愿内寻找其他导师,能匹配则匹配成功。
这一部分与传统的匈牙利思路是类似的。
至于复杂度,每次加入一个人时,最多将所有人的某一志愿全部访问一遍,因此总复杂度是n ^ 2 * c的。
这样一来我们就可以解决第一问了,那么第二问呢?
我们第一个想法就是二分,因为答案显然具有可二分性。
对于每个答案k,先把前k-1个人跑一遍匈牙利,再加入当前这个人即可。
然而如果我们每次都需要重跑一遍匈牙利的话,复杂度就上升到了n ^ 3 c logn,对于0.3s一组数据来说会T。
不过我们可以暴力地记录每一个前缀跑匈牙利的结果,然后每次check时直接在对应版本上加入一个人即可。
总复杂度n ^ 2 c logn,luogu跑到92ms暂时应该是rk1。
#include<cstdio> #include<iostream> #include<cmath> #include<algorithm> #include<cstring> #include<cstdlib> #include<ctime> using namespace std; #define li long long #define gc getchar() #define pc putchar #define kg pc(' ') #define hh pc('\n') li read(){ li x = 0,y = 1; char c; c = gc; while(!isdigit(c)){ if(c == '-') y = -1; c = gc; } while(isdigit(c)){ x = (x << 1) + (x << 3) + c - '0'; c = gc; } return x * y; } void print(li x){ if(x < 0){ pc('-'); x = -x; } if(x >= 10) print(x / 10); pc(x % 10 + '0'); } int t,c,n,m; int a[210][210]; int zy[210][210][11],sl[210][210]; int mx[210],b[210]; bool vst[210]; int p1[210],p2[210][210],nw[210],an[210]; int tp1[210][210],tp2[210][210][210],tnw[210][210]; bool dfs(int q,int w){//匈牙利 int i,j,k,l; for(j = 1;j <= sl[q][w];++j){ k = zy[q][w][j]; if(vst[k]) continue; vst[k] = 1; if(nw[k] < mx[k]){ p1[q] = k; p2[k][++nw[k]] = q; return 1; } for(l = 1;l <= mx[k];++l){ i = p2[k][l]; if(dfs(i,a[i][k])){ p1[q] = k; p2[k][l] = q; return 1; } } } return 0; } void wk1(){//第一问 int i,j,k; for(i = 1;i <= n;++i){ memset(vst,0,sizeof(vst)); for(j = 1;j <= m;++j){ if(!sl[i][j]) continue; if(dfs(i,j)) break; } //下面是复制匈牙利的结果 for(j = 1;j <= i;++j) tp1[i][j] = p1[j]; for(j = 1;j <= m;++j) tnw[i][j] = nw[j]; for(j = 1;j <= m;++j){ for(k = 1;k <= nw[j];++k) tp2[i][j][k] = p2[j][k]; } } } bool wk2(int q,int w){//第二问的check,表示第q个人如果在第w名是否可行 int i,j; for(i = 1;i <= w - 1;++i) p1[i] = tp1[w - 1][i]; for(i = 1;i <= m;++i) nw[i] = tnw[w - 1][i]; for(i = 1;i <= m;++i){ for(j = 1;j <= nw[i];++j) p2[i][j] = tp2[w - 1][i][j]; }//copy前w-1个人的结果 memset(vst,0,sizeof(vst)); for(i = 1;i <= b[q];++i){ if(!sl[q][i]) continue; if(dfs(q,i)) return 1; } return 0; } int main(){ int i,j,l,r,mid,as; t = read(); c = read(); while(t--){ memset(sl,0,sizeof(sl)); memset(p1,0,sizeof(p1)); memset(nw,0,sizeof(nw)); n = read(); m = read(); for(i = 1;i <= m;++i) mx[i] = read(); for(i = 1;i <= n;++i){ for(j = 1;j <= m;++j) { a[i][j] = read(); if(a[i][j]) zy[i][a[i][j]][++sl[i][a[i][j]]] = j; } } for(i = 1;i <= n;++i) a[i][0] = m + 1; for(i = 1;i <= n;++i) b[i] = read(); wk1(); for(i = 1;i <= n;++i) { an[i] = a[i][p1[i]]; print(an[i]); kg; } hh; for(i = 1;i <= n;++i){ if(an[i] <= b[i]){ putchar('0');kg;continue; } l = 1;r = i - 1;as = 0; while(l <= r){ mid = l + r >> 1; if(wk2(i,mid)) { as = mid; l = mid + 1; } else r = mid - 1; } print(i - as);kg; } hh; } return 0; }update:
鉴于大多数人写的都是dinic,本蒟蒻再斗胆更新一波dinic算法的题解。
原点向每个选手连边,边权为1;每个导师向汇点连边,边权是这个导师的名额上限。
需要注意的一点是,这里的dinic需要动态加边。
同样还是枚举每个人,枚举每个志愿。
把这个人与这个志愿的导师连边,边权为1。
然后在残量网络上跑一次增广路,如果有流则匹配成功。
否则这一组的边就用不上了,不妨直接删除。(据说不这样的话会T?)
这样第1问就解决了。
对于第2问,同样可以二分答案并暴力记录每一个前缀的残量网络,然后直接加上前面所有组的边,跑一次增广路即可判断。
这样一来,由于二分图中dinic的复杂度大约为n * sqrt(n),所以总复杂度大约为n^2 sqrt(n) logn。
经过一番丧心病狂的底层优化后luogu跑到了180ms,也许是dinic的rk1?#include<bits/stdc++.h> using namespace std; int p,c,n,m; struct edge{ int fr,to,nxt,pre,val; }e[10010],te[210][10010]; int tf[210][410],fir[410],dq[410],an[210],tct[210]; int cnt,s,g; int q[100010],h,t; int a[210][210],b[210],mx[210]; int sl[210][210],zy[210][210][11]; int vis[410]; #define gc getchar() #define pc putchar #define kg pc(' ') #define hh pc('\n') #define inf 987654321 #define rg register inline int read(){ int x = 0; char c; c = gc; while(!isdigit(c)) c = gc; while(isdigit(c)){ x = (x << 1) + (x << 3) + c - '0'; c = gc; } return x; } void print(int x){ if(x >= 10) print(x / 10); pc(x % 10 + '0'); } inline void ins(int u,int v,int w){ e[++cnt].to = v;e[cnt].nxt = fir[u];e[cnt].fr = u; if(fir[u]) e[fir[u]].pre = cnt; fir[u] = cnt;e[cnt].val = w; e[++cnt].to = u;e[cnt].nxt = fir[v];e[cnt].fr = v; if(fir[v]) e[fir[v]].pre = cnt; fir[v] = cnt;e[cnt].val = 0; } inline void in2(int u,int v,int w){//这里是底层优化 e[++cnt].to = v;e[cnt].nxt = fir[u];fir[u] = cnt;e[cnt].val = w; e[++cnt].to = u;e[cnt].nxt = fir[v];fir[v] = cnt;e[cnt].val = 0; } inline void del(int q){//删边,类似于从链表中删除元素的操作 if(e[q].nxt) e[e[q].nxt].pre = e[q].pre; if(e[q].pre) e[e[q].pre].nxt = e[q].nxt; if(q == fir[e[q].fr]) fir[e[q].fr] = e[q].nxt; } //以下是dinic bool bfs(){ memset(vis,0,g * 4 + 4); rg int i,j; memcpy(dq,fir,g * 4 + 4); h = t = 0; q[++t] = s; vis[s] = 1; while(h < t){ j = q[++h]; for(i = fir[j];i;i = e[i].nxt){ if(e[i].val <= 0) continue; if(vis[e[i].to]) continue; vis[e[i].to] = vis[j] + 1; q[++t] = e[i].to; } } return vis[g]; } int dfs(int q,int fl){ if(q == g) return fl; int tp,nw = 0; for(int &i = dq[q];i;i = e[i].nxt){ if(e[i].val <= 0) continue; if(vis[e[i].to] != vis[q] + 1) continue; tp = dfs(e[i].to,min(e[i].val,fl)); nw += tp; e[i].val -= tp; e[i ^ 1].val += tp; if(nw == fl) return nw; } if(nw != fl) vis[q] = -1; return nw; } void wk1(){//第一问 rg int i,j,k; int l; for(i = 1;i <= n;++i){ an[i] = m + 1; ins(s,i,1); for(j = 1;j <= m;++j){ if(!sl[i][j]) continue; l = cnt; for(k = 1;k <= sl[i][j];++k) ins(i,zy[i][j][k] + n,1);//动态加边 if(bfs() && dfs(s,inf)){ an[i] = j; break; } else{ for(k = l;k <= cnt;++k) del(k);//动态删边 cnt = l; } } //copy残量网络 for(j = 2;j <= cnt;++j) te[i][j] = e[j]; for(j = 1;j <= g;++j) tf[i][j] = fir[j]; tct[i] = cnt; } } bool wk2(int q,int w){//第二问的check,表示第q个人如果在第w名是否可行 rg int i,j; cnt = tct[w - 1]; for(i = 1;i <= g;++i) fir[i] = tf[w - 1][i]; for(i = 2;i <= cnt;++i) e[i] = te[w - 1][i]; //以上为copy前w-1个人的残量网络 ins(s,q,1); for(i = 1;i <= b[q];++i){ if(!sl[q][i]) continue; for(j = 1;j <= sl[q][i];++j) in2(q,zy[q][i][j] + n,1); } return bfs() && dfs(s,inf); } int main(){ rg int i,j; int l,r,mid,as; p = read(); c = read(); while(p--){ memset(sl,0,sizeof(sl)); memset(fir,0,sizeof(fir)); cnt = 1; n = read(); m = read(); s = n + m + 1; g = n + m + 2; for(i = 1;i <= m;++i) { mx[i] = read(); ins(i + n,g,mx[i]); } tct[0] = cnt; for(i = 1;i <= g;++i) tf[0][i] = fir[i]; for(i = 2;i <= cnt;++i) te[0][i] = e[i]; //别忘了把一开始的边也记录下来,我在这里wa了若干次qwq for(i = 1;i <= n;++i){ for(j = 1;j <= m;++j){ a[i][j] = read(); if(!a[i][j]) continue; zy[i][a[i][j]][++sl[i][a[i][j]]] = j; } } for(i = 1;i <= n;++i) b[i] = read(); wk1(); for(i = 1;i <= n;++i){ print(an[i]); kg; } hh; for(i = 1;i <= n;++i){ if(an[i] <= b[i]){ pc('0');kg; continue; } l = 1;r = i - 1;as = 0; while(l <= r){ mid = l + r >> 1; if(wk2(i,mid)){ as = mid; l = mid + 1; } else r = mid - 1; } print(i - as);kg; } hh; } return 0; }
- 1
信息
- ID
- 3374
- 时间
- 1000ms
- 内存
- 500MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者