T20201109
T1(树上的数)
直观感觉是 \(\text{DFS}\) 序 + 线段树,时间复杂度 \(O(m\log n)\),然后常数大的一批,\(\text{T}\) 成 \(60\) 分。
其实吧,可以直接每次暴力遍历整棵树,给一个永久标记即可
Code
Code
1 |
|
T2(时代的眼泪)
首先交换求和顺序,然后就可以发现诸多有用的性质。
算法一 (25分)
就是每次暴力查询,单次查询的时间复杂度为 \(O(n\log n)\)。
就是可以用树状数组维护,这一条链有多少个点的权值严格大于当前点,然后就可以加入贡献了。
Code
1 | int root, t[maxn]; |
算法二 (20分)
整张图是一个 \(1\sim n\) 的链,所以每个点的贡献是一定的,就可以 \(O(n\log n)\) 预处理出所有点贡献,做两次前缀和就可以了。
结合算法一,就可以获得 \(45\) 分的好成绩。
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44int root, ans;
ll l[maxn], r[maxn];
int t[maxn];
inline void add(int x, int val)
{
while (x <= n) {
t[x] += val;
x += lowbit(x);
}
}
inline int query(int x)
{
int res(0);
while (x) {
res += t[x];
x -= lowbit(x);
}
return res;
}
int solve()
{
memset (l, 0, sizeof l);
memset (r, 0, sizeof r);
memset (t, 0, sizeof t);
for (int i = 1; i <= n; ++i) {
l[i] = l[i - 1] + query(val[i] - 1);
add(val[i], 1);
}
memset (t, 0, sizeof t);
for (int i = n; i; --i) {
r[i] = r[i + 1] + query(val[i] - 1);
add(val[i], 1);
}
while (m--) {
root = read();
printf ("%lld\n", l[root] + r[root]);
}
}
算法三 (20分)
就是一个以 \(1\) 为中心的一个菊花图,这个手摸一下就可以发现,我们可以 \(O(n)\),预处理出 \(1\) 作为根时的答案,然后在考虑根变成其他的时候的答案变化量,这个变化量的处理单词时间复杂度是 \(O(\log n)\)。
结合算法一、算法二就可以取得 \(65\) 分的好成绩。
Code
1 | int root, ans, jub(0); |
About 80
就是 \(Q=1\) 时,可以用算法一,这样一来就可以拿到 \(80\) 分的好成绩
我考场就是 \(Q=1\) 判错了就只有 \(65\)。。。
所以个人认为这道题十分的良心,给足了部分分,整整 \(80\)。
About 100
这其实是一个换根 \(dp\),看上去也很像一个换根 \(dp\),可惜当时就是没有推出来。
就是考虑,从一个根到另一个根,它少了些什么,它又该多一些什么。


可以发现,少的那一部分是现在的根对原来的根的贡献,而多的那一部分则是原来的根作为子树对现在的根的贡献。
首先,声明几个数组
- \(f_i\) 表示在以 \(i\) 为根的子树中,有多少个点的点权严格小于当前节点
- \(g_i\) 表示在以 \(i\) 为根的子树中,有多少个点的点权严格小于当前节点的父亲
- \(k_i\) 表示在所有的点里面,有多少个点的点权严格小于当前节点
所有可以有如下转移: \[ ans_v = ans_u - g_v + k_v - f_v; \] 然后 \(f、g\) 的求法是十分的巧妙,它可以类似查分的形式出现,所以这道题就结束了。
Code
Code
1 |
|
T3(传统艺能)
考场暴力 \(25\),还行吧
就是考虑这么一个矩阵 \(A_{i,j}\) 表示以 \(i\) 开头以 \(j\) 结尾的方案数,但是这个 \(j\) 其实是不存在的。
所以当两个矩阵相乘的时候,刚好就表示了这个方案的转移,所以可以用线段树来维护,每一个节点就是一个矩阵,就这样结束了。
至于为什么它能保证本质不同呢?
这个与矩阵乘法的性质有关,就不赘述了。
Code
Code
1 |
|