P4258 [WC2016]挑战NPC

题目链接


题目大意:
有n个球,用整数1到n编号。还有m个筐子,用整数1到m编号。每个筐子最多能装3个球。每个球只能放进特定的筐子中。 具体有e个条件,第i个条件用两个整数 vi和ui描述,表示编号为vi的球可以放进编号为ui 的筐子中。每个球都必须放进一个筐子中。如果一个筐子内有不超过 11 个球,那么我们称这样的筐子为半空的。求半空的筐子最多有多少个,以及在最优方案中, 每个球分别放在哪个筐子中。
解题思路
要用到拆点,因为每个框能装3个球,所以要把框拆成3个点,每个点之间互相连接,同时如果球能装进框里,则球也要与3个框连接,又因为如果一个筐半空,那么拆成的三个点会有两个以上没有和球匹配,那么它们自身能构成一个匹配,每个球都能放进去,求一下原图的最大匹配,答案就是最大匹配-球数。 参考博客1
参考博客2
参考博客3
#include<bits/stdc++.h>

using namespace std;

const int maxn = 1e3+5;
const int maxm = 4e5+5;

struct Node{
    int x,y;
}p[maxn];

struct Edge{
    int next,to;
}e[maxm];

int n,m,qe,tot,ans,Id,sum;
int head[maxn],match[maxn],id[maxn],pre[maxn],f[maxn],vis[maxn];
queue<int> q;

void init(){
	Id=tot=0;
	for(int i=1;i<=n+3*m;i++)
		head[i]=id[i]=match[i]=0;
}

void add(int u,int v){
    e[tot].to=v;e[tot].next=head[u];
    head[u]=tot++;
    e[tot].to=u;e[tot].next=head[v];
    head[v]=tot++;
}

int find(int x){
    return f[x]==x?x:f[x]=find(f[x]);
}

int Lca(int x,int y){
    for(++Id;;swap(x,y)){
        if(x){
            x=find(x);
            if(id[x]==Id)return x;
            else id[x]=Id,x=pre[match[x]];
        }
    }
}

void blossom(int x,int y,int l){
    while(find(x)!=l){
        pre[x]=y,y=match[x];
        if(vis[y]==2)vis[y]=1,q.push(y);
        if(find(x)==x){
            f[x]=l;
        }
        if(find(y)==y){
            f[y]=l;
        }
        x=pre[y];
    }
}

bool bfs(int s){
    for(int i=1;i<=sum;i++){
        vis[i]=pre[i]=0,f[i]=i;
    }
    while(!q.empty())q.pop();
    q.push(s);
    vis[s]=1;
    while(!q.empty()){
        int now=q.front();q.pop();
        for(int i=head[now];i;i=e[i].next){
            int v=e[i].to;
            if(find(now)==find(v)||vis[v]==2)continue;
            if(!vis[v]){
                vis[v]=2;pre[v]=now;
                if(!match[v]){
                    for(int x=v,last;x;x=last){
                        last=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;
                    }
                    return 1;
                }
                vis[match[v]]=1,q.push(match[v]);
            }else{
				int lca=Lca(now,v);
                blossom(now,v,lca);blossom(v,now,lca);
            }
        }
    }
    return 0;
}

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d",&n,&m,&qe);
        init();
        while(qe--){
            int u,v;
            scanf("%d%d",&u,&v);
            add(u,n+v);
            add(u,n+m+v);
            add(u,n+m*2+v);
        }
        for(int i=1;i<=m;i++){
            add(n+i,n+m+i);
            add(n+i,n+2*m+i);
            add(n+m+i,n+2*m+i);
        }
        sum=n+3*m;
        ans=-n;
        for(int i=1;i<=sum;i++){
            if(!match[i]&&bfs(i)){
                ans++;
            }
        }
        printf("%d\n",ans);
        for(int i=1;i<=n;i++){
            printf("%d%c",(match[i]-n-1)%m+1,i==n?'\n':' ');
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值