[bzoj4874]筐子放球

题目描述

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

题解

我们把筐子看做点,球看做边,得到一副无向图。
对于一个联通块,如果有偶数条边,可以让这个联通块不产生任何半空点。
假设一种方案使得半空点存在,由于总数为偶数,一定有另一个半空点也存在。更大的,半空点数量一定是偶数。我们找到这两个半空点之间的一条路径,显然可以通过调整使得半空点被一条边连接,然后改变这条边的去向,可以使得两个半空点均被消除。这样任意次可以消除图中所有半空点。
如果一个联通块有奇数条边,那么无论如何都会存在一个半空点。
假如该联通块就是一颗树,每个点都根据到儿子边的去向,来决定到父亲边的去向,来让这个点不是半空点,最后根节点无法这样做,因此会有一个半空点。
如果不是一颗树,可以造出一颗生成树,然后删去任意一条非树边,剩余还是联通图且边数为偶数,根据之前的证明可以不存在半空点,加上这条被删去的边后会存在一个半空点。
因此只要统计奇数条边的联通块个数就是答案。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=200000+10;
int h[maxn],go[maxn*2],next[maxn*2];
bool bz[maxn],pd[maxn];
int i,j,k,l,t,n,m,tot,ans;
void add(int x,int y){
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
void dfs(int x){
    pd[x]=1;
    int t=h[x];
    while (t){
        if (!bz[(t+1)/2]){
            bz[(t+1)/2]=1;
            l++;
            if (!pd[go[t]]) dfs(go[t]);
        }
        t=next[t];
    }
}
int main(){
    scanf("%d%d",&n,&m);
    fo(i,1,n){
        scanf("%d%d",&j,&k);
        add(j,k);add(k,j);
    }
    fo(i,1,m)
        if (!pd[i]){
            l=0;
            dfs(i);
            if (l%2==1) ans++;
        }
    printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值