【UR #2】树上GCD
这道题是有根树点分治+烧脑的容斥+神奇的分块
因为是规定1为根,还要求LCA,所以我们不能像在无根树上那样随便浪了,必须规定父亲,并作特殊讨论
因为gcd并不好求,所以我们用容斥转化一下,求x为gcd的因数的个数,这样就可以随便统计了,个人觉得代码比题解要好懂。
又因为统计完重心的所有子树,还有重心的父亲,所以在这个分治块内沿着重心的父亲一路向上爬,这时候重心的子树到重心的父亲的距离是变的,所以我们用神奇的分块大法,分类讨论,$≤\sqrt{n}$使用数组记录答案,方便以后再用到的时候统计,$>\sqrt{n}$时直接暴力统计,因为此时统计的复杂度并不高。这样使时间复杂度空降为$O(n\sqrt{n})$。个人感觉题解说得太含糊了,一切都不如直接看代码明晰,或者说题解是帮助看懂代码的233
这道题细节太多了,跪了1天半终于AC了蛤蛤蛤蛤蛤蛤蛤
这道题我先膜了鏼爷的代码,因为各种指针看不懂啊,但学习了非递归求重心的新姿势,无限仰膜Orz!!!SDOI就是要练就各种非递归能力
然后我又膜了ShallWe的题解,学会了容斥统计的方法,无限仰膜啊!!!巧妙的特判和两个指针的移动使得容斥统计不重不漏,真是太神奇了!!!
#include<cmath> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; const int N = 200003; void read(int &k) { k = 0; int fh = 1; char c = getchar(); for(; c < '0' || c > '9'; c = getchar()) if (c == '-') fh = -1; for(; c >= '0' && c <= '9'; c = getchar()) k = (k << 1) + (k << 3) + c - '0'; k = k * fh; } bool vis[N]; struct node { int nxt, to; } E[N]; int qu[N], ct[N], n, m, fa[N], de, deep[N], cnt = 0, point[N], root, sz[N], boo[N]; ll t[503][503], cn[N], sct[N], scn[N], ans2[N], S[N]; void ins(int x, int y) {E[++cnt].nxt = point[x]; E[cnt].to = y; point[x] = cnt;} void findrt(int x) { int p = 0, q = 0; qu[++q] = x; while (p != q) { int u = qu[++p]; boo[u] = sz[u] = 1; for(int tmp = point[u]; tmp; tmp = E[tmp].nxt) if (!vis[E[tmp].to]) qu[++q] = E[tmp].to; } for(int i = q; i; --i) { if (boo[qu[i]] && sz[qu[i]] * 2 >= q) {root = qu[i]; return;} sz[fa[qu[i]]] += sz[qu[i]]; if (sz[qu[i]] * 2 >= q) boo[fa[qu[i]]] = 0; } }//非递归找重心!!!国家队rank3的鏼爷Orz给SDOIers带来福利 void BFS(int x) { deep[x] = 1; int p = 0, q = 0; qu[++q] = x; while (p != q) { int u = qu[++p]; ++ct[deep[u]]; for(int tmp = point[u]; tmp; tmp = E[tmp].nxt) if (!vis[E[tmp].to]) deep[E[tmp].to] = deep[u] + 1, qu[++q] = E[tmp].to; } de = deep[qu[q]]; } void Q(int x) { vis[x] = 1; int up = 0, upp = 0; for(int tmp = point[x]; tmp; tmp = E[tmp].nxt) if (!vis[E[tmp].to]) { BFS(E[tmp].to); up = max(up, de); for(int i = 1; i <= de; ++i) for(int j = i; j <= de; j += i) cn[i] += ct[j]; for(int i = 1; i <= de; ++i) { ans2[i] += ct[i]; sct[i] += ct[i]; S[i] += scn[i] * cn[i]; scn[i] += cn[i]; ct[i] = cn[i] = 0; } } sct[0] = 1; int step = 0, son = x, line, to; for(int i = fa[x]; !vis[i] && i; son = i, i = fa[i]) { ++step; to = 0; for(int tmp = point[i]; tmp; tmp = E[tmp].nxt) if (!vis[E[tmp].to] && E[tmp].to != son) BFS(E[tmp].to), to = max(to, de); de = to; upp = max(upp, de); for(int j = 1; j <= de; ++j) for(int k = j; k <= de; k += j) cn[j] += ct[k]; line = min(de, m); for(int j = 1; j <= line; ++j) { to = step % j; if (t[j][to] == -1) { t[j][to] = 0; for(int k = (j - to) % j; k <= up; k += j) t[j][to] += sct[k]; } S[j] += t[j][to] * cn[j]; } for(int j = m + 1; j <= de; ++j) for(int k = (j - step % j) % j; k <= up; k += j) S[j] += sct[k] * cn[j]; for(int j = 1; j <= de; ++j) ct[j] = cn[j] = 0; ++ans2[step]; } //下面是向ShallWe学的 int L = 1, R = 0, tot = step + up; ll now = 0; for(int i = 2; i <= tot; ++i) { if (R + 1 < i) now += sct[++R]; if (L + step < i) now -= sct[L++]; ans2[i] += now; } //仰膜上方ShallWe的代码 line = min(m, upp); for(int i = 1; i <= line; ++i) for(int j = 0; j < i; ++j) t[i][j] = -1; for(int i = 0; i <= up; ++i) sct[i] = scn[i] = 0; if (son != x) { findrt(son); Q(root); } for(int tmp = point[x]; tmp; tmp = E[tmp].nxt) if (!vis[E[tmp].to]) { findrt(E[tmp].to); Q(root); } } ll ans[N]; int main() { read(n); m = (sqrt(n)); for(int i = 2; i <= n; ++i) { read(fa[i]); ins(fa[i], i); } memset(t, -1, sizeof(t)); findrt(1); Q(root); for(int i = n - 1; i; --i) for(int j = i + i; j < n; j += i) S[i] -= S[j]; for(int i = 1; i < n; ++i) printf("%lld\n", S[i] + ans2[i]); return 0; }
_(:з」∠)_
NOI 2017 Bless All