[WC2016]挑战NPC

前言

这是WC2016的第一题,在场上迅速能发现60分可做。要分成四个部分。最后由于没发现最多只能放三个球就0分了。还有这题不是NPC问题出题人傻逼

题目大意

给定你e个关系第i个关系表明编号为ai的球可以放到编号为bi的筐子里。每个筐子最多放三个球。请你安排方案,让每个球放进一个筐子里,且所装球数不超过1的箱子数最多。
球的个数n<=3*m,筐子的个数m<=100。

巧妙建模

我们将一个筐子拆为三个点,并让它们连边成为三元环。对于每个球建一个点,然后该点对可以放的筐子的点都连上一条边。现在,只要做一般图最大匹配,答案减去n就是答案。
为什么?
假如一个三元环只有不超过一个匹配点,那么显然就会出现一条匹配边,否则就没有匹配边。因此最大匹配减去球数就是所装球数不超过1的箱子的最大值。
至于一般图最大匹配,要使用带花树算法,UOJ有一道模板题。

参考程序



#include<cstdio>
#include<algorithm>
#include<deque>
#include<iostream>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=1000+10,maxm=100000+10;
deque<int> dl;
int h[maxn],go[maxm*2],next[maxm*2],fa[maxn],f[maxn],type[maxn],match[maxn],vis[maxn];
int i,j,k,l,t,n,m,e,tot,ans,cnt,ca,wdc;
void add(int x,int y){
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
void link(int x,int y){
    add(x,y);add(y,x);
}
int getf(int x){
    return f[x]?f[x]=getf(f[x]):x;
}
int lca(int x,int y){
    ++cnt;
    while (x||y){
        if (x){
            x=getf(x);
            if (vis[x]==cnt) return x;
            vis[x]=cnt;
            x=fa[match[x]];
        }
        swap(x,y);
    }
}
void change(int u,int y){
    int v,p;
    while (u!=y){
        v=match[u],p=fa[v];
        if (getf(p)!=y) fa[p]=v;
        if (type[v]==2){
            type[v]=1;
            dl.push_back(v);
        }
        if (!f[u]) f[u]=y;
        if (!f[v]) f[v]=y;
        u=p;
    }
}
int dfs(int x){
    int i,u,v,p;
    fo(i,1,wdc) type[i]=f[i]=fa[i]=0;
    type[x]=1;
    dl.push_back(x);
    while (!dl.empty()){
        u=dl.front();
        dl.pop_front();
        t=h[u];
        while (t){
            v=go[t];
            t=next[t];
            if (type[v]==2||match[v]==u||getf(u)==getf(v)) continue;
            if (type[v]==1){
                p=lca(u,v);
                if (getf(u)!=p) fa[u]=v;
                if (getf(v)!=p) fa[v]=u;
                change(u,p);
                change(v,p);
            }
            else{
                if (!match[v]){
                    while (u){
                        j=fa[match[u]];k=match[u];
                        match[u]=v;match[v]=u;
                        v=k;u=j;
                    }
                    while (!dl.empty()) dl.pop_front();
                    return 1;
                }
                else{
                    fa[v]=u;
                    type[v]=2;
                    type[match[v]]=1;
                    dl.push_back(match[v]);
                }
            }
        }
    }
    return 0;
}
int main(){
    scanf("%d",&ca);
    while (ca--){
        tot=0;
        cnt=0;
        scanf("%d%d%d",&n,&m,&e);
        wdc=3*m+n;
        fill(vis+1,vis+wdc+1,0);
        fill(h+1,h+wdc+1,0);
        fill(match+1,match+wdc+1,0);
        ans=0;
        fo(i,1,m){
            link(3*i-3+1,3*i-3+2);
            link(3*i-3+2,3*i-3+3);
            link(3*i-3+3,3*i-3+1);
        }
        fo(i,1,e){
            scanf("%d%d",&j,&k);
            link(3*m+j,3*k-3+1);
            link(3*m+j,3*k-3+2);
            link(3*m+j,3*k-3+3);
        }
        fd(i,wdc,1)
            if (!match[i]) ans+=dfs(i);
        printf("%d\n",ans-n);
        fo(i,1,n) printf("%d ",(match[3*m+i]-1)/3+1);
        printf("\n");
    }
}

注意

由于我们需要输出方案
所以要先从代表球的点搞不然只能答案正确方案错误!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值