cogimyunの小窝

Loading...

P14532 [RMI 2018] 颜色 / Colors 题解

题意简述:

一个 \(n\)\(m\) 条边的无向连通图,每个点有权值 \(a_i\),如果存在边 \((u,v)\) 可以进行操作 \(a_u\leftarrow \min(a_u,a_v)\),问是否可以将 \(a_i\) 变为 \(b_i\)

\(n\le 1.5\times 10^5,m\le 2\times 10^5\)

前置知识:

线段树分治、可撤销并查集。

暴力(47 pts)

考虑将 \(a_u\) 变为 \(b_u\) 的充要条件是:

\[\exists v\ s.t. \ a_v=b_u\wedge\forall i\in l(u,v)\ b_u\in[b_i.a_i] \]

其中记 \(l(u,v)\) 为一条 \(u\)\(v\) 的路径所经过点的集合,此时我们可以通过令 \(i\in l(u,v)\)\(a_i=b_u\) 来传递 \(b_u\),但不会使 \(a_i\) 过低而导致 \(a_i\) 无法通过操作 \(\min(a_u,a_v)\) 变成 \(b_i\)

一个朴素的想法是从每个节点 \(i\) 出发进行 bfs,按照上面的条件找到所有可以到达的节点 \(j\),这样的时间复杂度是 \(O(\sum n^2)\) 的,理论可以获得 47 分。但是 Subtask 7 的测试点 1 数据有误 \(nm\) 达到了\(2\times 10^{10}\)

线段树分治 + 可撤销并查集优化 (100pts)

接下来考虑优化,由于我们比较关注连通性,于是考虑一条边 \((u,v)\) 什么时候是可以用于连接 \(u\)\(v\) 的,假设当前需要传递 \(x\),发现需要满足:

\[x\in[b_u,a_u]\cup[b_v,a_v] \]

即:

\[x\in[\max(b_u,b_v),\min(a_u,a_v)] \]

这样我们不难想到线段树分治,\((u,v)\) 这条边就作用于区间 \([\max(b_u,b_v),\min(a_u,a_v)]\) 上,每次进入一个新的节点,将该节点存储的边连上,离开时就断开连边,这里的连通性可以使用可撤销并查集维护。

至于判断可达性,当目前的线段树节点代表单点 \(x\) 时,就是在判断 \(x\) 的传递,我们可以对于所有 \(a_i=x\)\(i\),将其祖先时间戳标为 \(x\),然后对于所有 \(b_i=x\)\(i\),如果 \(i\) 的祖先时间戳为 \(x\) 就证明 \(a_i\) 可以变为 \(b_i\),这部分 \(a_i=x\)\(b_i=x\) 的信息可以使用 vector 记录。

这样总体时间复杂度为 \(O(\sum n\log^2 n)\),可以获得 100 分。

Code

#include<bits/stdc++.h>
using namespace std;
int tt,n,m,a[150005],b[150005],vis[150005];
vector<int> ra[150005],rb[150005];
vector<int> tr[600005];
struct node{
    int u,v;
}s[200005];
bitset<150005> fl;
class dsu{
private:
    int f[150005],sz[150005];
    stack<pair<int,int>> q;
public:
    void init(){for(int i=1;i<=n;i++)f[i]=i,sz[i]=1;}
    int getf(int x){while(f[x]!=x)x=f[x];return x;}
    void merge(int u,int v){
        if(u==v)return;
        if(sz[u]<sz[v])swap(u,v);
        f[v]=u;
        sz[u]+=sz[v];
        q.push({u,v});
    }
    void del(){
        int u=q.top().first,v=q.top().second;
        q.pop();
        f[v]=v;
        sz[u]-=sz[v];
    }
    int get_sz(){return q.size();}
}T;
void add(int p,int s,int t,int l,int r,int id){
    if(l<=s&&t<=r){
        tr[p].push_back(id);
        return;
    }
    int mid=(s+t)>>1;
    if(l<=mid)add(p<<1,s,mid,l,r,id);
    if(r>mid)add(p<<1|1,mid+1,t,l,r,id);
}
void solve(int p,int l,int r){
    int st=T.get_sz();
    for(auto i:tr[p]){int fu=T.getf(s[i].u),fv=T.getf(s[i].v);T.merge(fu,fv);}
    if(l==r){
        for(auto i:ra[l])vis[T.getf(i)]=l;
        for(auto i:rb[l])if(vis[T.getf(i)]==l)fl[i]=1;
    }else{
        int mid=(l+r)>>1;
        solve(p<<1,l,mid);
        solve(p<<1|1,mid+1,r);
    }
    while(T.get_sz()>st)T.del();
    tr[p].clear();
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>tt;
    while(tt--){
        cin>>n>>m;
        for(int i=1;i<=n;i++)ra[i].clear(),rb[i].clear();
        for(int i=1;i<=n;i++)cin>>a[i],ra[a[i]].push_back(i);
        for(int i=1;i<=n;i++)cin>>b[i],rb[b[i]].push_back(i);
        for(int i=1;i<=m;i++){
            int x,y;
            cin>>x>>y;
            s[i]={x,y};
            if(max(b[x],b[y])<=min(a[x],a[y]))add(1,1,n,max(b[x],b[y]),min(a[x],a[y]),i);
        }
        T.init();
        fl.reset();
        memset(vis,0,sizeof vis);
        solve(1,1,n);
        int flag=true;
        for(int i=1;i<=n&&flag;i++)flag&=fl[i];
        if(flag)cout<<"1\n";
        else cout<<"0\n";
    }
    return 0;
}
posted @ 2026-03-10 15:47  cogimyun  阅读(12)  评论(0)    收藏  举报