• [bzoj4874]筐子放球


    来自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;
    }
  • 相关阅读:
    蓝桥杯 大数定理
    蓝桥杯 密码发生器
    简单定时器的使用
    Eclipse中更改Project Explorer的字体
    列的别名修改
    ||拼接字符串
    SQL知识总结
    java 打开记事本
    报表使用分组
    js处理异步问题
  • 原文地址:https://www.cnblogs.com/FallDream/p/bzoj4874.html
Copyright © 2020-2023  润新知