强大的解决对于子树的询问问题。
有个英文名字叫 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
评论区还有很多题,慢慢补吧...
来源:oschina
链接:https://my.oschina.net/u/4303162/blog/3365422