【题解】「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);
}