题解 P6541 【[WC2018]即时战略】
学OI一年半,终于切了第一道交互题。
简述题意
有一棵有标号的树,explore(x, y)函数,其中explore的次数大约为
解法
首先我们有一个很显然的暴力思路。
对于每一个未知的点,我们从explore,一直走到这个点。
由于树的深度最坏是
考虑减小每次达到新点的复杂度,也就是减小“深度”,不难想到可以利用点分治的性质,即点分治深度为
维护已知树的点分树,从点分根节点开始,设当前所在节点为explore(x, y),那么我们从explore的调用次数是
现在我们的问题是,如何给点分树动态加点。我们每次先默认新加点的点分父亲是原树父亲,从这个点往上爬点分父亲,找到最后一个点分子树大小超过点分父亲点分子树大小
对于这道题,我们需要特别注意一下链的情况,允许的调用次数是random_shuffle一下。令已知链的左端点为explore(l, y),如果
代码
#include <bits/stdc++.h>
//#include "rts.h"
#define MAXN _________
using namespace std;
const int MAXN = 1e6 + 5;
int n, a[MAXN], vis[MAXN], fini[MAXN];
int explore(int, int);
// int explore(int x, int y) {}
// int main() {}
void WorkLine() {
srand(time(0));
for (int i = 1; i < n; i++) a[i] = i + 1;
random_shuffle(a + 1, a + n);
int l = 1, r = 1;
fini[1] = 1;
for (int i = 1; i < n; i++) {
int ter = a[i];
if (fini[ter]) continue;
int x = explore(l, ter), now = 0;
if (fini[x]) now = r, r = ter;
else fini[x] = 1, now = x, l = ter;
while (!fini[ter]) now = explore(now, ter), fini[now] = 1;
}
}
int to[MAXN], nx[MAXN], head[MAXN], ecnt;
int dfa[MAXN], ddep[MAXN], dsiz[MAXN], maxw[MAXN], siz[MAXN];
int nowrt;
void Add(int x, int y) {
to[++ecnt] = y; nx[ecnt] = head[x]; head[x] = ecnt;
to[++ecnt] = x; nx[ecnt] = head[y]; head[y] = ecnt;
}
int Find(int u, int fa, int tot) {
siz[u] = 1;
maxw[u] = 0;
int res = 0;
for (int i = head[u]; i; i = nx[i]) {
int v = to[i];
if (vis[v] || v == fa) continue;
int cen = Find(v, u, tot);
if (!res || maxw[cen] < maxw[res]) res = cen;
maxw[u] = max(maxw[u], siz[v]);
siz[u] += siz[v];
}
maxw[u] = max(maxw[u], tot - siz[u]);
if (!res || maxw[u] < maxw[res]) res = u;
return res;
}
void Divide(int u, int tot) {
vis[u] = 1;
for (int i = head[u]; i; i = nx[i]) {
int v = to[i];
if (vis[v]) continue;
int subs = (siz[v] < siz[u] ? siz[v] : tot - siz[u]);
int subc = Find(v, u, subs);
dfa[subc] = u;
ddep[subc] = ddep[u] + 1;
dsiz[subc] = subs;
Divide(subc, subs);
}
}
void Clear(int u, int fa, int lim) {
dfa[u] = 0;
ddep[u] = 0;
dsiz[u] = 0;
vis[u] = 0;
for (int i = head[u]; i; i = nx[i]) {
int v = to[i];
if (v == fa || ddep[v] < lim) continue;
Clear(v, u, lim);
}
};
void Rebuild(int u) {
int tot = dsiz[u];
int fa = dfa[u];
int depth = ddep[u];
Clear(u, 0, depth);
int cen = Find(u, 0, tot);
if (u == nowrt) nowrt = cen;
dfa[cen] = fa;
ddep[cen] = depth;
dsiz[cen] = tot;
Divide(cen, tot);
}
void Update(int now) {
int reb = 0;
while (dfa[now]) {
if (1.0 * dsiz[now] > 0.7 * dsiz[dfa[now]]) reb = dfa[now];
now = dfa[now];
}
if (reb) Rebuild(reb);
}
void Reach(int ter) {
int now = nowrt;
while (!fini[ter]) {
int nex = explore(now, ter);
if (!fini[nex]) {
Add(now, nex);
dfa[nex] = now;
ddep[nex] = ddep[now] + 1;
vis[nex] = 1;
fini[nex] = 1;
dsiz[nex] = 0;
for (int cur = nex; cur; cur = dfa[cur]) dsiz[cur]++;
now = nex;
Update(now);
} else {
int son = nex;
while (dfa[son] != now) son = dfa[son];
now = son;
}
}
}
void Work() {
srand(time(0));
for (int i = 1; i < n; i++) a[i] = i + 1;
random_shuffle(a + 1, a + n);
fini[1] = 1;
ddep[1] = 1;
vis[1] = 1;
dsiz[1] = 1;
nowrt = 1;
for (int i = 1; i < n; i++) {
int ter = a[i];
if (fini[ter]) continue;
Reach(ter);
}
}
void play(int n, int T, int dataType) {
::n = n;
if (dataType == 3) WorkLine();
else Work();
}