BZOJ 5251: [2018多省省队联测]劈配 网络流+二分

时间:2019-08-17
本文章向大家介绍BZOJ 5251: [2018多省省队联测]劈配 网络流+二分,主要包括BZOJ 5251: [2018多省省队联测]劈配 网络流+二分使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

title

BZOJ 5251

LUOGU 4382

Background

一年一度的综艺节目《中国新代码》又开始了。Zayid 从小就梦想成为一名程序员,他觉得这是一个展示自己的舞台,于是他毫不犹豫地报名了。

Description

轻车熟路的 Zayid 顺利地通过了海选,接下来的环节是导师盲选,这一阶段的规则是这样的:

总共 \(n\) 名参赛选手(编号从 \(1\)\(n\))每人写出一份代码并介绍自己的梦想。接着由所有导师对这些选手进行排名。为了避免后续的麻烦,规定不存在排名并列的情况

同时,每名选手都将独立地填写一份志愿表,来对总共 \(m\) 位导师(编号从 \(1\)\(m\))作出评价。志愿表上包含了共 \(m\) 档志愿。对于每一档志愿,选手被允许填写最多 \(C\) 位导师,每位导师最多被每位选手填写一次(放弃某些导师也是被允许的)。

在双方的工作都完成后,进行录取工作。每位导师都有自己战队的人数上限,这意味着可能有部分选手的较高志愿、甚至是全部志愿无法得到满足。节目组对”前 \(i\) 名的录取结果最优“作出如下定义:

  • \(1\) 名的录取结果最优,当且仅当\(1\) 名被其最高非空志愿录取(特别地,如果第 \(1\) 名没有填写志愿表,那么该选手出局)。

  • \(i\) 名的录取结果最优,当且仅当在前 \(i-1\) 名的录取结果最优的情况下:第 \(i\) 名被其理论可能的最高志愿录取(特别地,如果第 \(i\) 名没有填写志愿表、或其所有志愿中的导师战队均已满员,那么该选手出局)。

如果一种方案满足‘‘前 \(n\) 名的录取结果最优’’,那么我们可以简称这种方案是最优的

举例而言,\(2\) 位导师 \(T\) 老师、\(F\) 老师的战队人数上限分别都是 \(1\) 人;\(2\) 位选手 Zayid 、DuckD 分列第 \(1\)\(2\) 名。那么下面 \(3\) 种志愿表及其对应的最优录取结果如表中所示:

可以证明,对于上面的志愿表,对应的方案都是唯一的最优录取结果。

每个人都有一个自己的理想值 \(s_i\),表示第 \(i\) 位同学希望自己被第 \(s_i\) 或更高的志愿录取,如果没有,那么他就会非常沮丧。

现在,所有选手的志愿表和排名都已公示。巧合的是,每位选手的排名都恰好与它们的编号相同。

对于每一位选手,Zayid 都想知道下面两个问题的答案:

  • 在最优的录取方案中,他会被第几志愿录取。
  • 在其他选手相对排名不变的情况下,至少上升多少名才能使得他不沮丧。

作为《中国新代码》的实力派代码手,Zayid 当然轻松地解决了这个问题。不过他还是想请你再算一遍,来检验自己计算的正确性。

Input

每个测试点包含多组测试数据,第一行 \(2\) 个用空格隔开的非负整数 \(T,C\),分别表示数据组数、每档志愿最多允许填写的导师数目。

接下来依次描述每组数据,对于每组数据:

  • \(1\) 行两个用空格隔开的正整数 \(n,m\)

    \(n,m\) 分别表示选手的数量、导师的数量。

  • \(2\)\(m\) 个用空格隔开的正整数:其中第 \(i\) 个整数为 \(b_i\)

    \(b_i\) 表示编号为 \(i\) 的导师战队人数的上限。

  • \(3\) 行至第 \(n+2\) 行,每行 \(m\) 个用空格隔开的非负整数:其中第 \(i+2\) 行左起第 \(j\) 个数为 \(a_{i,j}\)

    \(a_{i,j}\) 表示编号为i的选手将编号为j的导师编排在了第 \(a_{i,j}\) 志愿。特别地,如果 \(a_{i,j}=0\),则表示该选手没有将该导师填入志愿表。

    在这一部分,保证每行中不存在某一个正数出现超过 \(C\) 次(0可能出现超过 \(C\) 次),同时保证所有\(a_{i,j}\leqslant m\)

  • \(n+3\)\(n\) 个用空格隔开的正整数,其中第 \(i\) 个整数为 \(S_i\)

    \(S_i\) 表示编号为i的选手的理想值。

    在这一部分,保证 \(S_i\leqslant m\)

    \(T\leqslant 5,m\leqslant n\leqslant 200,b_i\leqslant N\)

Output

按顺序输出每组数据的答案。对于每组数据,输出 \(2\) 行:

  • \(1\) 行输出 \(n\) 个用空格隔开的正整数,其中第 \(i\) 个整数的意义为:

    在最优的录取方案中,编号为 \(i\) 的选手会被该档志愿录取。

    特别地,如果该选手出局,则这个数为 \(m+1\)

  • \(2\) 行输出 \(n\) 个用空格隔开的非负整数,其中第 \(i\) 个整数的意义为:

    使编号为 \(i\) 的选手不沮丧,最少需要让他上升的排名数。

    特别地,如果该选手一定会沮丧,则这个数为 \(i\)

Sample Input

3 5
2 2
1 1
2 2
1 2
1 1
2 2
1 1
1 2
1 2
2 1
2 2
1 1
0 1
0 1
2 2

Sample Output

2 1
1 0
1 2
0 1
1 3
0 1

Hint

三组数据分别与【题目描述】中的三个表格对应。

对于第 \(1\) 组数据:由于选手 \(1\) 没有填写第一志愿,所以他一定无法被第一志愿录取,也就一定会沮丧。

选手 \(2\) 按原排名就不沮丧,因此他不需要提升排名。

对于第 \(2\) 组和第 \(3\) 组数据:\(1\) 号选手都不需要提升排名。

而希望被第一志愿录取的 \(2\) 号选手都必须升到第 \(1\) 名才能如愿。

analysis

第一问:

左边一列点代表选手,右边一列点代表导师,源点 \(s\) 连向每名选手,流量为 1,每名导师连向汇点 \(t\) ,流量为导师战队人数的上限;

然后从每名选手的志愿往里面加边,

如果当前选手的当前志愿可以满足,即目前网络流可以满流,保留这一志愿的边,然后下一个选手,

否则,删除这一志愿的边,然后下一个志愿。


第二问:

二分这个选手要前进多少名。

假设是选手 \(i\) 要前进 \(x\) 名,把前 \(i-x-1\) 名的选手在第一问中满足的志愿的边加进去,在把选手 \(i\) 的边加进去,判断是否满流,注意判断满流的时候不包括\(i-x-1\) 名选手里没有任何导师的选手。

code

#include<bits/stdc++.h>

const int maxn=211,maxm=80801;

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }

    char Out[1<<24],*fe=Out;
    inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
    template<typename T>inline void write(T x,char str)
    {
        if (!x) *fe++=48;
        if (x<0) *fe++='-', x=-x;
        T num=0, ch[20];
        while (x) ch[++num]=x%10+48, x/=10;
        while (num) *fe++=ch[num--];
        *fe++=str;
    }
}

using IO::read;
using IO::write;

int ver[maxm<<1],edge[maxm<<1],Next[maxm<<1],head[maxm<<1],len=1;
inline void add(int x,int y,int z)
{
    ver[++len]=y,edge[len]=z,Next[len]=head[x],head[x]=len;
    ver[++len]=x,edge[len]=0,Next[len]=head[y],head[y]=len;
}

int s,t;
int dist[maxn<<1],cur[maxn<<1];
inline bool bfs()
{
    std::queue<int>q;
    for (int i=s; i<=t; ++i) cur[i]=head[i],dist[i]=0;
    q.push(s),dist[s]=1;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x]; i; i=Next[i])
        {
            int y=ver[i];
            if (edge[i] && !dist[y])
            {
                dist[y]=dist[x]+1;
                if (y==t) return 1;
                q.push(y);
            }
        }
    }
    return 0;
}

inline int get(int x,int low)
{
    if (x==t) return low;
    int tmp=low;
    for (int &i=cur[x]; i; i=Next[i])
    {
        int y=ver[i];
        if (edge[i] && dist[y]==dist[x]+1)
        {
            int a=get(y,std::min(tmp,edge[i]));
            if (!a) dist[y]=0;
            edge[i]-=a;
            edge[i^1]+=a;
            if (!(tmp-=a)) break;
        }
    }
    return low-tmp;
}

typedef int iarr[maxn];
iarr match,lim,dream;
int cnt[maxn][maxn],n,m;
int a[maxn][maxn][maxn];

inline void Clear()
{
    memset(match,0,sizeof(match));
    memset(cnt,0,sizeof(cnt));
    memset(head,0,sizeof(head));
    len=1;
}

inline void InPut()
{
    read(n);read(m); s=0, t=n+m+1;
    for (int i=1; i<=m; ++i) read(lim[i]);
    for (int i=1; i<=n; ++i)
        for (int j=1,x; j<=m; ++j)
        {
            read(x);
            if (x) a[i][x][++cnt[i][x]]=j;//第 i 位选手的第 x 个志愿是第 cnt[i][x] 个的为第 j 个导师
        }
    for (int i=1; i<=n; ++i) read(dream[i]);
}

inline void Solve1()
{
    for (int i=1; i<=m; ++i) add(i+n,t,lim[i]);//每名导师 连向 汇点t,流量为导师战队人数的上限
    for (int i=1; i<=n; ++i)
    {
        add(s,i,1);//源点s 连向 每名选手,流量为1
        for (int j=1; j<=m; ++j)
            if (cnt[i][j])
            {
                for (int k=1; k<=cnt[i][j]; ++k) add(i,a[i][j][k]+n,1);//每名选手 连向 志愿导师,流量为1
                if (bfs())
                {
                    get(s,i);
                    match[i]=j;
                    break;
                }
                else
                    for (int k=1,h=len; k<=cnt[i][j]; ++k,h-=2) edge[h]=edge[h^1]=0;//相当于删边操作
            }
    }
    for (int i=1; i<=n; ++i) write(match[i] ? match[i] : m+1,i==n ? '\n' : ' ');
}

inline bool check(int x,int goal)
{
    memset(head,0,sizeof(head));
    len=1;
    for (int i=1; i<=m; ++i) add(i+n,t,lim[i]);//开始检测
    int sum=0;
    for (int i=1; i<goal; ++i)
    {
        add(s,i,1);
        if (!match[i]) ++sum;
        else
            for (int j=1; j<=cnt[i][match[i]]; ++j) add(i,a[i][match[i]][j]+n,1);
    }
    add(s,x,1);
    for (int i=1; i<=dream[x]; ++i)
        for (int j=1; j<=cnt[x][i]; ++j) add(x,a[x][i][j]+n,1);
    while (bfs()) sum+=get(s,goal);
    return sum==goal;
}

inline void Solve2()
{
    for (int i=1,l,r,mid,ans; i<=n; ++i)
    {
        if (match[i] && match[i]<=dream[i]) ans=0;
        else
        {
            l=1, r=i-1, ans=i;
            while (l<=r)
            {
                mid=(l+r)>>1;
                if (check(i,i-mid)) r=mid-1, ans=mid;
                else l=mid+1;
            }
        }
        write(ans,i==n ? '\n' : ' ');
    }
}

int main()
{
    int T,C;read(T);read(C);
    while (T--)
    {
        Clear();
        InPut();
        Solve1();
        Solve2();
    }
    IO::flush();
    return 0;
}

原文地址:https://www.cnblogs.com/G-hsm/p/11369890.html