UOJ#171. 【WC2016】挑战NPC(带花树)

传送门

题解:
利用奇环的性质来达到二分图中诸如每个点匹配多少个点之后贡献是多少的限制。
因为最大匹配随着点数增加而增加,我们应该想办法使得在特定情况少增加一个。
对于本题每个桶构造一个三角形,每个能放入桶中的球连三个点,发现匹配 0,1 0 , 1 个球时这个三角形和与其相匹配的点减去匹配数等于 2 2 ,否则等于3,很容易就可以列出计算贡献的式子。
由于新图不是二分图,采用带花树解决最大匹配,时间复杂度 O(n3) O ( n 3 ) .

还有一点需要注意的是如果要输出方案的话每个不是桶的点必须有匹配,根据增广路性质不会使得路上的匹配点变为不匹配,因此我们先增广不是桶的点即可。

#include <bits/stdc++.h>
using namespace std;
inline int rd() {
    char ch=getchar(); int i=0,f=1;
    while(!isdigit(ch)) {if(ch=='-')f=-1; ch=getchar();}
    while(isdigit(ch)) {i=(i<<1)+(i<<3)+ch-'0'; ch=getchar();}
    return i*f;
}
const int N=1e3+50;
vector <int> edge[N];
int n,m,nn,id[N],mate[N],pre[N],anc[N];
queue <int> q;
inline void add(int x,int y) {edge[x].push_back(y); edge[y].push_back(x);}
inline int getanc(int x) {return (anc[x]==x)?x:(anc[x]=getanc(anc[x]));}
inline void merge(int x,int y) {anc[getanc(x)]=getanc(y);}
inline int lca(int x,int y) {
    static int vis[N],vt; ++vt;
    while(x) {
        if(vis[x]==vt) return x;
        vis[x]=vt; x=getanc(pre[mate[x]]);
        if(!x) swap(x,y);
    }
}
inline void group(int x,int f) {
    while(getanc(x)!=f) {
        int y=mate[x], z=pre[y];
        if(getanc(z)!=f) pre[z]=y;
        if(id[y]) id[y]=0, q.push(y);
        if(id[z]) id[z]=0, q.push(z);
        merge(x,y); merge(y,z); x=z;
    }
}
inline bool bfs(int x) {
    for(int i=1;i<=nn;i++) id[i]=-1,anc[i]=i;
    while(!q.empty()) q.pop();
    pre[x]=0; q.push(x); id[x]=0;
    while(!q.empty()) {
        int u=q.front(); q.pop();
        for(int e=edge[u].size()-1;e>=0;e--) {
            int v=edge[u][e];
            if(!~id[v]) {
                id[v]=1; pre[v]=u;
                if(!mate[v]) {
                    while(u) {
                        int t=mate[u];
                        mate[u]=v; mate[v]=u;
                        v=t; u=pre[v];
                    }
                    return true;
                } 
                id[mate[v]]=0; q.push(mate[v]);
            } else if(!id[v] && getanc(u)!=getanc(v)) {
                int l=lca(getanc(u),getanc(v));
                if(getanc(u)!=l) pre[u]=v;
                if(getanc(v)!=l) pre[v]=u;
                group(u,l); group(v,l);
            }
        }
    }
    return false;
}
inline void solve() {
    n=rd(), m=rd(); nn=3*m+n; int ans=0;
    for(int i=1;i<=nn;i++) edge[i].clear(), mate[i]=0;
    for(int i=rd();i;i--) {
        int x=rd(), y=rd();
        add(x+3*m,y); add(x+3*m,y+m); add(x+3*m,y+2*m);
    }
    for(int i=1;i<=m;i++) {
        add(i,i+m); add(i+m,i+2*m); add(i+2*m,i);
    }
    for(int i=nn;i;i--) if(!mate[i]) ans+=bfs(i);
    printf("%d\n",ans-n);
}
int main() {
    for(int T=rd();T;T--) solve();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值