• CF687D Dividing Kingdom II


    洛谷传送门 CF传送门

    Solution

    题解里都是暴力的做法,这里也介绍一下。

    既然要求找最小的边,那么必然要将 ((l,r)) 的边从大到小排序,然后将每条边的两个端点尽量分到不同的集合,直到分不了,就可以输出答案了。

    时间复杂度:(O(mq))

    要不是时限 (6) 秒,就都得没了


    但是想法是好的,我们可以沿着这个想法继续思考。

    将要加入并查集的边分为三种:

    1. (u)(v) 不在同一个连通块里,那么将连通块联通,并标记 (u,v) 不在同一集合
    2. (u)(v) 在同一连通块中,但是不在同一集合,那么跳过即可
    3. (u)(v) 在同一连通块中,但是在一个集合里,输出这条边

    我们发现第 (1) 种边最多有 (n-1) 条,第 (3) 种边有 (1) 条,并且最后的答案和第 (2) 种边是没有关系的。

    也就是说第 (1) 种的这 (n-1) 边就足以确定那个并查集了。

    那么将这 (n-1) 条边和第 (3) 种边,即总共 (n) 条边存下来最后用来判断答案,就可以将复杂度 (O(m) ightarrow O(n))

    用什么来存储呢?

    我们发现还有一个细节没说——它是对于一个区间来询问的

    哦,那就用线段树存吧(**)

    但是问题是在建树时怎么向上pushup呢?

    我们需要维护每个区间最多不超过 (n) 条边而且边还要从大到小排序。排序好说,在合并两个区间的边时,归并排序即可;那么当两个区间合并时,怎么判断剩下的是哪几条边呢?还是拿并查集来维护嘛╮(╯-╰)╭

    查询的时候同理。

    建树复杂度为 (O[nmalpha(n)]) ,查询复杂度为 (O[qnalpha(n)log m]) ,总时间复杂度为 (O[alpha(n)n(m+qlog m)])

    Code

    #include<cstdio>
    #include<vector>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ls rt<<1
    #define rs rt<<1|1
    #define VI vector<int>
    
    using namespace std;
    const int N=1010,M=500010;
    int n,m,q,X[M],Y[M],W[M],f[N],g[N],p[M];
    VI tr[M<<2];
    VI::iterator ia,ib;
    
    inline int read(){
        int x=0,f=1;
        char ch=getchar();
        while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
        return x*f;
    }
    
    int find(int x){
        if(f[x]==x) return x;
        int t=f[x];
        f[x]=find(t);
        g[x]^=g[t];
        return f[x];
    }
    
    inline VI merge(VI a,VI b){
        int cnt=0;
        VI res;
        for(ia=a.begin();ia!=a.end();ia++)
            f[X[*ia]]=X[*ia],f[Y[*ia]]=Y[*ia],g[X[*ia]]=g[Y[*ia]]=0;
        for(ia=b.begin();ia!=b.end();ia++)
            f[X[*ia]]=X[*ia],f[Y[*ia]]=Y[*ia],g[X[*ia]]=g[Y[*ia]]=0;
        for(ia=a.begin(),ib=b.begin();ia!=a.end()||ib!=b.end();){
            if(ia!=a.end()&&(ib==b.end()||W[*ia]>W[*ib]))
                 p[++cnt]=*ia,ia++;
            else p[++cnt]=*ib,ib++;         //归并排序思想
        }
        for(int i=1,x,y;i<=cnt;i++){
            x=X[p[i]],y=Y[p[i]];
            if(find(x)!=find(y)) g[find(x)]=g[x]^g[y]^1,f[f[x]]=f[y],res.push_back(p[i]);
            else if(g[x]!=g[y]) continue;
            else{
                res.push_back(p[i]);
                break;
            }
        }
        return res;
    }
    
    void build(int rt,int l,int r){
        if(l==r){
            tr[rt].push_back(l);
            return ;
        }
        int mid=(l+r)>>1;
        build(ls,l,mid); build(rs,mid+1,r);
        tr[rt]=merge(tr[ls],tr[rs]);
    }
    
    VI query(int rt,int l,int r,int L,int R){
        if(L<=l&&r<=R) return tr[rt];
        int mid=(l+r)>>1;
        if(R<=mid) return query(ls,l,mid,L,R);
        if(L>mid) return query(rs,mid+1,r,L,R);
        return merge(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));
    }
    
    int main(){
        VI t;
        n=read(); m=read(); q=read();
        for(int i=1;i<=m;i++)
            X[i]=read(),Y[i]=read(),W[i]=read();
        build(1,1,m);
        for(int i=1,l,r,x,y;i<=q;i++){
            l=read(),r=read();
            t=query(1,1,m,l,r);
            for(ia=t.begin();ia!=t.end();ia++) f[X[*ia]]=X[*ia],f[Y[*ia]]=Y[*ia];
            for(ia=t.begin();ia!=t.end();ia++){
                x=X[*ia],y=Y[*ia];
                if(find(x)==find(y)) break;
                f[find(x)]=find(y);
            }
            if(ia==t.end()) puts("-1");
            else printf("%d
    ",W[*ia]);
        }
        return 0;
    }
    
  • 相关阅读:
    航班预定统计(差分数组+前缀和)
    救生艇
    Xcode 的正确打开方式——Debugging
    多次页面跳转后pop回主界面的问题
    理解Bitcode:一种中间代码
    使用AFNetWorking读取JSON出现NSCocoaErrorDomain Code=3840的解决方法
    No identities are available for signing的解决方法
    Aufree/trip-to-iOS
    Alcatraz -- 一个神奇的管理插件的Xcode插件
    GenericKeychain
  • 原文地址:https://www.cnblogs.com/jasony/p/14049942.html
Copyright © 2020-2023  润新知