SDOI2018 原题识别
原题识别
「人肉题库」HS刷题非常勤奋,题量破万。每当有人拿题目请教他时,HS总能在1秒内报出这是哪个OJ的哪道题。因此,HS是被当作「原题搜索机」一样的存在。
有一天,HS来到了一棵\(n\)个节点的有根树下,这棵树的根节点为\(1\)号点,且每个节点都印着一道题目。凭借超大的题量,HS迅速识别出了每道题的来源,并发现有些题目被搬运了好多次。他把每个节点的题目都做了一个分类,第\(i\)个节点的题目对应的题目种类为\(a_i\),当且仅当\(a_i=a_j\)时,\(i\)点和\(j\)点的题目来源是相同的。
同一道题目做多次除了增加AC数以外,对本身的水平没有任何提高。为了调查这棵树的题目质量,HS会不断提出以下两种询问共\(m\)次:
1 x y
:如果将\(x\)点到\(y\)点的最短路径上的所有点(包括\(x\)和\(y\))对应的题目都做一遍,那么一共可以做到多少道本质不同的题目?2 A B
:如果在\(A\)点到根的最短路径上(包括\(A\)点和根)等概率随机选择一个点\(x\),在\(B\)点到根的最短路径上(包括\(B\)点和根)等概率随机选择一个点\(y\),那么询问1 x y
的答案期望是多少?
定义\(\text{cnt}_x\)表示\(x\)点到根最短路径上的节点个数,因为HS不喜欢分数,而且第2类询问的答案一定可以表示成\(\frac{\text{ans}}{\text{cnt}_A\times \text{cnt}_B}\)的形式,你只需要告诉他\(\text{ans}\)的值就可以了。
识别这些题目消耗了HS太大的精力,他没有办法自己去计算这些简单的询问的答案。请写一个程序回答HS的所有\(m\)个问题。
为了在某种程度上减少输入量,树边和每个点的题目种类\(\texttt{a[]}\)将由以下代码生成:
unsigned int SA, SB, SC;
unsigned int rng61(){
SA ^= SA << 16;
SA ^= SA >> 5;
SA ^= SA << 1;
unsigned int t = SA;
SA = SB;
SB = SC;
SC ^= t ^ SA;
return SC;
}
void gen(){
scanf("%d%d%u%u%u", &n, &p, &SA, &SB, &SC);
for(int i = 2; i <= p; i++)
addedge(i - 1, i);
for(int i = p + 1; i <= n; i++)
addedge(rng61() % (i - 1) + 1, i);
for(int i = 1; i <= n; i++)
a[i] = rng61() % n + 1;
}
\(1\le T\le 3,2\le p\le n\le 10^5,1\le m\le 2\times 10^5,10^4\le SA,SB,SC\le 10^6,1\le x,y,A,B\le n\)。
题解
刘巨:比较麻烦的二维数点。
https://blog.csdn.net/WAautomaton/article/details/87288466
http://jklover.hs-blog.cf/2020/04/14/Loj-2564-原题识别/#more
先考虑链的情况。
链的情况
令\(p_i\)为\(i\)之前最近一个与\(i\)颜色相同的位置。
第一问
区间颜色种数,经典的二维数点问题。
不妨设\(x\leq y\)。我们把\((i,p_i)\)看成平面上的点,那么每次询问的就是\((x\leq i\leq y,p_i< x)\)的点的个数。主席树维护即可。
第二问
不妨设\(A\leq B\)。根据期望的线性性,考虑点\(i\)对答案的贡献。
-
\(A< i\leq B, p_i\leq A\),贡献为\((A-p_i)(B-i+1)\)。
-
\(1\leq i\leq A,x\leq y\),贡献为\((i-p_i)(B-i+1)\)。
-
\(1\leq i\leq A,x\geq y\),贡献为\((i-p_i)(A-i+1)\)。
注意到二、三两种情况会算重\(1\leq i\leq A,x=y\)的贡献。减掉\(A\)即可。
仔细分析一下这些贡献:
-
第一种情况
\[\sum_{i=A+1,p_i\leq A}^B (A-p_i)(B-i+1)\\=A(B+1)\sum 1-(B+1)\sum p_i-A\sum i+\sum p_ii \]主席树维护\(\sum 1,\sum p_i,\sum i,\sum p_ii\)即可。
-
综合第二、三种情况
\[\sum_{i=1}^A (i-p_i)(B-i+1)+\sum_{i=1}^A (i-p_i)(A-i+1)\\=(A+B+2)\sum (i-p_i)-2\sum i(i-p_i) \]前缀和维护\(\sum (i-p_i),\sum i(i-p_i)\)即可。
到此为止,链的情况被我们在\(O(\log n)\)的时间内做完了。
要想推广到树,我们需要了解这题随机方式的一些性质。
随机方式的性质
-
前\(p\)个点形成一条主链,后面的点随机向前面的点连边,那么每个点到主链的距离期望是\(O(\log n)\)的。
-
于是对于树上任意两个点\(x,y\)和它们的最近公共祖先\(f\),\(\min\{\operatorname{dist}(x,f),\operatorname{dist}(y,f)\}\)的期望也是\(O(\log n)\)的。
-
此外因为每个点颜色是在\([1,n]\)中随机的,所以在期望意义下每种颜色只会有\(O(1)\)个点。
有了这些性质,我们可以推广到树上了。
推广到树
在树上就令\(p_i\)表示\(i\)的所有真祖先中,颜色与\(i\)相同且深度最大的点。
第一问
不妨设\(\operatorname{dist}(x,f)< \operatorname{dist}(y,f)\)。
先求出\([f,y]\)这条链上的颜色种数。
再枚举\((f,x]\)链上的每个点\(i\),若\(i\)的颜色是\(c\),就找出所有颜色是\(c\)的点,如果都没有在\([f,y]\)上出现,\(i\)就有贡献\(1\)。
检查某个点是否在\([f,y]\)这条链上可以直接用欧拉序判断。
\((f,x]\)这条链上期望只会有\(O(\log n)\)个点,而每个点期望只会有\(O(1)\)个点和它颜色相同,每次判断也是\(O(1)\)的。
于是单次第一种询问的时间复杂度为\(O(\log n)\)。
第二问
不妨设\(\operatorname{dist}(A,f)< \operatorname{dist}(B,f)\)。
我们可以划分成如下三个子问题:
-
\(x\in [1,f),y\in [1,B]\),这个和链上的情况一模一样。
-
\(x\in [f,A],y\in [1,f]\)。
考虑容斥,用\(x\in [1,A],y\in [1,f)\)的贡献减去\(x\in [1,f),y\in [1,f)\)的贡献。
-
\(x\in [1,A],y\in [1,f)\)和链上的情况一模一样。
-
\(x\in [1,f),y\in [1,f)\)还是利用期望的线性性,化成
\[\sum_{i=1}^{f-1} (2(i-p_i)(f-i)-1) \]
-
-
\(x\in [f,A],y\in [f,B]\)。
-
先算\(i\in [f,B]\)对答案的贡献。
\[\sum_{i=f,p_i< f}^B (B-i+1)(A-f+1) \]
-
-
再考虑\(i\in (f,A]\)对答案的贡献。
首先\(i\)得满足条件\(p_i\leq f\)。设\(j\)为\([f,B]\)中第一个同它颜色相同的点(若不存在则\(j=B+1\))。那么贡献为\((A-i+1)(j-f)\)。
每种情况都可以在\(O(\log n)\)的时间内求出贡献,总复杂度是大常数\(O(n\log n)\)。
我发现在这种毒瘤统计题里面我不打空格代码就会丑得我自己都看不下去了。
#include <bits/stdc++.h>
using namespace std;
#define CO const
#define IN inline
typedef unsigned uint;
typedef long long int64;
template <class T>
IN T read() {
T x = 0, w = 1;
char c = getchar();
for (; !isdigit(c); c = getchar())
if (c == '-')
w = -w;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
return x * w;
}
template <class T>
IN T read(T& x) {
return x = read<T>();
}
array<int64, 2> operator+(CO array<int64, 2>& a, CO array<int64, 2>& b) {
return { a[0] + b[0], a[1] + b[1] };
}
array<int64, 2> operator-(CO array<int64, 2>& a, CO array<int64, 2>& b) {
return { a[0] - b[0], a[1] - b[1] };
}
array<int64, 4> operator+(CO array<int64, 4>& a, CO array<int64, 4>& b) {
return { a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3] };
}
array<int64, 4> operator-(CO array<int64, 4>& a, CO array<int64, 4>& b) {
return { a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3] };
}
int n, len;
uint SA, SB, SC;
IN uint rng61() {
SA ^= SA << 16, SA ^= SA >> 5, SA ^= SA << 1;
unsigned t = SA;
SA = SB, SB = SC, SC ^= t ^ SA;
return SC;
}
CO int N = 4e5 + 10;
vector<int> vec[N];
int col[N];
vector<int> to[N];
int fa[N], dep[N];
int in[N], out[N], dfn, st[N][18], lg[N];
int buc[N], pre[N];
array<int64, 2> sum[N];
namespace seg {
int root[N], tot;
int lc[N * 20], rc[N * 20];
array<int64, 4> tree[N * 20];
#define mid ((l + r) >> 1)
void insert(int& x, int l, int r, int p, CO array<int64, 4>& v) {
++tot, lc[tot] = lc[x], rc[tot] = rc[x];
tree[tot] = tree[x] + v, x = tot;
if (l == r)
return;
if (p <= mid)
insert(lc[x], l, mid, p, v);
else
insert(rc[x], mid + 1, r, p, v);
}
array<int64, 4> query(int x, int l, int r, int ql, int qr) {
if (!x)
return { 0, 0, 0, 0 };
if (ql <= l and r <= qr)
return tree[x];
if (qr <= mid)
return query(lc[x], l, mid, ql, qr);
if (ql > mid)
return query(rc[x], mid + 1, r, ql, qr);
return query(lc[x], l, mid, ql, qr) + query(rc[x], mid + 1, r, ql, qr);
}
#undef mid
} // namespace seg
void dfs(int x) {
st[in[x] = ++dfn][0] = x;
pre[x] = buc[col[x]], buc[col[x]] = x;
seg::root[x] = seg::root[fa[x]]; // 1,p[i],i,p[i]*i
seg::insert(seg::root[x], 0, n, dep[pre[x]], { 1, dep[pre[x]], dep[x], (int64)dep[pre[x]] * dep[x] });
sum[x] = { dep[x] - dep[pre[x]], (int64)(dep[x] - dep[pre[x]]) * dep[x] };
sum[x] = sum[x] + sum[fa[x]]; // i-p[i],i*(i-p[i])
for (int y : to[x]) {
dep[y] = dep[x] + 1;
dfs(y);
st[++dfn][0] = x;
}
out[x] = dfn;
buc[col[x]] = pre[x];
}
int lca(int x, int y) {
x = in[x], y = in[y];
if (x > y)
swap(x, y);
int k = lg[y - x + 1];
return min(st[x][k], st[y - (1 << k) + 1][k], [&](int x, int y) -> bool { return dep[x] < dep[y]; });
}
int vis[N], tim;
IN bool onlink(int x, int y, int p) {
return in[x] <= in[p] and in[p] <= out[x] and in[p] <= in[y] and in[y] <= out[p];
}
int64 solve1(int x, int y) {
++tim;
if (lca(x, len) > lca(y, len))
swap(x, y);
int f = lca(x, y);
int64 ans = seg::query(seg::root[y], 0, n, 0, dep[f] - 1)[0];
ans = ans - seg::query(seg::root[fa[f]], 0, n, 0, dep[f] - 1)[0];
for (int i = x; i != f; i = fa[i])
if (vis[col[i]] != tim) {
vis[col[i]] = tim;
bool flag = 1;
for (int p : vec[col[i]])
if (onlink(f, y, p)) {
flag = 0;
break;
}
ans += flag;
}
return ans;
}
IN int64 calc(int A, int B, CO array<int64, 4>& t) {
int64 ans = (t[0] * dep[A] - t[1]) * (dep[B] + 1) - t[2] * dep[A] + t[3];
ans += (dep[A] + dep[B] + 2) * sum[A][0] - 2 * sum[A][1] - dep[A];
return ans;
}
int64 solve2(int A, int B) {
++tim;
if (lca(A, len) > lca(B, len))
swap(A, B);
int f = lca(A, B);
array<int64, 4> t1 = seg::query(seg::root[B], 0, n, 0, dep[f] - 1);
t1 = t1 - seg::query(seg::root[fa[f]], 0, n, 0, dep[f] - 1);
array<int64, 4> t2 = seg::query(seg::root[A], 0, n, 0, dep[f] - 1);
t2 = t2 - seg::query(seg::root[fa[f]], 0, n, 0, dep[f] - 1);
int64 ans = calc(fa[f], B, t1) + calc(fa[f], A, t2);
ans -= 2 * (dep[f] * sum[fa[f]][0] - sum[fa[f]][1]) - (dep[f] - 1);
ans += ((dep[B] + 1) * t1[0] - t1[2]) * (dep[A] - dep[f] + 1);
vector<int> stk;
for (int i = A; i != f; i = fa[i]) stk.push_back(i);
for (; stk.size(); stk.pop_back()) {
int i = stk.back();
if (vis[col[i]] != tim) {
vis[col[i]] = tim;
int mn = dep[B] + 1;
for (int p : vec[col[i]])
if (onlink(f, B, p))
mn = min(mn, dep[p]);
ans += (int64)(mn - dep[f]) * (dep[A] - dep[i] + 1);
}
}
return ans;
}
void real_main() {
read(n), read(len);
read(SA), read(SB), read(SC);
for (int i = 2; i <= n; ++i) to[fa[i] = i <= len ? i - 1 : rng61() % (i - 1) + 1].push_back(i);
for (int i = 1; i <= n; ++i) vec[col[i] = rng61() % n + 1].push_back(i);
dep[1] = 1, dfs(1);
lg[0] = -1;
for (int i = 1; i <= dfn; ++i) lg[i] = lg[i >> 1] + 1;
for (int j = 1; j <= lg[dfn]; ++j)
for (int i = 1; i + (1 << j) - 1 <= dfn; ++i)
st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1],
[&](int x, int y) -> bool { return dep[x] < dep[y]; });
for (int m = read<int>(); m--;) {
if (read<int>() == 1) {
int x = read<int>(), y = read<int>();
printf("%lld\n", solve1(x, y));
} else {
int A = read<int>(), B = read<int>();
printf("%lld\n", solve2(A, B));
}
}
dfn = seg::tot = 0;
for (int i = 1; i <= n; ++i) {
seg::root[i] = vis[i] = buc[i] = 0;
to[i].clear(), vec[i].clear();
}
}
int main() {
for (int T = read<int>(); T--;) real_main();
return 0;
}