2021“MINIEYE杯”中国大学生算法设计超级联赛(4)1002. Kanade Loves Maze Designing(DFS/树状数组)
Problem Description
Kanade is designing a mini-game. It's a puzzle game that orders players to get out of a maze. Players should also collect as many kinds of elements as they can to gain a better score.
For the easy mode, the maze can be described as a tree. There are n crossings and n−1 undirected passages which make the n crossings connected. The n crossings is numbered with integers from 1 to n. Exactly one element is placed on each crossing. The kind of element placed at crossing i is denoted by an integer ci in the range [1,n].
To evaluate the maze's difficulty, Kanade wants to know how many kinds of elements appear on p(u,v) for every two integers u,v∈[1,n]. p(u,v) indicates the simple path from crossing u to crossing v in the maze.
Input
The first line of input contains one integer T (1≤T≤10), indicating the number of test cases.
For each test case, the first line contains one integer n (2≤n≤2000), indicating the number of crossings in the maze.
The second line contains n−1 integers p2,p3,…,pn (pi<i), indicating that the i-th crossing is connected with the pi-th crossing by a passage.
The third line contains n integers c1,c2,…,cn (1≤ci≤n), indicating that the kind of element placed at crossing i is ci.
It is promised that for all test cases, ∑n≤5000.
Output
For each test case, output n lines. Each line contains two integers. Let ai,j be the number of kinds of elements appear on p(i,j). Let
f(i,x)=∑j=1nai,jxj−1
Then for the i-th line, output f(i,19560929)mod(109+7) and f(i,19560929)mod(109+9), space separated.
Sample Input
1
6
1 1 2 2 3
1 1 4 5 1 4
Sample Output
495644981 518101442
495644981 518101442
397599492 896634980
612255048 326063507
495644981 518101442
397599492 896634980
Hint
Let A=(aij), then for the example, A equals to
1 1 2 2 1 2
1 1 2 2 1 2
2 2 1 3 2 1
2 2 3 1 2 3
1 1 2 2 1 2
2 2 1 3 2 1
写了波暴力卡过了...
注意到最后输出的形式比较特殊,是每一个点到其他各个点的树上路径的答案进行运算得到的。又注意到点的范围实际上不大(2e3),考虑对每个点dfs得到答案然后计算并输出。问题就转换成了怎么求树上每个点到树根的路径上有多少种不同的点权。因为点权的范围是1到n,因此可以用树状数组维护(桶排序的思想)。每搜索到一个点就直接统计,然后遍历这个点关联的边,如果下一个点的权值已经出现过则不modify,如果没有出现过则让这个位置+1,说明这个位置对应的权值出现过则不用管。注意如果dfs前更新过,dfs完一定要回溯。
#include <bits/stdc++.h>
#define mod1 1000000007
#define mod2 1000000009
#define C 19560929
#define int long long
#define N 2005
using namespace std;
int n, head[N], ver[2 * N], Next[2 * N], tot;
int c[N];
int num[N];
void add(int x, int y) {
ver[++tot] = y, Next[tot] = head[x], head[x] = tot;
}
int b[N];
void modify(int x, int y) {
for(; x <= n; x += (x & -x)) b[x] += y;
}
int query(int x) {
int ans = 0;
for(; x; x -= (x & -x)) {
ans += b[x];
}
return ans;
}
bitset<2005> bst;
void dfs(int x, int pre) {
num[x] = query(n);
//num[x] = bst.count();
for(int i = head[x]; i; i = Next[i]) {
int y = ver[i];
if(y == pre) continue;
int tmp = query(c[y]) - query(c[y] - 1);
//int tmp = bst[c[y]];
if(!tmp) {
modify(c[y], 1);
//bst[c[y]] = 1;
}
dfs(y, x);
if(!tmp) {
modify(c[y], -1);
//bst[c[y]] = 0;
}
}
}
int p1[2005], p2[2005];
signed main() {
int t;
cin >> t;
p1[1] = p2[1] = 1;
for(int i = 2; i <= 2000; i++) {
p1[i] = p1[i - 1] * C % mod1;
p2[i] = p2[i - 1] * C % mod2;
}
while(t--) {
tot = 0;
memset(head, 0, sizeof(head));
//bst.reset();
scanf("%lld", &n);
for(int i = 1; i <= n + 10; i++) b[i] = 0;
for(int i = 2; i <= n; i++) {
int p;
scanf("%lld", &p);
add(i, p);
add(p, i);
}
for(int i = 1; i <= n; i++) {
scanf("%lld", &c[i]);
}
for(int i = 1; i <= n; i++) {
modify(c[i], 1);
//bst[c[i]] = 1;
dfs(i, 0);
int ans1 = 0, ans2 = 0;
for(int j = 1; j<= n; j++) {
ans1 = (ans1 + num[j] * p1[j] % mod1) % mod1;
ans2 = (ans2 + num[j] * p2[j] % mod2) % mod2;
}
modify(c[i], -1);
//bst[c[i]] = 0;
printf("%lld %lld\n", ans1, ans2);
}
}
return 0;
}