[八省联考2018]劈配

[八省联考2018]劈配 

并不难的一道题,甚至比一双木棋还容易一些

关键点就是一个:怎么处理“最优结果”,即总体字典序最小

显然要反悔。匹配问题反悔套路就多了

 

法一:

变形匈牙利算法

记录右部点导师的战队情况

枚举每个人,枚举每一层进行尝试匹配

最多把前面的人的边都遍历到,总共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('\n');}

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
*/
View Code

 

法二:

动态加边dinic最大流

第一问就是把匈牙利变成dinic,据说要把匹配失败的边删除防止TLE

第二问:

二分。用vector存第一问每次图的边

一次性把前s[i]个边都加入,跑dinic

 

技巧:

删除的话,只用vector.pop_back即可。其他的边,由于找不到增广路,所以依然流量守恒的

 

 

题面很长,实际不难

就是考察一个匹配的反悔机制了

暴力分还很多、、、

posted @ 2019-03-25 19:38  *Miracle*  阅读(240)  评论(0编辑  收藏  举报