[bzoj4874]筐子放球

来源:互联网 发布:java 接口开发 编辑:程序博客网 时间:2024/05/01 20:02

题目描述

小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);}
原创粉丝点击