挑战NPC(洛谷-P4258)

题目描述

小 N 最近在研究 NP 完全问题,小 O 看小 N 研究得热火朝天,便给他出了一道这样的题目:

有 n 个球,用整数 1 到 n 编号。还有 m 个筐子,用整数 1 到 m 编号。每个筐子最多能装 3 个球。

每个球只能放进特定的筐子中。 具体有 e 个条件,第 i 个条件用两个整数 vi 和 ui 描述,表示编号为 vi 的球可以放进编号为 ui 的筐子中。

每个球都必须放进一个筐子中。如果一个筐子内有不超过 1 个球,那么我们称这样的筐子为半空的。

求半空的筐子最多有多少个,以及在最优方案中, 每个球分别放在哪个筐子中。

小 N 看到题目后瞬间没了思路,站在旁边看热闹的小 I 嘿嘿一笑:“水题!” 然后三言两语道出了一个多项式算法。

小 N 瞬间就惊呆了,三秒钟后他回过神来一拍桌子:“不对!这个问题显然是 NP 完全问题,你算法肯定有错!”

小 I 浅笑:“所以,等我领图灵奖吧!”

小 O 只会出题不会做题,所以找到了你——请你对这个问题进行探究,并写一个程序解决此题。

输入输出格式

输入格式:

输入文件 npc.in 第一行包含 1 个正整数 T, 表示有 T 组数据。

对于每组数据,第一行包含 3 个正整数 nn,m,e, 表示球的个数,筐子的个数和条件的个数。

接下来 e 行,每行包含 2 个整数 vi,ui,表示编号为 vi 的球可以放进编号为 ui 的筐子。

输出格式:

输出文件为 npc.out。

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

然后再输出一行,包含 n 个整数 p1​,p2​,...,pn​,相邻整数之间用空格隔开,表示一种最优解。其中 pi​ 表示编号为 i 的球放进了编号为 pi​ 的筐子。 如果有多种最优解,可以输出其中任何一种。

输入输出样例

输入样例#1:

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

输出样例#1:

2
1 2 3 3

说明

对于所有数据,T≤5,1≤n≤3m。 保证 1≤vi​≤n,1≤ui​≤m,且不会出现重复的条件。

保证至少有一种合法方案,使得每个球都放进了筐子,且每个筐子内球的个数不超过 3。

各测试点满足以下约定: 

思路:

简单来说,就是有 n 个球,m 个筐,一个筐最多装三个球,有 e 个条件表示哪个球能装哪个筐里,要求每个球必须进一个筐,问不超过一个球的筐最多有几个

由于一个筐最多装三个球,因此将每个筐拆成三个点,代表三个空位,然后对每个球可以放入的筐添加三条对应边,再将每个筐的三个点间互相连边,跑带花树算法求最大匹配即可

源代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<bitset>
#define EPS 1e-9
#define PI acos(-1.0)
#define INF 0x3f3f3f3f
#define LL long long
#define Pair pair<int,int>
const int MOD = 1073741824;
const int N = 2000+5;
const int dx[] = {-1,1,0,0,-1,-1,1,1};
const int dy[] = {0,0,-1,1,-1,1,-1,1};
using namespace std;

struct Edge {
    int to,next;
} edge[N*N*2];
int head[N],tot;
int n;//n个点
int father[N],pre[N];//father记录一个点属于哪个一个点为根的花
int Q[N*N*2],first,tail;//bfs队列
int match[N];//匹配
bool odd[N],vis[N];//odd记录一个点为奇点/偶点,1为奇,0为偶
int timeBlock;//LCA时间戳
int top[N],rinedge[N];

void addEdge(int u,int v) {//添边
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int Find(int x){//并查集寻找根节点
    if(father[x]!=x)
        return father[x]=Find(father[x]);
    return x;
}
int lca(int x, int y){//求解最近公共祖先
    timeBlock++;
    while(x){
        rinedge[x]=timeBlock;
        x=Find(top[x]);
    }
    x=y;
    while(rinedge[x]!=timeBlock)
        x=Find(top[x]);
    return x;
}
void blossom(int x, int y, int k) {//将奇环缩成一个点并将原来是奇点的点变为偶点并加入队列
    while(Find(x)!=Find(k)){
        pre[x]=y;

        y=match[x];
        odd[y]=false;
        Q[tail++]=y;

        father[Find(x)]=k;
        father[Find(y)]=k;

        x=pre[y];
    }
}
bool bfs(int s) {
    memset(top,0,sizeof(top));
    memset(pre,0,sizeof(pre));
    memset(odd,false,sizeof(odd));
    memset(vis,false,sizeof(vis));
    for(int i=1;i<=n;i++)
        father[i]=i;

    vis[s]=true;
    first=tail=0;
    Q[tail++]=s;

    while(first!=tail){
        int now=Q[first++];
        for(int i=head[now];i!=-1;i=edge[i].next){
            int to=edge[i].to;
            if(!vis[to]){
                top[to]=now;
                pre[to]=now;
                odd[to]=true;
                vis[to]=true;

                if(!match[to]){
                    int j=to;
                    while(j){
                        int x=pre[j];
                        int y=match[x];
                        match[j]=x;
                        match[x]=j;
                        j=y;
                    }
                    return true;
                }

                vis[match[to]]=true;
                top[match[to]]=to;
                Q[tail++]=match[to];
            }
            else if(Find(now)!=Find(to) && odd[to]==false) {
                int k=lca(now,to);
                blossom(now,to,k);
                blossom(to,now,k);
            }
        }
    }
    return false;
}

int main() {
    int t;
    scanf("%d",&t);
    while(t--){
        memset(head,-1,sizeof(head));
        memset(match,0,sizeof(match));
        tot=0;

        int p,m,e;
        scanf("%d",&p);
        scanf("%d",&m);
        scanf("%d",&e);

        for(int i=1;i<=e;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            y=p+(y-1)*3;
            addEdge(x,y+1); addEdge(y+1,x);
            addEdge(x,y+2); addEdge(y+2,x);
            addEdge(x,y+3); addEdge(y+3,x);
        }
        for(int i=1;i<=m;i++){
            int point=p+(i-1)*3;
            addEdge(point+1,point+2);
            addEdge(point+2,point+1);
        }

        n=p+m*3;//总点数
        int res=0;
        for(int i=1;i<=n;i++)
            if(!match[i])
                res+=bfs(i);
        res-=p;

        printf("%d\n",res);
        for(int i=1;i<=p;i++)
            printf("%d ",(match[i]-p-1)/3+1);
        printf("\n");
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值