洛谷P4382 [八省联考2018]劈配(网络流,二分答案)

洛谷题目传送门

说不定比官方sol里的某理论最优算法还优秀一点?

Orz良心出题人,暴力有70分2333

思路分析

正解的思路很巧妙,其实我并不觉得这是个正儿八经的网络流或者二分图匹配的题目,主要还是个思维+建图模型+乱搞。。。。。。

\(C=1\)时我们就可以对于每个人直接匹配而不会影响到后面的选择了。但是\(C>1\)的话,可能某一个人可以选多个导师,当他随便选了其中一个以后,可能影响到后面某个人使其选不到本来的最优解。而此时后面那个人就要让前面那个人改变决策,做出反悔。

这时候有没有想到网络流算法的反悔边的应用呢?(因为蒟蒻只会网络流所以就用网络流来跑二分图匹配)建一个二分图,左边\(n\)个点代表选手,右边\(m\)个点代表导师(战队),一开始所有导师向汇点\(T\)连流量为战队人数上限的边。每次从前往后枚举选手,找到能够选择的最高志愿,动态地向该档志愿能选择的所有导师连容量为\(1\)的边,然后增广一次(这时会随意选择一个可行的导师)。因为增广后会留下反悔边,所以这样做就很方便啦。因为每次是动态加边直接从每个选手开始增广 ,所以貌似连源点都不需要(这就是蒟蒻一直觉得这题像一个乱搞题的原因)

这时候留下一个问题,如何方便地判断某时刻某战队是否已经不可选了呢?一开始蒟蒻yy各种极其错误的方法来乱搞。后来发现,将问题带入到我们建的网络流模型里面,不就是要知道每个代表导师的点到汇点是否存在增广路吗?具体方法,从汇点开始dfs搜索,如果某条边的反向边有流量就搜过去,把搜到的点标记起来就好啦!

对于第二问,显然可以想到二分。先把每次每个战队能否选择的状态保存下来,对于每个选手,二分他之前的排名,根据是否存在一个可以选择而且令他满意的战队来决定下面往哪一半区间接着找。

复杂度分析

有三步。首先要暴力找到能满足的最高志愿,复杂度\(O(n^2m)\)

接着,增广(蒟蒻直接dfs去找而不是dinic)和更新当前战队是否可选状态,因为是在图上搞,边数不超过\(O(nC)\),所以这部分的复杂度都是\(O(n^2C)\)的(据说在二分图上使用dinic可以使增广的复杂度降到\(O(n\sqrt nC)\)?但是这是动态加边,每次都要跑,那应该是不能优化的吧)

最后,二分最少上升的排名,复杂度\(O(nm\log n)\)

综上,因为n与m同阶,所以总复杂度应该是\(O(n^3)\)级别的。由于是网络流,所以复杂度非常玄学,也不至于删边那么麻烦,蒟蒻随意加了一点点剪枝,还有register,inline,快读快写,就只要8ms,开O2 4ms。居然没有0msTAT,欢迎超越

最后提一个惨痛的经历:蒟蒻调这题一天多了,所有的算法性错误都更正了,还是WA,最后才发现重建图的时候反向边的流量忘记赋成0来覆盖掉原来的信息了TAT

#include<cstdio>
#include<cstdlib>
#include<cstring>
#define R register int
#define I inline void
#define pc(C) *po++=C
#define add(X,Y,F)    to[++p]=Y,ne[p]=he[X],f[he[X]=p]=F,    to[++p]=X,ne[p]=he[Y],f[he[Y]=p]=0//反边赋0!
const int SZ=1<<20,N=209,M=N<<1,L=N*22;//边数最多2(nC+m)
char ibuf[SZ],obuf[SZ],*pi=ibuf-1,*po=obuf;
I in(R&x){
    while(*++pi<‘-‘);
    x=*pi&15;
    while(*++pi>‘-‘)x*=10,x+=*pi&15;
}
I out(R x){
    if(x>9)out(x/10);
    pc(x%10|‘0‘);
}
int T,he[M],ne[L],to[L],f[L],he1[N],ne1[N];
int a[N][N],b[N],up[N];//up记录最少上升排名
bool c[N][N],vis[M];//c存下战队是否可选
void remain(R x){//更新状态,利用好反向边!
    vis[x]=1;
    for(R i=he[x];i;i=ne[i])
        if(f[i^1]&&!vis[to[i]])remain(to[i]);
}
bool flow(R x){//dfs增广
    if(vis[x])return x==T;
    vis[x]=1;
    for(R i=he[x];i;i=ne[i])
        if(f[i]&&flow(to[i])){
            --f[i];++f[i^1];
            return 1;
        }
    return 0;
}
int main(){
    fread(ibuf,1,SZ,stdin);
    R TT,CC,n,m,p,i,j,x,s,l,r,mi;
    in(TT);in(CC);
    while(TT--){
        in(n);in(m);
        T=n+m+1;p=vis[T]=1;
        for(j=1;j<=m;++j)
            in(b[j]),add(j+n,T,b[j]);
        for(i=1;i<=n;++i)
            for(j=1;j<=m;++j)in(a[i][j]);
        for(j=1;j<=m;++j)
            c[1][j]=(bool)b[j];//初始状态就是收不收人(不会来个导师不收人吧233)
        for(i=1;i<=n;++i){
            memset(he1,0,(n+1)<<2);
            for(j=1;j<=m;++j)//链表把同等级志愿挂一起,像邻接表一样
                ne1[j]=he1[a[i][j]],he1[a[i][j]]=j;
            for(x=1;x<=m;++x){
                for(j=he1[x];j;j=ne1[j])
                    if(c[i][j])break;
                if(j){
                    for(j=he1[x];j;j=ne1[j])
                        if(c[i][j])add(i,j+n,1);//贪心选择,动态连边
                    memset(vis,0,T);flow(i);
                    break;
                }
            }
            out(x);pc(‘ ‘);
            if(x<=m){
                memset(vis,0,T);remain(T);
                memcpy(c[i+1]+1,vis+n+1,m);
            }//没选到肯定状态不变啦,直接copy上一次的
            else memcpy(c[i+1]+1,c[i]+1,N);
            in(s);if(x<=s){up[i]=0;continue;}//判掉一开始就满意的
            l=0;r=i-1;
            F:while(l!=r){
                mi=(l+r+1)>>1;//注意二分区间是左闭右开的,所以要写成这样
                for(x=s;x;--x)
                    for(j=he1[x];j;j=ne1[j])
                        if(c[mi][j]){l=mi;goto F;}
                r=mi-1;
            }
            up[i]=i-l;
        }
        pc(‘\n‘);for(i=1;i<=n;++i){out(up[i]);pc(‘ ‘);}pc(‘\n‘);
        memset(he,0,(T+1)<<2);//应该只有he需要清空
    }
    fwrite(obuf,1,po-obuf,stdout);
    return 0;
}

原文地址:https://www.cnblogs.com/flashhu/p/8799068.html

时间: 2024-10-12 19:28:14

洛谷P4382 [八省联考2018]劈配(网络流,二分答案)的相关文章

[BZOJ5251][九省联考2018]劈配(网络流)

5251: [2018多省省队联测]劈配 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 33  Solved: 22[Submit][Status][Discuss] Description 一年一度的综艺节目<中国新代码>又开始了. Zayid从小就梦想成为一名程序员,他觉得这是一个展示自己的舞台,于是他毫不犹豫地报名了. 题目描述 轻车熟路的Zayid顺利地通过了海选,接下来的环节是导师盲选,这一阶段的规则是这样的: 总共n名参赛选手(编号

[八省联考2018] 劈配

题目背景 一年一度的综艺节目<中国新代码>又开始了.Zayid 从小就梦想成为一名程序员,他觉得这是一个展示自己的舞台,于是他毫不犹豫地报名了. 题目描述 轻车熟路的Zayid 顺利地通过了海选,接下来的环节是导师盲选,这一阶段的规则是这样的: 总共n 名参赛选手(编号从1 至n)每人写出一份代码并介绍自己的梦想.接着 由所有导师对这些选手进行排名.为了避免后续的麻烦,规定不存在排名并列的情况. 同时,每名选手都将独立地填写一份志愿表,来对总共 m 位导师(编号从 1 至 m)作出评价.志愿表

【BZOJ5251】【八省联考2018】劈配(网络流,二分答案)

[BZOJ5251][八省联考2018]劈配(网络流,二分答案) 题面 洛谷 BZOJ Description 一年一度的综艺节目<中国新代码>又开始了. Zayid从小就梦想成为一名程序员,他觉得这是一个展示自己的舞台,于是他毫不犹豫地报名了. 题目描述 轻车熟路的Zayid顺利地通过了海选,接下来的环节是导师盲选,这一阶段的规则是这样的: 总共n名参赛选手(编号从1至n)每人写出一份代码并介绍自己的梦想.接着由所有导师对这些选手进行排名. 为了避免后续的麻烦,规定不存在排名并列的情况. 同

洛谷P3747 [六省联考2017]相逢是问候

传送门 题解 扩展欧拉定理. 线段树维护,已经全改到底了的节点就不管,不然暴力修改下去. //Achen #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<vector> #include<cstdio> #include<queue> #include<cmath> const int N=5

洛谷P3749 [六省联考2017]寿司餐厅

传送门 题解 这几道都是上周llj讲的题,题解也写得十分好了,所以直接贴了几个链接和代码. //Achen #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<vector> #include<queue> #include<cmath> #include<

[八省联考2018]林克卡特树lct

#include<bits/stdc++.h> #define RG register #define IL inline #define _ 500005 #define INF 1e18 #define ll long long using namespace std; IL ll gi(){ RG ll data = 0 , m = 1; RG char ch = 0; while(ch != '-' && (ch<'0' || ch > '9')) ch =

[八省联考2018]林克卡特树

题目描述 小L 最近沉迷于塞尔达传说:荒野之息(The Legend of Zelda: Breath of The Wild)无法自拔,他尤其喜欢游戏中的迷你挑战. 游戏中有一个叫做"LCT" 的挑战,它的规则是这样子的:现在有一个N 个点的 树(Tree),每条边有一个整数边权vi ,若vi >= 0,表示走这条边会获得vi 的收益:若vi < 0 ,则表示走这条边需要支付- vi 的过路费.小L 需要控制主角Link 切掉(Cut)树上的 恰好K 条边,然后再连接 K

解题:八省联考2018 林克卡特树

题面 DP凸优化 题目并不难 先转化问题,显然k=0的时候我们都知道是求直径,然后k=1就是选两条点不相交的链拼起来,很容易推出题目就是要我们在树上选$k+1$条点不相交的链 事实上你直接按照边不相交做,取k+1次直径都可以得到50pts的好成绩,我佛了(不要问我怎么知道的 这个东西是可以DP的(稍微有点麻烦):设$dp[i][j][0/1/2]$表示以$i$为根的子树里选出j条链,这时i的度数为0/1/2的最优解.度数为零表示i是单独一个点,度数为1表示是链的端点,度数为2就是链中间的一个点

P4383 [八省联考2018]林克卡特树lct 树形DP+凸优化/带权二分

$ \color{#0066ff}{ 题目描述 }$ 小L 最近沉迷于塞尔达传说:荒野之息(The Legend of Zelda: Breath of The Wild)无法自拔,他尤其喜欢游戏中的迷你挑战. 游戏中有一个叫做"LCT" 的挑战,它的规则是这样子的:现在有一个N 个点的 树(Tree),每条边有一个整数边权vi ,若vi >= 0,表示走这条边会获得vi 的收益:若vi < 0 ,则表示走这条边需要支付- vi 的过路费.小L 需要控制主角Link 切掉(