【题目】
原题地址
我需要吐槽一波原题面写这么复杂干嘛,看了我半天
给一棵有根树,每个节点一种颜色,支持动态加叶子,并且在线回答加入的叶子到根路径上出现次数不是
3
3
3的倍数的颜色有
0
0
0个,
1
1
1个还是多个,并要求在答案是
1
1
1个时输出该颜色。
m
≤
2
×
1
0
6
m\leq 2\times 10^6
m≤2×106
【解题思路】
考虑一个离线问题,我们直接开数组维护每种颜色到根出现了多少次,然后
DFS
\text{DFS}
DFS一遍即可。如果我们要将这个问题强制在线,必须使用可持久化线段树或其他可持久化数据结构,那这样以来时间和空间复杂度都是
O
(
n
log
m
)
O(n\log m)
O(nlogm)的了,虽然时间可以卡一卡,但空间上会爆炸。
确定性算法似乎不能做到更优了,一个十分玄学的想法是在模 3 3 3意义下给每一种颜色随机一个权值,然后进行下面的判断:
- 到根权和为 0 0 0时,说明所有人出现次数都为 3 3 3的倍数,答案为 − 1 -1 −1
- 若到根权和不为 0 0 0,若存在一个颜色权值的一倍或两倍和这个值相等,那么就认为这个颜色是答案
- 否则说明有不止一种颜色出现次数不为 3 3 3的倍数,答案为 − 2 -2 −2
然而这个算法正确率实在是太低了!所以我们决定给每一种颜色随机一个
w
w
w维的向量。
现在我们来看看这个算法的正确率:由于加法运算的均匀性,任意有限多个互相独立的均匀随机的向量的和还是一个均匀随机的向量。同时由于
3
3
3是质数,向量的数乘也拥有同样性质。每个点的到根路径上如果至少有一种颜色出现次数不是
3
3
3的倍数,则到根路径上的权和可以看成一个均匀随机的向量。它恰好等于
0
0
0的概率为
1
3
w
\frac {1} {3^w}
3w1。运用同样的方法,可以证明如果发现到根路径权和与某个向量的
1
1
1倍或
2
2
2倍相等,判断错误的概率为
1
−
(
1
−
1
3
w
)
n
1-(1-\frac 1 {3^w})^n
1−(1−3w1)n,当
w
w
w很大时这个结果趋近于
n
3
w
\frac n {3^w}
3wn
于是 m m m次操作中至少出错一次的概率为 1 − ( 1 − n 3 w ) m 1-(1-\frac n {3^w})^m 1−(1−3wn)m,当 w w w很大时,结果趋近于 n m 3 w \frac {nm} {3^w} 3wnm。
然后我们取
w
=
40
w=40
w=40时,出错的概率大概是
1
0
−
8
10^{-8}
10−8级别的,当然也可以随便取取。
于是我们现在只需要用哈希表维护权值到编号的映射就可以在
O
(
w
(
n
+
m
)
)
O(w(n+m))
O(w(n+m))的时间下得到答案了。
预处理
f
i
,
j
f_{i,j}
fi,j表示将两个向量的某维变成
i
+
j
i+j
i+j的
b
a
s
bas
bas可以提高效率(也就是算出
3
k
3^k
3k)。
【参考代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int S=729,M=700001,N=2e6+10;
const ll mod=(ll)150094635296999121;
int n,m,ans;
ll f[S][S],s[N];
int read()
{
int ret=0,f=1;char c=getchar();
while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return f?ret:-ret;
}
ll rnd(){return (ll)rand()*rand()%mod;}
struct Hash
{
int head[M],cnt;
struct node{ll id,v;int nex;}hs[N];
ll find(ll x)
{
int v=x%M;
for(int i=head[v];i;i=hs[i].nex) if(hs[i].id==x) return hs[i].v;
return -1;
}
void insert(ll x,ll val)
{
int v=x%M;
hs[++cnt]=(node){x,val,head[v]};head[v]=cnt;
}
}h1,h2;//h1=id_to_val,h2=val_to_id
ll up(ll x,ll y)//merge x and y
{
ll res=0,bas=1;
for(int i=0;i<6;++i)
{
res+=f[x%S][y%S]*bas;
x/=S;y/=S;bas*=S;
}
return res;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("LOJ502.in","r",stdin);
freopen("LOJ502.out","w",stdout);
#endif
srand(19260817);
for(int i=0;i<S;++i) for(int j=0;j<S;++j)
{
ll now=0,bas=1,ti=i,tj=j;
for(int k=0;k<6;++k)
{
now+=bas*((ti%3+tj%3)%3);
ti/=3;tj/=3;bas*=3;
}
f[i][j]=now;
}
n=read();m=read();
for(int i=1;i<=m;++i)
{
int u=read()^ans,fa=read()^ans;
ll v=h1.find(u);
if(v<0)
{
v=rnd();h1.insert(u,v);
h2.insert(v,u);h2.insert(up(v,v),u);//double
}
s[i]=up(s[fa],v);
if(!s[i]) ans=-1;
else if((ans=h2.find(s[i]))>0);
else ans=-2;
printf("%d\n",ans);
}
return 0;
}