1. 程式人生 > >【隨機化】LOJ502 ZQC的截圖

【隨機化】LOJ502 ZQC的截圖

【題目】
原題地址
我需要吐槽一波原題面寫這麼複雜幹嘛,看了我半天
給一棵有根樹,每個節點一種顏色,支援動態加葉子,並且線上回答加入的葉子到根路徑上出現次數不是 3 3 的倍數的顏色有 0 0 個, 1

1 個還是多個,並要求在答案是 1 1 個時輸出該顏色。 m 2 ×
1 0 6 m\leq 2\times 10^6

【解題思路】
考慮一個離線問題,我們直接開陣列維護每種顏色到根出現了多少次,然後 DFS \text{DFS}

一遍即可。如果我們要將這個問題強制線上,必須使用可持久化線段樹或其他可持久化資料結構,那這樣以來時間和空間複雜度都是 O ( n log m ) O(n\log m) 的了,雖然時間可以卡一卡,但空間上會爆炸。

確定性演算法似乎不能做到更優了,一個十分玄學的想法是在模 3 3 意義下給每一種顏色隨機一個權值,然後進行下面的判斷:

  • 到根權和為 0 0 時,說明所有人出現次數都為 3 3 的倍數,答案為 1 -1
  • 若到根權和不為 0 0 ,若存在一個顏色權值的一倍或兩倍和這個值相等,那麼就認為這個顏色是答案
  • 否則說明有不止一種顏色出現次數不為 3 3 的倍數,答案為 2 -2

然而這個演算法正確率實在是太低了!所以我們決定給每一種顏色隨機一個 w w 維的向量。
現在我們來看看這個演算法的正確率:由於加法運算的均勻性,任意有限多個互相獨立的均勻隨機的向量的和還是一個均勻隨機的向量。同時由於 3 3 是質數,向量的數乘也擁有同樣性質。每個點的到根路徑上如果至少有一種顏色出現次數不是 3 3 的倍數,則到根路徑上的權和可以看成一個均勻隨機的向量。它恰好等於 0 0 的概率為 1 3 w \frac {1} {3^w} 。運用同樣的方法,可以證明如果發現到根路徑權和與某個向量的 1 1 倍或 2 2 倍相等,判斷錯誤的概率為 1 ( 1 1 3 w ) n 1-(1-\frac 1 {3^w})^n ,當 w w 很大時這個結果趨近於 n 3 w \frac n {3^w}

於是 m m 次操作中至少出錯一次的概率為 1 ( 1 n 3 w ) m 1-(1-\frac n {3^w})^m ,當 w w 很大時,結果趨近於 n m 3 w \frac {nm} {3^w}

然後我們取 w = 40 w=40 時,出錯的概率大概是 1 0 8 10^{-8} 級別的,當然也可以隨便取取。
於是我們現在只需要用雜湊表維護權值到編號的對映就可以在 O ( w ( n + m ) ) O(w(n+m)) 的時間下得到答案了。
預處理 f i , j f_{i,j} 表示將兩個向量的某維變成 i + j i+j b a s bas 可以提高效率(也就是算出 3 k 3^k )。

【參考程式碼】

#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;
}