【UOJ#550】【UNR#4】网络恢复

随机权值法

题目大意:

交互题。

给定 $n$ 个点 $m$ 条边的随机无向简单图,你不知道哪些点之间有边。

你可以进行以下操作:

  • 给每个结点赋一个权值。记为 $A_0,A_1,\ldots,A_n$。
  • 选取一个边集 $S$。
  • 调用 Query(A,S)。这个函数会返回一个长度为 $n$ 的数组 $B$,其中 $B_i$ 表示与 $i$ 通过 $S$ 中的边直接相连的所有结点的 $A$ 的异或和。

你至多可以调用 $50$ 次 Query(A,S),要求找出所有边(不需要按编号顺序找出)。

题解:

首先考虑一些特殊情况的做法。

当图呈链状的时候,显然对于两端的点的 $B$,它恰好是另一个结点的 $A$,而其它的结点的 $B$ 为某两个 $A$ 的异或和。那么我们只需要找到在 $A$ 中出现的 $B$ 就能找到一条边。然后把这个点删掉继续做即可。

需要找一种赋值方法能使若干不同 $A$ 的异或和不是另一个 $A$。这里在 64 位整数中随机权值即可,冲突的概率极小。

考虑一般图的情况。我们可以用上述方法将图中的一度点都删掉。则图中的点的度数都至少为 $2$。

由于是随机图,它的子图每个点度数都大于等于 $3$ 的概率非常低,因此我们可以认为任意时刻至少有一个点的度数不超过 $2$。

考虑随机一个点 $x$,对于其他点,尝试用 $B$ 异或上 $A_x$。如果此时这个 $B$ 在 $A$ 中出现,说明这个点是二度点且与 $x$ 有边。那么把这条边删掉就又多了一个一度点,从而可以将这个点有关的边全部找出并删掉这个点。

重复此方法直到所有边都弄完即可。

我们如果一次对 $m$ 条边考虑的话,效率较低。由于有 $50$ 次询问可以用,我们将边随机均分为 $50$ 份,对其分别做上述算法即可。

稍微卡卡常就可以在时限内通过。

Code:

#include<bits/stdc++.h>
#include"explore.h"
using namespace std;
#define N NA
const int N=300006;
mt19937_64 mt(time(0)+(size_t)new int);
typedef unsigned long long LL;
int e[N];
LL val[N],b[N];
vector<LL>A,B;struct Hash_Table{
    static const int M=19260819,md=M-2;
    int cnt[M],head[M],nxt[M],tot;
    LL val[M];
    Hash_Table(){tot=0;}
    inline int&operator[](const LL&k){
        const int x=k%md;
        for(int i=head[x];i;i=nxt[i])
            if(val[i]==k)return cnt[i];
        nxt[++tot]=head[x],head[x]=tot,val[tot]=k;
        return cnt[tot];
    }
    inline bool count(const LL&k)const{
        const int x=k%md;
        for(int i=head[x];i;i=nxt[i])
            if(val[i]==k)return 1;
        return 0;
    }
}mp;
set<int>st[N];
int cnt,n,m,K;
int Q[N*20],hd,tl;
void rep(int u,int v){
    if(u>v)swap(u,v);
    if(!st[u].count(v)){
        st[u].insert(v);
        Report(u,v);
        ++cnt;
    }
}
void check1(){
    hd=1,tl=0;
    for(int i=1;i<=n;++i)if(b[i]){
        if(mp.count(b[i]))Q[++tl]=i;
    }
    while(hd<=tl&&cnt<m){
        int u=Q[hd++];
        int fa=mp[b[u]];
        b[u]=0;
        if(!fa)continue;
        rep(u,fa);
        b[fa]^=val[u];
        if(mp.count(b[fa]))Q[++tl]=fa;
    }
}
void check2(){
    for(int T=1;cnt<K;++T){
        int id=mt()%n+1;LL v=val[id];
        bool suc=0;
        for(int i=1;i<=n;++i)
        if(b[i]&&mp.count(b[i]^v)&&i!=id){
            rep(i,id);
            b[i]^=v,b[id]^=val[i];
            suc=1;
        }
        if(suc)check1();
    }
}
void Solve(int nn,int mm){
    n=nn,m=mm;
    for(int i=1;i<=m;++i)e[i]=i;
    shuffle(e+1,e+m+1,mt);
    const int Bl=m/50+1;
    for(int i=1;i<=n;++i)
    val[i]=mt(),A.push_back(val[i]),mp[val[i]]=i;
    for(int l=1,r=Bl;l<=m;l+=Bl,r+=Bl){
        if(r>m)r=m;
        cnt=0;K=r-l+1;
        vector<int>S;
        for(int i=l;i<=r;++i)S.push_back(e[i]);
        B=Query(A,S);
        for(int i=1;i<=n;++i)b[i]=B[i-1];
        check1();
        check2();
    }
}