首先说一下,Dfs序上跑莫队是十分巧妙的做法,与树上莫队如出一辙。
但是在这个题里莫队比较慢,亲测花了5000+ms。
这里提供一个不一样的思路,快了整整4倍。
做树上启发式合并,先用桶暴力维护每种颜色出现的次数,再对桶里的颜色个数再次计数,最后统计每种颜色出现次数是否一致。
这样讲比较抽象,这里讲详细一点:
设cnt[x]表示颜色x出现次数,f[i]表示cnt[x]==i的i的个数,若统计到u的子树,判断时只需判断f[cnt[c[u]]]∗cnt[c[u]]==sz[u]即可,非常方便,复杂度也相当优秀,达到了O(nlog2n)
啥,不了解树上启发式合并?
实质上就是优化暴力,在暴力统计的时候继承重儿子的贡献,暴力收集轻儿子信息,是O(nlog2n)的。
因为重链剖分后,每个节点u只会在u到根的路径上轻边处被暴力统计,而到根的路径上轻边数量是O(logn)的,所以每个点的贡献不超过(logn)级别,总体上是线性对数,非常快,常数也小。
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const lucky[6] = "H8281";
const int N = 2e5 + 20, M = N * 2;
int n, m, a[N], ans;
int f[N], g[N];
int idx, to[M], nxt[M], h[N], sz[N], son[N];
void add(int u, int v)
{
idx ++; to[idx] = v; nxt[idx] = h[u]; h[u] = idx;
}
void Dfs1(int u, int pa)
{
int v;
sz[u] = 1;
for(int i = h[u]; i ; i = nxt[i])
{
v = to[i];
if(v == pa) continue;
Dfs1(v, u);
sz[u] += sz[v];
if(sz[v] > sz[son[u]]) son[u] = v;
}
}
void Clear(int u, int pa)
{
int v;
g[f[a[u]]] = 0;
f[a[u]] = 0;
for(int i = h[u]; i ; i = nxt[i])
{
v = to[i];
if(v == pa) continue;
Clear(v, u);
}
}
void collect(int u, int pa)
{
int v;
f[a[u]] ++;
g[f[a[u]] - 1] --;
g[f[a[u]]] ++;
for(int i = h[u]; i ; i = nxt[i])
{
v = to[i];
if(v == pa) continue;
collect(v, u);
}
}
void Dfs2(int u, int pa)
{
int v;
for(int i = h[u]; i ; i = nxt[i])
{
v = to[i];
if(v == pa || v == son[u]) continue;
Dfs2(v, u);
Clear(v, u);
}
if(son[u]) Dfs2(son[u], u);
f[a[u]] ++; g[f[a[u]]] ++; g[f[a[u]] - 1] --;
for(int i = h[u]; i ; i = nxt[i])
{
v = to[i];
if(v == pa || v == son[u]) continue;
collect(v, u);
}
if(1ll * g[f[a[u]]] * f[a[u]] == sz[u]) ans ++;
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++)
{
int C, F;
scanf("%d%d", &C, &F);
a[i] = C; if(F) add(F, i), add(i, F);
}
Dfs1(1, 0);
Dfs2(1, 0);
printf("%d\n", ans);
return 0;
}
大佬请教一下为什么我这代码会TLE 18/20?
#include <bits/stdc++.h> #define endl '\n' using namespace std; typedef long long LL; typedef pair<int, int> PII; const int N = 2e5 + 10, INF = 0x3f3f3f3f, MOD = 1e9 + 7; int n, m, ans; vector<int> e[N]; int col[N]; int sz[N], hvy[N]; int l[N], r[N], id[N], idx; int cnt[N]; void add(int x) { cnt[col[x]] ++; } void del(int x) { cnt[col[x]] --; } void dfs1(int u, int dad) { l[u] = ++ idx, id[idx] = u, sz[u] = 1; for(int v : e[u]) if(v != dad) { dfs1(v, u); sz[u] += sz[v]; if(sz[hvy[u]] < sz[v]) hvy[u] = v; } r[u] = idx; } void dfs2(int u, int dad, bool keep) { for(int v : e[u]) if(v != dad && v != hvy[u]) dfs2(v, u, false); if(hvy[u]) dfs2(hvy[u], u, true); for(int v : e[u]) { if(v == dad || v == hvy[u]) continue; for(int i = l[v]; i <= r[v]; i ++) add(id[i]); } add(u); bool ok = true; for(int i = l[u]; i <= r[u]; i ++) if(cnt[col[id[i]]] != cnt[col[u]]) { ok = false; break; } ans += ok; if(!keep) for(int i = l[u]; i <= r[u]; i ++) del(id[i]); } void solve() { cin >> n; for(int i = 1; i <= n; i ++) { cin >> col[i]; int j; cin >> j; e[i].push_back(j), e[j].push_back(i); } dfs1(1, 0); dfs2(1, 0, true); cout << ans << endl; } int main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int T = 1; // cin >> T; while(T --) solve(); return 0; }
57 ~ 62行,还有66行,就是dfs2里面不能遍历u的子树,不然在一条链的情况下复杂度退化为O(n2),可以参考我的写法优化,判断只需要O(1)
把 57−62 行的判断改成 O(1) 就过了