来自FallDream的博客,未经允许,请勿转载,谢谢。
小N最近在研究NP完全问题,小O看小N研究得热火朝天,便给他出了一道这样的题目:
有 n 个球,用整数 1 到 n 编号。还有 m 个筐子,用整数1到m编号。
每个球只能放进特定的两个筐子之一,第 i 个球可以放进的筐子记为 Ai 和 Bi 。
每个球都必须放进一个筐子中。
如果一个筐子内有奇数个球,那么我们称这样的筐子为半空的。
求半空的筐子最少有多少个。
小N看到题目后瞬间没了思路,站在旁边看热闹的小I嘿嘿一笑:"水题!"
然后三言两语道出了一个多项式算法。
小N瞬间就惊呆了,三秒钟后他回过神来一拍桌子:
"不对!这个问题显然是NP完全问题,你算法肯定有错!"
小I浅笑:"所以,等我领图灵奖吧!"
小O只会出题不会做题,所以找到了你--请你对这个问题进行探究,并写一个程序解决此题
n,m<=200000
感觉是一道智商题 所以来做了做
想了半天只会一个log的做法
考虑线性基 每次有一个球可以选择a,b(a<b),就先把a的权值塔上1,然后加入只有a和b位是1的线性基
直接暴力插入是n^2,但是发现一个点出发的很多条边,在选择了一个基之后,假设基是u->v,那么u->其他点的边都变成了v->其他点的边,所以可并堆维护即可,复杂度nlogn
当然只有这道题才能满足线性基贪心
然后去搜了搜题解,发现答案就是有奇数条边的联通块个数
因为偶数条边一定能满足两两配对 应该是可以证明的吧
所以直接dfs就行了 复杂度O(n)
线性基+可并堆
#include<iostream> #include<cstdio> #include<queue> #define INF 2000000000 #define Dis(x) (x?x->dis:0) #define MN 200000 using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } int n,m,s[MN+5],to[MN+5],ans=0; struct Tree { Tree *l,*r;int dis,x; Tree(int k){dis=0;x=k;l=r=NULL;} int top(){return x;} friend Tree* Merge(Tree*a,Tree*b) { if(!a) return b; if(!b) return a; if(a->x>b->x) swap(a,b); a->r=Merge(a->r,b); if(Dis(a->l)<Dis(a->r)) swap(a->l,a->r); a->dis=Dis(a->r)+1; return a; } Tree* pop(){return Merge(l,r);} Tree* ins(int x){return Merge(new Tree(x),this);} }*rt[MN+5]; int main() { m=read();n=read(); for(int i=1;i<=n;i++) s[i]=1,rt[i]=new Tree(INF); for(int i=1;i<=m;++i) { int x=read(),y=read(); if(x>y) swap(x,y); s[x]^=1;rt[x]=rt[x]->ins(y); } for(int i=1;i<=n;++i) { while(rt[i]->top()==i) rt[i]=rt[i]->pop(); int x=rt[i]->top(); if(x==INF) continue; to[i]=x; rt[x]=Merge(rt[x],rt[i]); } for(int i=1;i<=n;++i) { if(to[i]&&!s[i]) s[i]^=1,s[to[i]]^=1; ans+=s[i]; } cout<<n-ans<<endl; return 0; }
靠谱做法
#include<iostream> #include<cstdio> #define getchar() (*S++) #define MN 200000 char B[1<<26],*S=B; using namespace std; inline int read() { int x = 0; char ch = getchar(); while(ch < '0' || ch > '9') ch = getchar(); while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x; } int head[MN+5],ans=0,sum=0,cnt=0,n,m; struct edge{int to,next,w;}e[MN*2+5]; bool mark[MN+5]; inline void ins(int f,int t) { e[++cnt]=(edge){t,head[f]};head[f]=cnt; e[++cnt]=(edge){f,head[t]};head[t]=cnt; } void dfs(int x) { mark[x]=1; for(int i=head[x];i;i=e[i].next,++sum) if(!mark[e[i].to]) dfs(e[i].to); } int main() { fread(B,1,1<<26,stdin); m=read();n=read(); for(register int i=1;i<=m;++i) ins(read(),read()); for(register int i=1;i<=n;++i)if(!mark[i]) { sum=0;dfs(i); ans+=((sum/2)&1); } cout<<ans; return 0; }