2023NOIP A层联测31-异或连通

给定 K K K,有 n n n 个点 m m m 条边,第 i i i 条边连接 u i , v i u_i, v_i ui,vi,有边权 c i c_i ci

q q q 次询问,每次给出一个 x x x,若 c i ⊕ x < K c_i⊕xcix<K,则这条边存在,否则不存在。其中 ⊕ ⊕ 表示异或。对于每个询问,输出互相连通的点对个数(即有多少 1 ≤ i < j ≤ n 1≤i < j≤n 1i<jn使得 i , j i, j i,j 联通)。

n , m ≤ 1 0 5 , c i , x , K ≤ 1 0 9 n,m\le10^5,c_i,x,K\le10^9 n,m105,ci,x,K109


考虑对于每个 c i c_i ci,放哪些 x x x 是能使 c i ⊕ x < K c_i\oplus xcix<K 恒成立。可以在 01trie 上做,当前是第 i i i 位(前 i − 1 i-1 i1 位保证 c i ⊕ x c_i\oplus x cix K K K 相等)。

  • 若当前位 K K K 1 1 1 c i c_i ci 1 1 1,那么若 x x x 这位取 1 1 1 c i ⊕ x c_i\oplus x cix 恒小于 K K K,在此处打标记。
  • 若当前位 K K K 1 1 1 c i c_i ci 0 0 0,那么若 x x x 这位取 0 0 0 c i ⊕ x c_i\oplus x cix 恒小于 K K K,在此处打标记。
  • 若当前位 K K K 0 0 0 c i c_i ci 只能取 0 0 0

然后把 c i ⊕ x c_i\oplus x cix 当前位变成与 K K K 相同的,就在当前位 x ← c i ⊕ K x\gets c_i\oplus K xciK。发现当 01trie 上一个点被打了标记后,它所对应的边在点下面是一直存在的。

所以我们可以遍历 01trie,每走到一个点就把该点的边加入到并查集中,然后就维护答案,回溯时就把加的边删掉。由于要有删边操作,使用可撤销并查集,具体实现就是用栈记录操作顺序,然后从栈顶依次把操作撤销,注意到不能用路径压缩,要用启发式合并。

时间复杂度 O ( n log ⁡ n log ⁡ V ) O(n\log n\log V) O(nlognlogV) V V V 是值域。

#include
using namespace std;
#define ll long long
const int N=1e5+1;
int n,m,Q,K,q[N];
long long sum,ans[N];
struct node
{
    int u,v,w;
    bool operator<(const node &a)const{
        return w<a.w;
    }
}e[N];
struct dsu
{
    int fa[N],sz[N],cnt;
    pair<int,int> s[N];
    ll ans;
    void init(){for(int i=1;i<=n;i++) fa[i]=i,sz[i]=1;}
    ll calc(ll x){return 1ll*x*(x-1)/2;}
    int find(int x){return fa[x]==x?x:find(fa[x]);}
    void merge(int a,int b){
        int x=find(a),y=find(b);
        if(x==y) return;
        if(sz[x]<sz[y]) swap(x,y);
        ans-=calc(sz[x])+calc(sz[y]);
        fa[y]=x;
        sz[x]+=sz[y];
        s[++cnt]=make_pair(x,y);
        ans+=calc(sz[x]);
    }
    void del(){
        int x=s[cnt].first,y=s[cnt].second;
        cnt--;
        ans-=calc(sz[x]);
        sz[x]-=sz[y];
        fa[y]=y;
        ans+=calc(sz[x])+calc(sz[y]);
    }
    void DEL(int x){while(cnt>x) del();}
}dsu;
struct trie
{
    int tr[31*N][2];
    int cnt=1;
    vector<int> bj[31*N][2];
    ll ans[31*N];
    void insert1(int w,int id)
    {
        int czn=w^K;
        int rt=1;
        for(int i=30;i>=0;i--){
            int x=K>>i&1,t=w>>i&1,y=czn>>i&1;
            if(x&&t) bj[rt][1].push_back(id);
            if(x&&!t) bj[rt][0].push_back(id);
            if(!tr[rt][y]) tr[rt][y]=++cnt;
            rt=tr[rt][y];
        }
    }
    int insert2(int w)
    {
        int rt=1;
        for(int i=30;i>=0;i--){
            int x=w>>i&1;
            if(!tr[rt][x]) tr[rt][x]=++cnt;
            rt=tr[rt][x];
        }
        return rt;
    }
    void getans(int rt)
    {
        ans[rt]=dsu.ans;
        if(tr[rt][0]){
            int now=dsu.cnt;
            for(auto i:bj[rt][0]) dsu.merge(e[i].u,e[i].v);
            getans(tr[rt][0]);
            dsu.DEL(now);
        }
        if(tr[rt][1]){
            int now=dsu.cnt;
            for(auto i:bj[rt][1]) dsu.merge(e[i].u,e[i].v);
            getans(tr[rt][1]);
            dsu.DEL(now);
        }
    }
}tr;
int main()
{
    freopen("xor.in","r",stdin);
    freopen("xor.out","w",stdout);
    cin.tie(0)->sync_with_stdio();
    cin>>n>>m>>Q>>K;
    dsu.init();
    for(int i=1;i<=m;i++){
        cin>>e[i].u>>e[i].v>>e[i].w;
        tr.insert1(e[i].w,i);
    }
    for(int i=1;i<=Q;i++){
        cin>>q[i];
        q[i]=tr.insert2(q[i]);
    }
    tr.getans(1);
    for(int i=1;i<=Q;i++) cout<<tr.ans[q[i]]<<"\n";
}

你可能感兴趣的:(算法,数据结构)