1. 程式人生 > 其它 >【題解】「JOISC 2019 Day4」礦物

【題解】「JOISC 2019 Day4」礦物

互動題,給定 \(N\) 種顏色,每種顏色恰好 \(2\) 個球,每次可以向集合中插入/刪除一個球,然後得到集合中有多少種顏色。你需要在 \(10^6\) 次操作內將球兩兩配對 \(N\le 4.3\times 10^4\)

首先不難想到生日悖論,每次隨機向集合中加入一個球,當集合中出現相同的球時就配對,然後清空集合。這樣做期望次數是 \(\mathcal{O}(N\sqrt N)\),實際得分 \(6\) 分。

這個資料範圍顯然是留給 \(\log\) 做法的,我們優先考慮分治。現在我們 solve(S) 表示將集合 \(S\) 中的球配對。假設一共有 \(n\) 對,取 \(m = n / 2\)

,將集合中的球依次加入直到有 \(m\) 對同色球,然後再掃一遍將集合中沒有配對的球刪去就可以得到 \(\mathcal{O}(N\log N)\) 的演算法。但是常數非常大,只能得到 \(40\) 分。

想辦法優化常數,如果我們在呼叫 solve(S) 的時候,已經將每對球中恰好一個球加入集合中,那麼我們掃一遍可以同時分出兩組 \(m\) 對球,並且每對球都是恰好一個在集合中。這樣做每次 solve 的操作次數都是嚴格小於 \(|S|\) 次,但由於毒瘤出題人,仍只有 \(40\) 分,但是操作次數已經由 \(2\times 10^6\) 優化到 \(1.3\times 10^6\)

我們發現操作多的原因在於掃的時候,如果一個球不能配對,就需要再操作一次把它放回集合。我們觀察一下發現,我們不需要每對球恰好一個在集合中,我們只需要將 \(S\)

能分成的兩組 \(A,B\) 使得每組中不存在相同的顏色,而這可以通過最開始掃一遍將每個球染色即可。這樣每次 solve 的操作次數都是嚴格小於 \(\frac{3}{4}|S|\),可以得到 \(85\) 分。

由於出題人喪心病狂的卡常,最後一個點多了大概一萬次操作我們注意到每次 solve\(\frac{3}{4}\) 的常數,那麼我們每次在中點分治並不是最優的,將分治點偏移一點,調參就過了。

#define S 86005
mt19937 rd(time(0));
int v[S], lst = 0, idx, id[S];
int Query(int);
void Answer(int,int);
int ask(int x){v[x] ^= 1; return Query(x);}
void solve(vector<int>c){
	int n = si(c);
	if(n == 2){Answer(c[0], c[1]); return ;}
	vector<int>p, q, l, r;
	go(x, c)if(id[x])p.pb(x); else q.pb(x);
	int m = max(1, (int)(n * 0.185)), s = 0;
	rep(i, 0, m - 1)lst = ask(q[i]), r.pb(q[i]);
	rep(i, m, si(q) - 1)l.pb(q[i]);
	int op = !v[q[0]];
	go(x, p){
		if(s == m)l.pb(x);
		else{
			int w = ask(x);
			if((w != lst) ^ op)l.pb(x);
			else r.pb(x), s++;
			lst = w;
		}
	}
	solve(l), solve(r);
}
void Solve(int n){
	n <<= 1;
	vector<int>a;
	rp(i, n)a.pb(i);
	shuffle(a.begin(), a.end(), rd);
	rp(i, n){
		int x = a[i - 1];
		int w = ask(x);
		if(w == lst)id[x] = 1;
		lst = w;
	}
	solve(a);
}