1 条题解

  • 0
    @ 2025-8-24 21:27:25

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Windows_XP
    **

    搬运于2025-08-24 21:27:24,当前版本为作者最后更新于2018-03-12 16:11:41,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    这题为什么要二分图呢?

    太大了好像划不住

    这题我们bfs一下就好了。二分图又不好写,还慢的要死,还丑,不如好写稳定的bfs。

    说是bfs其实就是乱搞。

    以下几段通俗易懂可爱面向萌新。大佬可以直接跳过看总结。

    一个装备只能提供一个属性。我们把两个属性值当做两个点来连线,那么我们可以感性地想象这条线上有一只猫,这只猫要不然趴在一头,要不然趴在另一头,哪个值被覆盖代表着这个装备提供哪个属性值。如果将属性值来这样建一张图,对于每一个连通图,因为每一只猫都能覆盖旁边的一个点,我们可以很轻松的想象到,如果说这个图中有m条边,n个点,那么一定可以有min(n,m)个点被覆盖。

    除了一个树以外,图上的边数一定大于等于点数。显然吧!那么说明图中的所有点都是可以被猫猫搞到的,树中有且只有一个点无法被覆盖。根据贪心的思想我们当然是让那个最大的值的那个点无法被覆盖。我们可以用bfs 通过对遍历到的边数和点数的比较 来维护这个过程,(不知道怎么维护可以看代码我会尽量的!写的非常!详细 毕竟我想过审核)

    而对于树中最大的点如何处理呢?每一次bfs都会遍历整个连通块,其他的bfs是够不到这个最大点的。所以最大的那个点无法被其他的bfs覆盖。那么我们将它的状态视为cant,即它绝对无法被覆盖。

    没有与任何点相连的点,也可以看做一个树。

    于是我们可以从1开始,向10000遍历。每一个值都是属性值,将每一个值都看做点。如果当前这个点没有被bfs过就bfs它 废话,要不然就判断是否是cant点。

    总结在这

    总结:将属性值连边,每一个强联通分量内的所有点都可选,每一个树内最大点不可选,用bfs判断。根本就不用缩点。(并查集可能会更快?我写不明白..)每一条边与每一个点都至多会被判断一次,上界复杂度O(n+m) (要是2就cant了,那么当然后面全不用判了)。

    多简单...

    说实话,处理结果的思路与楼下用并查集的大佬们的思路很像,但是又略有不同。严格地说并不是同一种方法。

    代码!

    #include <bits/stdc++.h> //我就好这一口
    #define rap(i,s,n) for(int i=s;i<=n;i++)//同上
    #define drap(i,s,n) for(int i=s;i>=n;i--)//同上
    #define N 23333
    #define M 2333333
    using namespace std;
    char xB[1<<15],*xS=xB,*xTT=xB;//读优 原因同上
    #define getc() (xS==xTT&&(xTT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xTT)?0:*xS++)
    #define isd(c) ((c>='0'&&c<='9')||(c=='-'))
    template<typename T>
    inline void rd(T & xaa){
        char xchh; T f=1; xaa=0; while(xchh=getc(),!isd(xchh));
        if(xchh=='-'){f=-1; xchh=getc();} xaa=xchh-'0';
        while(xchh=getc(),isd(xchh)) xaa=xaa*10+xchh-'0';
        xaa*=f; return;
    }
    int m,to[M],nxt[M],head[N],cnt;//存图
    bool vis[N],cant[N];//bfs用的
    void add(int a,int b){cnt++; to[cnt]=b; nxt[cnt]=head[a]; head[a]=cnt; return;}
    bool bfs(int k){//返回是否能覆盖
    	//printf("bfs(%d)\n",k); 
        if(!vis[k]){
            vis[k]=1; int maxp=k,nump=0,nume=0; queue<int>q; q.push(k);
            //maxp:涉及到的最大的点。设成0不会影响结果。
            //nump:点数 nume:边数
            while(!q.empty()){
                int u=q.front(); q.pop(); maxp=max(maxp,u); nump++;
                for(int i=head[u];i;i=nxt[i]){nume++; if(!vis[to[i]]) vis[to[i]]=1,q.push(to[i]);}
            }
            //每bfs一个点都让nump++(显然),每碰到一条边都让nume++。
            //但是因为是双向图,每条边都碰到了两遍。所以/2
            nume>>=1; if(nume<nump) cant[maxp]=1;
            //rap(i,1,maxp) printf("%d ",vis[i]); printf("\n");
        	//rap(i,1,maxp) printf("%d ",cant[i]); printf("\n");
        }
        return (!cant[k]);//判断当前点是否不可覆盖(其实只有它自己孤零零不和其他点相连才是0...
    }
    int main(){
        //freopen("1640.in","r",stdin);
        rd(m); int a,b; rap(i,1,m){rd(a),rd(b); add(a,b); add(b,a);}
        int k=1; while((vis[k]&&!cant[k])||bfs(k)) k++; printf("%d\n",k-1);
        //注意while中的判断。只有vis过才能判断cant值,否则cant值没有意义。没有vis过就会bfs。
        return 0;
    }
    

    点个赞吧 ^^(用一个PAFF酱的表情)

    • 1

    信息

    ID
    632
    时间
    1000ms
    内存
    128MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者