树上启发式合并
强大的解决对于子树的询问问题。
有个英文名字叫 dsu on tree。
一种是基于重链剖分的,一种是基于set map的启发式合并的。
第一种就是先走轻边,再走重边,重边的不改回来,这样修改次数就是 $logn$ 次的。
第二种就是看set map的size,把size小的并到大的上。
某些情况下!!!dfs序+主席树(某种数据结构)比这个做法可能少一个log!!!
模板题啦
#include <bits/stdc++.h> #define ll long long const int N = 1e5 + 7; int n, cc[N], col[N], son[N], sz[N], skip[N]; ll ans[N]; std::vector<int> G[N]; void dfs1(int u, int fa = 0) { sz[u] = 1; for (auto v: G[u]) { if (v == fa) continue; dfs1(v, u); sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v; } } ll sum; int cx; void edt(int u, int fa, int k) { cc[col[u]] += k; if (k > 0 && cc[col[u]] >= cx) { if (cc[col[u]] > cx) sum = 0, cx = cc[col[u]]; sum += col[u]; } for (auto v: G[u]) if (v != fa && !skip[v]) edt(v, u, k); } void dfs(int u, int fa = 0, bool kep = 0) { for (auto v: G[u]) if (v != fa && v != son[u]) dfs(v, u); if (son[u]) dfs(son[u], u, 1), skip[son[u]] = 1; edt(u, fa, 1); ans[u] = sum; if (son[u]) skip[son[u]] = 0; if (!kep) edt(u, fa, -1), cx = sum = 0; } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", col + i); for (int i = 1, u, v; i < n; i++) { scanf("%d%d", &u, &v); G[u].push_back(v); G[v].push_back(u); } dfs1(1); dfs(1); for (int i = 1; i <= n; i++) printf("%lld%c", ans[i], " \n"[i == n]); return 0; }
把询问离线,一些字母能组成回文串,即出现偶数次的字母不能超过一个,那么对每一个深度用一个26位的二进制数表示一个字母出现的奇偶性,记为 $cnt[dep]$ 即可。
#include <bits/stdc++.h> #define pii pair<int, int> #define fi first #define se second const int N = 5e5 + 7; std::vector<int> G[N]; std::vector<std::pii> que[N]; int cnt[N], n, m, dep[N], sz[N], son[N]; char s[N]; bool ans[N], skip[N]; void dfs1(int u, int d) { dep[u] = d; sz[u] = 1; for (auto v: G[u]) { dfs1(v, d + 1); sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v; } } void edt(int u) { cnt[dep[u]] ^= (1 << (s[u] - 'a')); for (auto v: G[u]) if (!skip[v]) edt(v); } bool check(int d) { int res = 0; for (int i = 0; i < 26; i++) if (cnt[d] >> i & 1) res++; return res <= 1; } void dfs(int u, int keep = 0) { for (auto v: G[u]) if (v != son[u]) dfs(v, 0); if (son[u]) dfs(son[u], 1), skip[son[u]] = 1; edt(u); for (auto p: que[u]) ans[p.se] = check(p.fi); if (son[u]) skip[son[u]] = 0; if (!keep) edt(u); } int main() { // freopen("in.txt", "r", stdin); scanf("%d%d", &n, &m); for (int i = 2; i <= n; i++) { int x; scanf("%d", &x); G[x].push_back(i); } scanf("%s", s + 1); for (int i = 1, u, h; i <= m; i++) { scanf("%d%d", &u, &h); que[u].push_back(std::pii(h, i)); } dfs1(1, 1); dfs(1); for (int i = 1; i <= m; i++) puts(ans[i] ? "Yes" : "No"); return 0; }
想用重链剖分的写法。用一个multiset维护出现的数字,一个multiset维护每个插入的数和附近两个数的差值.
但是这跟插入顺序有关,插入的时候正着插入,删除的时候倒着删除,然后WA on 20了... 正着删除有RE on 20了...
看了一下别人的解法就是,每个结点维护一个set,发现这个set元素个数最多是所有叶子个数,空间能接受,然后就启发式合并就OK了。
#include <bits/stdc++.h> namespace IO { void read() {} template <typename T, typename... T2> inline void read(T &x, T2 &... oth) { T f = 1; x = 0; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); } while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getchar(); } x *= f; read(oth...); } } // using namespace IO #define read IO::read #define print IO::print #define flush IO::flush const int N = 1e5 + 7; const int INF = 2147483647; std::vector<int> vec[N]; std::set<int> st[N]; int n, m, color[N], ans[N]; int merge(int u, int v) { if (st[u].size() < st[v].size()) std::swap(st[u], st[v]); int res = INF; for (std::set<int>::iterator it = st[v].begin(); it != st[v].end(); it++) { auto i = st[u].lower_bound(*it); if (i == st[u].begin()) { res = std::min(res, *i - *it); } else if (i == st[u].end()) { i--; res = std::min(res, *it - *i); } else { res = std::min(res, *i - *it); i--; res = std::min(res, *it - *i); } st[u].insert(*it); } st[v].clear(); return res; } void dfs(int u) { ans[u] = INF; if (vec[u].size() == 0) { st[u].insert(color[u]); return; } for (int v: vec[u]) { dfs(v); ans[u] = std::min(ans[u], ans[v]); ans[u] = std::min(ans[u], merge(u, v)); } } int main() { read(n, m); for (int i = 2; i <= n; i++) { int u; read(u); vec[u].push_back(i); } for (int i = n - m + 1; i <= n; i++) read(color[i]); dfs(1); for (int i = 1; i <= n - m; i++) printf("%d%c", ans[i], " \n"[i == n - m]); return 0; }
这个就是每个结点维护一个map表示其子树钟出现过的数字以及出现的次数。
然后根据合并前枚举小的map里面的每个元素,看看大的map中能与其相乘成为当前结点的值的就行了。
#include <bits/stdc++.h> #define ll long long const int N = 1e5 + 7; std::map<int, int> mp[N]; int n, val[N]; std::vector<int> vec[N]; ll ans; void merge(int u, int v) { if (mp[u].size() < mp[v].size()) std::swap(mp[u], mp[v]); for (auto p: mp[v]) { if (val[u] % p.first == 0) ans += 1LL * p.second * mp[u][val[u] / p.first]; } for (auto p: mp[v]) mp[u][p.first] += p.second; mp[v].clear(); } void dfs(int u, int fa) { mp[u][val[u]] = 1; for (int v: vec[u]) { if (v == fa) continue; dfs(v, u); merge(u, v); } } int main() { scanf("%d", &n); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); vec[u].push_back(v); vec[v].push_back(u); } for (int i = 1; i <= n; i++) scanf("%d", val + i); dfs(1, 0); printf("%lld\n", ans); return 0; }
对每一个深度用一个map维护出现的字符串以及出现的次数,然后然后就做完了。
#include <bits/stdc++.h> #define pii pair<int, int> #define fi first #define se second const int N = 1e5 + 7; char s[N][22]; std::vector<int> vec[N]; std::vector<std::pii> query[N]; std::map<std::string, int> mp[N]; int n, son[N], sz[N], dep[N], ans[N], root, cnt[N]; bool skip[N]; void dfs1(int u, int d) { sz[u] = 1; dep[u] = d; son[u] = -1; for (int v: vec[u]) { dfs1(v, d + 1); sz[u] += sz[v]; if (son[u] == -1 || sz[son[u]] < sz[v]) son[u] = v; } } void edt(int u, int k) { mp[dep[u]][s[u]] += k; int w = mp[dep[u]][s[u]]; if (k == 1 && w == 1) cnt[dep[u]]++; if (k == -1 && w == 0) cnt[dep[u]]--; assert(cnt[dep[u]] >= 0); for (int v: vec[u]) if (!skip[v]) edt(v, k); } void dfs(int u, bool keep = 0) { for (int v: vec[u]) if (v != son[u]) dfs(v); if (son[u] != -1) dfs(son[u], 1), skip[son[u]] = 1; edt(u, 1); for (auto p: query[u]) ans[p.se] = cnt[dep[u] + p.fi]; if (son[u] != -1) skip[son[u]] = 0; if (!keep) edt(u, -1); } int main() { scanf("%d", &n); for (int i = 1, u; i <= n; i++) { scanf("%s%d", s[i], &u); vec[u].push_back(i); } int m; scanf("%d", &m); for (int i = 1; i <= m; i++) { int u, k; scanf("%d%d", &u, &k); query[u].push_back(std::pii(k, i)); } dfs1(0, 0); dfs(0, 1); for (int i = 1; i <= m; i++) printf("%d\n", ans[i]); return 0; }
先转换一下询问,把 $v_i$ 往上跳 $p_i$ 步得到 $u_i$,变成询问 $u_i$ 有多少个 $p_i$ 级儿子。然后就做完了。
#include <bits/stdc++.h> #define pii pair<int, int> #define fi first #define se second const int N = 1e5 + 7; int dep[N], son[N], sz[N], n, fa[N][20], ans[N]; bool skip[N]; std::vector<int> vec[N]; std::vector<std::pii> query[N]; void dfs1(int u, int pre = 0, int d = 0) { dep[u] = d; fa[u][0] = pre; for (int i = 1; i <= 18; i++) { fa[u][i] = fa[fa[u][i - 1]][i - 1]; if (!fa[u][i]) break; } sz[u] = 1; son[u] = -1; for (int v: vec[u]) { dfs1(v, u, d + 1); sz[u] += sz[v]; if (son[u] == -1 || sz[son[u]] < sz[v]) son[u] = v; } } int jump(int u, int k) { for (int i = 18; ~i; i--) if (k >> i & 1) u = fa[u][i]; return u; } int cnt[N]; void edt(int u, int k) { cnt[dep[u]] += k; for (int v: vec[u]) if (!skip[v]) edt(v, k); } void dfs(int u, bool keep = 0) { for (int v: vec[u]) if (v != son[u]) dfs(v); if (son[u] != -1) dfs(son[u], 1), skip[son[u]] = 1; edt(u, 1); for (auto p: query[u]) { ans[p.se] = cnt[dep[u] + p.fi] - 1; } if (son[u] != -1) skip[son[u]] = 0; if (!keep) edt(u, -1); } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { int u; scanf("%d", &u); vec[u].push_back(i); } int m; scanf("%d", &m); dfs1(0); for (int i = 1; i <= m; i++) { int u, k; scanf("%d%d", &u, &k); u = jump(u, k); if (u) query[u].push_back(std::pii(k, i)); } dfs(0, 1); for (int i = 1; i <= m; i++) printf("%d%c", ans[i], " \n"[i == m]); return 0; }
这题可以用启发式合并,但是没必要。
另一个trick。当需要继承、查询祖先上的信息时,可以用一个(树状数组/桶)维护这些信息,进入一个点时加入该点的信息,离开这个点时删除这些信息即可。
比如树上斜率优化DP就可以这样做。
那么这道题就用一个vector维护哈希值,进入一个点,就继续插入哈希值,并在插入的过程中完成查询。离开时删除即可。
#include <bits/stdc++.h> #define ull unsigned long long #define ll long long const ull base = 19260817; const int N = 3e5 + 7; ull p[N], need; std::vector<char> s[N]; std::vector<int> vec[N]; std::vector<ull> cur; char t[N]; ll ans; int len, n; void dfs(int u) { for (int i = 0; i < s[u].size(); i++) { cur.push_back(cur.back() * base + s[u][i]); if (cur.size() > len) { ull x = cur.back() - cur[cur.size() - len - 1] * p[len]; if (x == need) ans++; } } for (int v: vec[u]) dfs(v); for (int i = 0; i < s[u].size(); i++) cur.pop_back(); } int main() { scanf("%d", &n); for (int i = p[0] = 1; i < N; i++) p[i] = p[i - 1] * base; for (int i = 2; i <= n; i++) { int u; scanf("%d%s", &u, t); vec[u].push_back(i); int len = strlen(t); for (int j = 0; j < len; j++) s[i].push_back(t[j]); } scanf("%s", t); len = strlen(t); for (int i = 0; i < len; i++) need = need * base + t[i]; cur.push_back(0); dfs(1); printf("%lld\n", ans); return 0; }
这题是长链剖分板子。而我用了重链剖分...
$O(nlog^{2}n)$ 水过去了...
用一个pair记录 cnt[dep[u]] 和 dep[u],然后用set维护,cnt取负就能变成越大的排越前,在cnt相同时dep更小的排越前。
当set为空或者第一个的cnt是-1时答案是0
#include <bits/stdc++.h> #define pii pair<int, int> const int N = 1e6 + 7; int n, cnt[N], dep[N], son[N], sz[N], cur, ans[N]; std::vector<int> vec[N]; std::set< std::pii > st; bool skip[N]; void dfs1(int u, int fa = 0) { dep[u] = dep[fa] + 1; sz[u] = 1; for (int v: vec[u]) { if (v == fa) continue; dfs1(v, u); sz[u] += sz[v]; if (sz[son[u]] < sz[v]) son[u] = v; } } void edt(int u, int fa, int k) { auto it = st.find(std::pii(-cnt[dep[u]], dep[u])); if (it != st.end()) st.erase(it); cnt[dep[u]] += k; st.insert(std::pii(-cnt[dep[u]], dep[u])); for (int v: vec[u]) if (v != fa && !skip[v]) edt(v, u, k); } void dfs(int u, int fa, bool keep = 0) { for (int v: vec[u]) if (v != fa && v != son[u]) dfs(v, u); if (son[u]) dfs(son[u], u, 1), skip[son[u]] = 1; edt(u, fa, 1); if (st.empty() || st.begin()->first == -1) ans[u] = 0; else ans[u] = st.begin()->second - dep[u]; if (son[u]) skip[son[u]] = 0; if (!keep) edt(u, fa, -1); } int main() { scanf("%d", &n); for (int i = 1, u, v; i < n; i++) scanf("%d%d", &u, &v), vec[u].push_back(v), vec[v].push_back(u); dfs1(1, 0); dfs(1, 0, 1); for (int i = 1; i <= n; i++) printf("%d\n", ans[i]); return 0; }
感谢 https://blog.csdn.net/zlttttt/article/details/78634449 这篇dsu on tree的解法。
要 $O(1)$ 继承重儿子的信息,而重儿子的信息和自己的信息的差距只为一条边的长度和一条边
那么就不实际维护实际的长度和深度,而是维护一个差值。
具体用一个 cur_dep 和 cur_dis 维护当前结点 $u$ 到重链底端经过的边数和距离。
然后map维护当前节点到子树中的 节点的距离 减去 cur_dis 需要用的最小 边数 减去 cur_dep
查询是否存在一个距离时得减去 cur_dis,更新答案是加上 cur_dep 即可。
#include <bits/stdc++.h> namespace IO { char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n'; int p, p3 = -1; void read() {} void print() {} inline int getc() { return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++; } inline void flush() { fwrite(buf2, 1, p3 + 1, stdout), p3 = -1; } template <typename T, typename... T2> inline void read(T &x, T2 &... oth) { T f = 1; x = 0; char ch = getc(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getc(); } while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); } x *= f; read(oth...); } template <typename T, typename... T2> inline void print(T x, T2... oth) { if (p3 > 1 << 20) flush(); if (x < 0) buf2[++p3] = 45, x = -x; do { a[++p] = x % 10 + 48; } while (x /= 10); do { buf2[++p3] = a[p]; } while (--p); buf2[++p3] = hh; print(oth...); } } // using namespace IO #define read IO::read #define print IO::print #define flush IO::flush #define pii pair<int, int> #define fi first #define se second inline void checkmin(int &a, int b) { if (a > b) a = b; } const int N = 2e5 + 7; const int INF = 0x3f3f3f3f; int n, dep[N], son[N], sz[N], in[N], out[N], dfn[N], tol, dis[N], ans; int cur_dis[N], cur_dep[N], k, wt[N]; std::vector<std::pii> vec[N]; std::unordered_map<int, int> f; void dfs1(int u, int fa = 0) { dfn[in[u] = ++tol] = u; sz[u] = 1; for (auto p: vec[u]) { int v = p.fi; if (v == fa) continue; dep[v] = dep[u] + 1; dis[v] = dis[u] + p.se; wt[v] = p.se; dfs1(v, u); sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v; } out[u] = tol; } void dfs(int u, int fa) { if (!son[u]) { cur_dis[u] = cur_dep[u] = 0; return; } for (auto p: vec[u]) if (p.fi != fa && p.fi != son[u]) dfs(p.fi, u); dfs(son[u], u); cur_dis[u] = cur_dis[son[u]] + wt[son[u]]; cur_dep[u] = cur_dep[son[u]] + 1; if (f.count(f[wt[son[u]] - cur_dis[u]])) checkmin(f[wt[son[u]] - cur_dis[u]], 1 - cur_dep[u]); else f[wt[son[u]] - cur_dis[u]] = 1 - cur_dep[u]; for (auto p: vec[u]) { if (p.fi == fa || p.fi == son[u]) continue; for (int j = in[p.fi]; j <= out[p.fi]; j++) { int v = dfn[j]; int temp = k - (dis[v] - dis[u]); if (temp > 0 && f.count(temp - cur_dis[u])) checkmin(ans, f[temp - cur_dis[u]] + cur_dep[u] + dep[v] - dep[u]); } for (int j = in[p.fi]; j <= out[p.fi]; j++) { int v = dfn[j]; int temp = dis[v] - dis[u]; if (temp <= k) if (f.count(temp - cur_dis[u])) checkmin(f[temp - cur_dis[u]], dep[v] - dep[u] - cur_dep[u]); else f[temp - cur_dis[u]] = dep[v] - dep[u] - cur_dep[u]; } } if (f.count(k - cur_dis[u])) checkmin(ans, f[k - cur_dis[u]] + cur_dep[u]); if (son[fa] != u && u != 1) f.clear(); } int main() { freopen("in.txt", "r", stdin); read(n, k); for (int i = 1; i < n; i++) { int u, v, w; read(u, v, w); u++, v++; vec[u].push_back(std::pii(v, w)); vec[v].push_back(std::pii(u, w)); } ans = INF; dfs1(1, 0); dfs(1, 0); printf("%d\n", ans == INF ? -1 : ans); return 0; }
查询子树中颜色出现次数不少于 $k_i$ 次的颜色种类数,用一个 $cnt$ 数组存颜色出现的次数,再用一个树状数组存 $cnt$ 值的出现次数。
回答询问就是一个后缀和
#include <bits/stdc++.h> #define pii pair<int, int> #define fi first #define se second const int N = 1e5 + 7; int color[N], cnt[N], n, m, sz[N], son[N], ans[N]; bool skip[N]; std::vector<int> vec[N]; std::vector<std::pii> query[N]; struct BIT { int tree[N]; inline int lowbit(int x) { return x & -x; } void add(int x, int k) { if (!x) return; for (int i = x; i < N; i += lowbit(i)) tree[i] += k; } int query(int x) { int ans = 0; for (int i = x; i; i -= lowbit(i)) ans += tree[i]; return ans; } } bit; void dfs1(int u, int fa) { sz[u] = 1; for (int v: vec[u]) { if (v == fa) continue; dfs1(v, u); sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v; } } void edt(int u, int fa, int k) { bit.add(cnt[color[u]], -1); cnt[color[u]] += k; bit.add(cnt[color[u]], 1); for (int v: vec[u]) if (v != fa && !skip[v]) edt(v, u, k); } void dfs(int u, int fa, bool kep) { for (int v: vec[u]) if (v != fa && v != son[u]) dfs(v, u, 0); if (son[u]) dfs(son[u], u, 1), skip[son[u]] = 1; edt(u, fa, 1); for (auto p: query[u]) ans[p.se] = bit.query(N - 1) - bit.query(p.fi - 1); if (son[u]) skip[son[u]] = 0; if (!kep) edt(u, fa, -1); } int main() { freopen("in.txt", "r", stdin); scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d", color + i); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); vec[u].push_back(v); vec[v].push_back(u); } for (int i = 1; i <= m; i++) { int u, k; scanf("%d%d", &u, &k); query[u].push_back(std::pii(k, i)); } dfs1(1, 0); dfs(1, 0, 1); for (int i = 1; i <= m; i++) printf("%d\n", ans[i]); return 0; }
查询有多少点对 $(u, v)$ 路径上的数字组成的数为 $M$ 的倍数。
点分治做的话,维护一个当前重心到当前点 $u$ 走过来组成的数字 $down_u$ 以及从 $u$ 走到重心组成的数字 $up_u$。
然后就是 $up_u * 10^{dep_v}+ down_v \equiv 0$($mod$ $M$)
得到 $up_u \equiv M - down_v * 10^{-dep_v}$($mod$ $M$)
用两个map维护这个东西就好了。
考虑重链剖分的做法,就是先递归轻儿子,再递归重儿子。
现在 $down_u$ 和 $up_u$ 分别代表根节点到 $u$ 和 $u$ 到根节点形成的两个数字。
考虑现在到了 $f$ 节点,其子树中一个节点 $u$ 和一个节点 $v$ 能形成一个合法的点对
从 $u$ 到 $v$ 需符合
$(up_u - up_f) * 10^{-dep_f}*10^{dep_v-dep_f} + (down_v - down_f * 10^{dep_v-dep_f}) \equiv 0$($mod$ $M$)
得到 $down_f * 10^{-dep_f}+(up_f-up_u)*10^{-dep_f} \equiv down_v * 10^{-dep_v}$($mod$ $M$)
从 $v$ 到 $u$ 需符合
$down_f*10^{dep_f}-down_{u}*10^{2dep_f-dep_u}+up_f \equiv up_v$($mod$ $M$)
用两个map维护右边两个值即可。
两种做法复杂度都是 $O(nlog^2n)$,但是常数有很大区别...
#include <bits/stdc++.h> #define ll long long #define pii pair<int, int> #define fi first #define se second const int N = 1e5 + 7; int n, MOD, down[N], up[N], dep[N], son[N], sz[N], in[N], out[N], dfn[N], tol; int base[N * 5]; std::vector<std::pii> vec[N]; int qp(int a, int b) { int ans = 1; while (b) { if (b & 1) ans = 1LL * ans * a % MOD; a = 1LL * a * a % MOD; b >>= 1; } return ans; } void dfs1(int u, int fa, int fac, int p1, int p2, int d) { sz[u] = 1; dep[u] = d; in[u] = ++tol; dfn[tol] = u; down[u] = p1, up[u] = p2; for (auto p: vec[u]) { int v = p.fi; if (v == fa) continue; dfs1(v, u, fac * 10LL % MOD, (p1 * 10LL + p.se) % MOD, (p2 + 1LL * p.se * fac) % MOD, d + 1); sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v; } out[u] = tol; } std::map<int, int> mp1, mp2; inline int BASE(int x) { return base[x + 3 * N]; } ll ans; void update(int u, int f) { int x = (1LL * down[f] * BASE(dep[f]) % MOD - 1LL * down[u] * BASE(2 * dep[f] - dep[u]) % MOD + up[f]) % MOD; x = (x + MOD) % MOD; std::map<int, int>::iterator it; it = mp1.find(x); if (it != mp1.end()) ans += it->se; x = (1LL * down[f] * BASE(-dep[f]) % MOD + 1LL * (up[f] - up[u]) * BASE(-2 * dep[f])) % MOD; x = (x + MOD) % MOD; it = mp2.find(x); if (it != mp2.end()) ans += it->se; } void getans(int u, int f) { for (int i = in[u]; i <= out[u]; i++) update(dfn[i], f); } void add(int u) { for (int i = in[u]; i <= out[u]; i++) { int f = dfn[i]; mp1[up[f]]++; int x = 1LL * down[f] * BASE(-dep[f]) % MOD; mp2[x]++; } } void dfs(int u, int fa) { if (son[u]) { for (auto p: vec[u]) if (p.fi != fa && p.fi != son[u]) dfs(p.fi, u), mp1.clear(), mp2.clear(); dfs(son[u], u); for (auto p: vec[u]) if (p.fi != fa && p.fi != son[u]) getans(p.fi, u), add(p.fi); update(u, u); } int x = 1LL * down[u] * BASE(-dep[u]) % MOD; mp2[x]++; mp1[up[u]]++; } int main() { scanf("%d%d", &n, &MOD); for (int i = 1; i < n; i++) { int u, v, w; scanf("%d%d%d", &u, &v, &w); u++; v++; vec[u].push_back(std::pii(v, w)); vec[v].push_back(std::pii(u, w)); } if (MOD == 1) { printf("%lld\n", 1LL * n * (n - 1)); return 0; } int phi = MOD, temp = MOD; for (int i = 2; i * i <= temp; i++) { if (temp % i == 0) { phi = phi / i * (i - 1); while (temp % i == 0) temp /= i; } } if (temp > 1) phi = phi / temp * (temp - 1); phi--; base[3 * N] = 1; int inv = qp(10, phi); for (int i = 1; i <= 2 * n; i++) base[i + 3 * N] = base[i + 3 * N - 1] * 10LL % MOD; for (int i = 1; i <= 2 * n; i++) base[3 * N - i] = 1LL * base[3 * N - i + 1] * inv % MOD; dfs1(1, 0, 1, 0, 0, 0); dfs(1, 0); printf("%lld\n", ans); return 0; }
741D - Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
看错题了之后不会写。
要求每个子树内最长的回文路径,回文路径定义为路径上的字符重排后能得到回文串。
那么就如同570D - Tree Requests
用一个二十六进制的数来表示一条路径上的字符,能成为回文路径就是该数字二进制下有不超过两个 $1$。
开一个桶 $f[x]$ 表示路径为 $x$ 最长是多少。先走重儿子,再枚举轻儿子,先更新,后合并信息。
#include <bits/stdc++.h> #define pii pair<int, int> #define fi first #define se second const int N = 5e5 + 7; int n, sz[N], son[N], pre[N], fa[N], ans[N], in[N], out[N], dfn[N], tol, dep[N]; std::vector<std::pii> vec[N]; inline void checkmax(int &a, int b) { if (a < b) a = b; } int f[1 << 22]; void dfs1(int u, int f) { sz[u] = 1; dfn[in[u] = ++tol] = u; dep[u] = dep[f] + 1; for (auto p: vec[u]) { int v = p.fi; if (v == f) continue; fa[v] = u; pre[v] = pre[u] ^ (1 << p.se); dfs1(v, u); sz[u] += sz[v]; if (sz[son[u]] < sz[v]) son[u] = v; } out[u] = tol; } void dfs(int u, bool kep) { for (auto p: vec[u]) if (p.fi != son[u]) dfs(p.fi, 0), checkmax(ans[u], ans[p.fi]); if (son[u]) dfs(son[u], 1), checkmax(ans[u], ans[son[u]]); if (f[pre[u]]) checkmax(ans[u], f[pre[u]] - dep[u]); for (int i = 0; i < 22; i++) if (f[pre[u] ^ (1 << i)]) checkmax(ans[u], f[pre[u] ^ (1 << i)] - dep[u]); checkmax(f[pre[u]], dep[u]); for (auto p: vec[u]) if (p.fi != son[u]) { for (int i = in[p.fi]; i <= out[p.fi]; i++) { int v = dfn[i]; if (f[pre[v]]) checkmax(ans[u], f[pre[v]] + dep[v] - 2 * dep[u]); for (int i = 0; i < 22; i++) if (f[pre[v] ^ (1 << i)]) checkmax(ans[u], f[pre[v] ^ (1 << i)] + dep[v] - 2 * dep[u]); } for (int i = in[p.fi]; i <= out[p.fi]; i++) { int v = dfn[i]; checkmax(f[pre[v]], dep[v]); } } if (!kep) for (int i = in[u]; i <= out[u]; i++) f[pre[dfn[i]]] = 0; } int main() { scanf("%d", &n); for (int i = 2; i <= n; i++) { int p; char s[3]; scanf("%d%s", &p, s); vec[p].push_back(std::pii(i, (int)(s[0] - 'a'))); } dfs1(1, 0); dfs(1, 1); for (int i = 1; i <= n; i++) printf("%d%c", ans[i], " \n"[i == n]); return 0; }
完结撒花!!!
https://codeforces.com/blog/entry/44351
评论区还有很多题,慢慢补吧...

浙公网安备 33010602011771号