bzoj4405: [wc2016]挑战NPC

25 篇文章 0 订阅
1 篇文章 0 订阅

题目链接

bzoj4405

题目描述

Description

小N最近在研究NP完全问题,小O看小N研究得热火朝天,便给他出了一道这样的题目:
有n个球,用整数1到n编号。还有m个筐子,用整数1到m编号。
每个筐子最多能装3个球。
每个球只能放进特定的筐子中。具体有e个条件,第i个条件用两个整数vi和ui描述,表示编号为vi的球可以放进编号为ui的筐子中。
每个球都必须放进一个筐子中。如果一个筐子内有不超过1个球,那么我们称这样的筐子为半空的。
求半空的筐子最多有多少个,以及在最优方案中,每个球分别放在哪个筐子中。
小N看到题目后瞬间没了思路,站在旁边看热闹的小I嘿嘿一笑:“水题!”
然后三言两语道出了一个多项式算法。
小N瞬间就惊呆了,三秒钟后他回过神来一拍桌子:
“不对!这个问题显然是NP完全问题,你算法肯定有错!”
小I浅笑:“所以,等我领图灵奖吧!”
小O只会出题不会做题,所以找到了你——请你对这个问题进行探究,并写一个程序解决此题。

Input

第一行包含1个正整数T,表示有T组数据。
对于每组数据,第一行包含3个正整数n,m,e,表示球的个数,筐子的个数和条件的个数。
接下来e行,每行包含2个整数vi,ui,表示编号为vi的球可以放进编号为ui的筐子。

Output

对于每组数据,先输出一行,包含一个整数,表示半空的筐子最多有多少个。

Sample Input

1
4 3 6
1 1
2 1
2 2
3 2
3 3
4 3

Sample Output

2

HINT

对于所有数据,T≤5,1≤n≤3m。保证 1≤vi≤n,1≤ui≤m,且不会出现重复的条件。
保证至少有一种合法方案,使得每个球都放进了筐子,且每个筐子内球的个数不超过 3。
M<=100

题解

我们将每一个篮子拆成1个三元环,由球想三元环上的每个点连边。
如果一个三元环上有一个点或没有点被匹配,则三元环自身能产生一个匹配,也就对应一个半空篮子。这样用带花树算法跑一边一般图的最大匹配就好了。


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;

#define N 610
int f[N];
int get(int x){return f[x]==x?x:f[x]=get(f[x]);}
#define combine(x,y) f[get(x)]=get(y)

struct edge{
    int x,next;
}e[N*N];
int first[N],tot;
void add(int x,int y){
    e[++tot].x=y;
    e[tot].next=first[x];
    first[x]=tot;
}

int mark[N],match[N],next[N],v[N];
int Q[N],p,q,n;
int lca(int x,int y){
    static int t=0;
    for(t++;;swap(x,y)){
        if(!x) continue;
        x=get(x);
        if(v[x]==t) return x;
        v[x]=t; x=next[match[x]];
    }
} 
void group(int a,int p){
    while(a!=p){
        int b=match[a],c=next[b];
        if(get(c)!=p) next[c]=b;
        if(mark[b]==2) mark[Q[++q]=b]=1;
        combine(a,b); combine(b,c);
        a=c;
    }
}
void find(int s){
    for(int i=1;i<=n;i++) f[i]=i,mark[i]=next[i]=v[i]=0;
    Q[p=q=1]=s; mark[s]=1;
    for(;p<=q;p++){
        int x=Q[p];
        for(int i=first[x];i;i=e[i].next){
            int y=e[i].x;
            if(mark[y]==2||match[x]==y||get(x)==get(y)) continue;
            if(mark[y]==1){
                int p=lca(x,y);
                if(get(x)!=p) next[x]=y;
                if(get(y)!=p) next[y]=x;
                group(x,p); group(y,p);
            }
            else if(match[y]){
                next[y]=x; mark[y]=2;
                mark[Q[++q]=match[y]]=1;
            }
            else {
                next[y]=x;
                for(int u=y;u;){
                    int v=next[u],tmp=match[v];
                    match[v]=u; match[u]=v;
                    u=tmp;
                }
                return;
            }
        }
    }
}
void work(){
    int m,t,ans=0,x,y; tot=0;
    memset(first,0,sizeof(first));
    memset(match,0,sizeof(match));
    scanf("%d%d%d",&n,&m,&t);
    for(int i=1;i<=t;i++){
        scanf("%d%d",&x,&y);
        add(x,n+3*y-2); add(n+3*y-2,x);
        add(x,n+3*y-1); add(n+3*y-1,x);
        add(x,n+3*y); add(n+3*y,x);
    }
    for(int i=1;i<=m;i++) add(n+i*3,n+i*3-1),add(n+i*3-1,n+i*3);
    t=n; n=n+m*3;
    for(int i=1;i<=n;i++) if(!match[i]) find(i);
    for(int i=1;i<=n;i++) if(match[i]) ans++;
    printf("%d\n",ans/2-t);
    for(int i=1;i<=t;i++) printf("%d ",(match[i]-t-1)/3+1);
    putchar('\n');
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--) work();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值