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)\rightarrow O(n)\)

用什么来存储呢?

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

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

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

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

查询的时候同理。

建树复杂度为 \(O[nm\alpha(n)]\) ,查询复杂度为 \(O[qn\alpha(n)\log m]\) ,总时间复杂度为 \(O[\alpha(n)n(m+q\log 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\n",W[*ia]);
    }
    return 0;
}
posted @ 2020-11-27 19:37  jasony_sam  阅读(174)  评论(0编辑  收藏  举报