周一
Polygon(思维)
如果最小n-1条边之和小于等于最大边,那么肯定不行,所以至少要满足大于。然后发现大于就一定可以构成多边形。也就是发现一个必要条件,然后发现这也是充分条件
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a) ; i < (b); i++)
#define _for(i, a, b) for(int i = (a) ; i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
int a[N], n;
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
int sum = 0;
_for(i, 1, n - 1) sum += a[i];
puts(sum > a[n] ? "YES" : "NO");
return 0;
}
Plus(打表)
打了个表发现只有2 3 但是因为数据一大会爆long long,不是很确定
交了一发,wa了,发现是没开long long 然后开long long就过了
不要犯低级错误,交之前检查一遍
Generator(期望dp+打表)
首先很容易写一个期望dp,dp[i]表示当前是i时,到达终点的期望。
于是就可以写一个O(n)的dp,但是题目数据范围是1e9
其实这时可以猜到必然有什么规律
把dp的表打出来,把dp[i] - dp[i - 1]打出来,发现每次增加1/i,也就是说有了可加性
因此1e9可以拆成10个1e8,预处理每一个1e8的答案,对于输入n,把前面多个1e8直接加到答案中,多出来的部分就暴力计算。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
double t[] = {18.9978964139, 0.6931471781, 0.4054651073, 0.2876820720,
0.2231435511, 0.1823215566, 0.1541506797, 0.1335313925, 0.1177830356, 0.1053605156};
int main()
{
int n;
scanf("%d", &n);
n--;
double ans = 1;
int cnt = 0, st = 1;
while(n - st + 1 >= 1e8)
{
st += 1e8;
ans += t[cnt++];
}
_for(i, st, n)
ans += 1.0 / i;
printf("%.10f\n", ans);
return 0;
}
周二
D. Problem with Random Tests(思维)
赛时是做出来了,但是实现的比较麻烦
看了第一名的代码,用了string的很多特性,比如在长度相同的情况下用字典序直接比较,用find函数,用substr函数,这使得写起来很短,很方便。字符'0'和'1'可以直接或,一个是48一个是49
还有就是要训练在压力下写题的心态,不着急,不乱
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int n; string s;
cin >> n >> s;
if(s.find('1') == string::npos)
{
cout << "0" << "\n";
return 0;
}
s = s.substr(s.find('1'));
if(s.find('0') == string::npos)
{
cout << s << "\n";
return 0;
}
int st = s.find('0');
string ans = s;
int len = s.size();
_for(i, 0, st)
{
string res = s;
for(int j = 0; j + i < len; j++)
res[j + i] |= s[j];
ans = max(ans, res);
}
cout << ans << "\n";
return 0;
}
Maze(bfs)
这题的关键在于,一般bfs中的vis数组都是记录坐标的
但是这道题多了一些东西,坐标并不能代表一个点的状态,还需要记录从哪个方向来,目前走了多少。
这样的时间复杂度是4e6左右,是不会T的。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 110;
int dir[4][2] = {0, 1, 0, -1, 1, 0, -1, 0};
int dis[N][N][N][4];
char s[N][N];
int n, m;
struct node
{
int x, y, step, dir, len;
};
int bfs()
{
if(s[1][1] != '.' || s[n][n] != '.') return 1e9;
_for(i, 1, n)
_for(j, 1, n)
_for(r, 0, m)
rep(k, 0, 4)
dis[i][j][r][k] = 1e9;
queue<node> q;
q.push({1, 1, 0, -1, 0});
while(!q.empty())
{
node u = q.front(); q.pop();
int x = u.x, y = u.y;
rep(i, 0, 4)
{
int xx = x + dir[i][0], yy = y + dir[i][1];
if(xx < 1 || xx > n || yy < 1 || yy > n || s[xx][yy] != '.') continue;
node v = {xx, yy, u.step + 1, i, u.dir == i ? u.len + 1 : 1};
if(v.len > m) continue;
if(xx == n && yy == n) return v.step;
if(v.step < dis[xx][yy][v.len][i])
{
dis[xx][yy][v.len][i] = v.step;
q.push(v);
}
}
}
return 1e9;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%s", s[i] + 1);
int ans = bfs();
printf("%d\n", ans == 1e9 ? -1 : ans);
}
return 0;
}
B. Dragon slayer(bfs+状压)
思路比较明显,状压+bfs,但是要注意一些细节
比如用左下角的坐标表示当前这个格子,对于墙需要双向,然后长度需要减去1
坐标范围是0~n-1和0~m-1
当然还有一种方法,二进制枚举+dfs
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 20;
int dir[4][2] = {0, 1, 0, -1, 1, 0, -1, 0};
int ban[N][N][N][N], n, m, k;
int xs, ys, xt, yt;
int vis[N][N][1 << 15];
struct node
{
int x, y, state;
};
void bfs()
{
_for(i, 0, n)
_for(j, 0, m)
rep(S, 0, 1 << k)
vis[i][j][S] = 0;
queue<node> q;
q.push({xs, ys, 0});
vis[xs][ys][0] = 1;
while(!q.empty())
{
node u = q.front(); q.pop();
int x = u.x, y = u.y;
rep(i, 0, 4)
{
int xx = x + dir[i][0], yy = y + dir[i][1];
if(xx < 0 || xx >= n || yy < 0 || yy >= m) continue;
int state = u.state;
if(ban[x][y][xx][yy] != -1)
{
int cur = ban[x][y][xx][yy];
if(!((state >> cur) & 1)) state |= 1 << cur;
}
if(!vis[xx][yy][state])
{
q.push({xx, yy, state});
vis[xx][yy][state] = 1;
}
}
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d%d", &n, &m, &k);
scanf("%d%d%d%d", &xs, &ys, &xt, &yt);
memset(ban, -1, sizeof ban);
_for(i, 1, k)
{
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
if(x1 == x2)
{
if(y1 > y2) swap(y1, y2);
y2--;
if(x1 > 0)
_for(j, y1, y2)
{
ban[x1 - 1][j][x1][j] = i - 1;
ban[x1][j][x1 - 1][j] = i - 1;
}
}
else
{
if(x1 > x2) swap(x1, x2);
x2--;
if(y1 > 0)
_for(j, x1, x2)
{
ban[j][y1 - 1][j][y1] = i - 1;
ban[j][y1][j][y1 - 1] = i - 1;
}
}
}
bfs();
int ans = 1e9;
rep(S, 0, 1 << k)
if(vis[xt][yt][S])
{
int cnt = 0;
rep(i, 0, k)
if((S >> i) & 1)
cnt++;
ans = min(ans, cnt);
}
printf("%d\n", ans);
}
return 0;
}
K. Random(思维)
对于求和,一个数的期望是0.5
而每次一半概率减去最大,一半概率减去最小,这样的话一个数的期望还是0.5
因此答案是(n - m) / 2,求一下逆元
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
ll binpow(ll a, ll b)
{
ll res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
ll inv(ll x) { return binpow(x, mod - 2); }
int main()
{
int T; scanf("%d", &T);
while(T--)
{
int n, m;
scanf("%d%d", &n, &m);
printf("%lld\n", (n - m) * inv(2) % mod);
}
return 0;
}
C. Backpack(背包+bitset优化)
对于枚举j,f[j] |= f[j - w] 可以写成二进制,即f |= f << w用bitset优化
这题就是加了一维而已
写法可以比较简洁,省掉第一维前i个物品
网上有字典树的做法,但是正确性有问题,这种做法每次加入字典树的值是把当前状态的最大值加入,实际上不一定。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = (1 << 10) + 10;
bitset<N> f[N];
int n, m, w[N], v[N];
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%d%d", &v[i], &w[i]);
rep(j, 0, 1 << 10)
f[j].reset();
f[0][0] = 1;
_for(i, 1, n)
rep(j, 0, 1 << 10)
f[j] |= f[j ^ w[i]] << v[i];
int ans = -1;
for(int S = (1 << 10) - 1; S >= 0; S--)
if(f[S][m])
{
ans = S;
break;
}
printf("%d\n", ans);
}
return 0;
}
A. String(kmp树+同余+二分)
首先暴力做法就是一直跳next,每个都判断是否是k的倍数
考虑如何优化,首先建立一颗kmp树,0为根节点
对于每个节点u,需要找它有多少个祖先v,满足
2v-u > 0
2v-u = 0 (mod k)
对于第二个条件,即2v与u同余,那么可以在dfs的过程中用vector存一下不同余数都有哪些点,即在node[2u%k]中加入2u
对于当前点u,查找node[u%k],这里面都是可能的答案
然后要满足里面的点要大于u,那么在vector中二分出一个位置,然后可以算出个数。二分算个数的话vector可以,set不行。这里加入的时候vector本身就是有序的
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int N = 1e6 + 10;
vector<int> g[N], node[N];
int ans[N], Next[N], n, k;
char str[N];
void get_next()
{
Next[0] = -1;
int i = 0, j = -1;
while(i < n)
{
if(j == -1 || str[i] == str[j])
{
i++; j++;
Next[i] = j;
g[Next[i]].push_back(i);
}
else j = Next[j];
}
}
void dfs(int u)
{
node[2 * u % k].push_back(2 * u);
int pos = upper_bound(node[u % k].begin(), node[u % k].end(), u) - node[u % k].begin();
ans[u] = node[u % k].size() - pos;
for(int v: g[u]) dfs(v);
node[2 * u % k].pop_back();
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%s%d", str, &k);
n = strlen(str);
_for(i, 0, n) g[i].clear();
get_next();
dfs(0);
ll res = 1;
_for(i, 1, n)
{
ll cur = ans[i] + 1;
res = res * cur % mod;
}
printf("%lld\n", res);
}
return 0;
}
还又一种O(n)的做法,即可以用两次kmp求出最长的小于等于一半的串。比赛时是用倍增求这个T了,这个东西其实可以用kmp求,只要保证匹配的时候小于等于一半的串,注意不匹配时是j=Next[j]不是j=Next2[j]
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int N = 1e6 + 10;
vector<int> g[N], q[N];
int Next[N], Next2[N], cnt[N], ans1[N], ans2[N], n, k;
char str[N];
void get_next()
{
Next[0] = -1;
int i = 0, j = -1;
while(i < n)
{
if(j == -1 || str[i] == str[j])
{
i++; j++;
Next[i] = j;
g[Next[i]].push_back(i);
}
else j = Next[j];
}
Next2[0] = -1;
i = 0, j = -1;
while(i < n)
{
if((j == -1 || str[i] == str[j]) && 2 * j + 2 <= i + 1)
{
i++; j++;
Next2[i] = j;
q[Next2[i]].push_back(i);
}
else j = Next[j];
}
}
void dfs(int u)
{
cnt[2 * u % k]++;
ans1[u] = cnt[u % k];
for(int x: q[u]) ans2[x] = cnt[x % k];
for(int v: g[u]) dfs(v);
cnt[2 * u % k]--;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%s%d", str, &k);
n = strlen(str);
_for(i, 0, n) g[i].clear(), q[i].clear();
get_next();
dfs(0);
ll res = 1;
_for(i, 1, n)
{
ll cur = ans1[i] - ans2[i] + 1;
res = res * cur % mod;
}
printf("%lld\n", res);
}
return 0;
}
周三
L Alice and Bob(博弈+二进制)
这题Alice和Bob的操作不同,不能用np态来考虑
主要是举几个例子,分析什么时候能赢,找规律
发现一个0Alice赢,两个1Alice赢,一个1两个2Alice赢
这里可以往二进制的方向想,即两个2换成一个1,变成两个1,Alice赢
仔细想一下,Alice可以把数均分成两份,Bob删掉一半,另一半减去1,就相当于a[i - 1] += a[i] / 2
Alice可以一直均分,那么数就越来越小,出现0就赢
所以就可以看作二进制。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
int a[N], n;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 0, n) scanf("%d", &a[i]);
for(int i = n; i >= 1; i--)
a[i - 1] += a[i] / 2;
puts(a[0] ? "Alice" : "Bob");
}
return 0;
}
Capital Program(bfs)
一开始想的是二分答案+树形dp,但是发现一个问题,就是只能统计当前节点的儿子的距离,并不能统计当前节点的父亲距离,然后就卡住了。那么就需要选择一个合适的根,那这个跟该怎么选呢?不好说
其实不应该从一个根dfs,应该从外围往中间bfs,把所有度数为1的点加入队列中,然后往内部bfs。因为bfs是一层一层的,所以每一次肯定是当前答案最好的结果了,所以是对的。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
vector<int> g[N];
int d[N], dis[N], n, k;
int main()
{
scanf("%d%d", &n, &k);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
d[u]++;
d[v]++;
}
queue<int> q;
_for(i, 1, n)
if(d[i] == 1)
{
dis[i] = 1;
q.push(i);
}
int ans = 0, cnt = n;
while(!q.empty())
{
int u = q.front(); q.pop();
ans = max(ans, dis[u]);
if(--cnt == k) break;
for(int v: g[u])
{
dis[v] = max(dis[v], dis[u] + 1);
if(--d[v] == 1) q.push(v);
}
}
printf("%d\n", ans);
return 0;
}
Game(博弈+猜结论)
这题和之前一道题很像,但是不一样的是当前的石子可以移动到很多堆
整体的思路是手推np态,猜结论,证明
推一下,可以猜一下堆奇数是N态,堆偶数的时候,要成对才是P态
在成对的时候,先手操作一堆石子,后手可以操作成对的另一堆石子,然后做一样的操作,这样整体又是成对的,确实是可以的。是符合NP态的,同时全0是终止状态,也是P态
实现的话就是莫队,网上还有一种比较骚的做法就是随机,就是把每个数映射成另一个随机的值,然后用异或和为0
成对的数异或和为0,但是异或和为0不一定成对。
为了避免异或和为0但不成对的情况,将每个数随机映射成一个值,这样几乎就异或和不可能为0了,但这时成对映射后依然成对,异或和依然为0
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int a[N], s[N], id[N], n;
int main()
{
srand(time(0));
_for(i, 1, n) id[i] = rand();
int n, q;
scanf("%d%d", &n, &q);
_for(i, 1, n) scanf("%d", &a[i]), a[i] = id[a[i]];
_for(i, 1, n) s[i] = s[i - 1] ^ a[i];
_for(i, 1, q)
{
int l, r;
scanf("%d%d", &l, &r);
if((r - l + 1) % 2 == 1) puts("Alice");
else
{
int cur = s[r] ^ s[l - 1];
puts(cur ? "Alice" : "Bob");
}
}
return 0;
}
I Laser(思维)
关键是发现一个点肯定在4条直线中的其中一条
随便找一个点,以当前为竖线为例,找一个不在竖线上的点,然后这个点有三种情况得到三个交点,然后判断交点。
然后把图旋转45度再做,重复4次即可
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int x[N], y[N], n;
bool can(int x, int y)
{
return !x || !y || x == y || x + y == 0;
}
bool pd(int xx, int yy)
{
_for(i, 1, n)
if(!can(x[i] - xx, y[i] - yy))
return false;
return true;
}
bool check() //以当前直线是竖线为例,比较好写
{
int flag = 1;
_for(i, 1, n)
{
if(x[i] == x[1]) continue;
flag = 0;
if(pd(x[1], y[i])) return true;
if(pd(x[1], y[i] + (x[i] - x[1]))) return true;
if(pd(x[1], y[i] - (x[i] - x[1]))) return true;
}
if(flag) return true;
return false;
}
bool solve()
{
_for(t, 1, 4)
{
if(check()) return true;
_for(i, 1, n)
{
int tx = x[i], ty = y[i];
x[i] = tx - ty; y[i] = tx + ty; //旋转45度
}
}
return false;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d%d", &x[i], &y[i]);
puts(solve() ? "YES" : "NO");
}
return 0;
}
P1967 [NOIP2013 提高组] 货车运输(krusal重构树模板)
给一个无向图,每次询问两点之间所有路径种,边权最小值的最大值。
那么肯定是边越大越好,于是用最大生成树重构,这时就变为最大生成树上两点之间唯一路径的最小值。这个可以用krusal重构树求
krusal重构树就是n个节点为叶子,然后遍历边,每个边建立一个新点,点权为边权,然后安装类似哈夫曼树那样合并。最后是一颗二叉树。如果不联通就是二叉树森林。细节上,编号越大的点越可能是根节点,dfs的时候编号从大到小遍历。空间记得开两倍点数
它有一个性质就是两点之间的边权最小值即重构树上两点的LCA的点权,因此求LCA即可
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e4 + 10;
struct Edge
{
int u, v, w;
bool operator < (const Edge& rhs) const
{
return w > rhs.w;
}
};
vector<Edge> e;
int f[N], val[N], up[N][25], d[N], cnt, n, m;
vector<int> g[N];
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
void dfs(int u, int fa)
{
d[u] = d[fa] + 1;
up[u][0] = fa;
_for(j, 1, 20)
up[u][j] = up[up[u][j - 1]][j - 1];
for(int v: g[u]) dfs(v, u);
}
int lca(int u, int v)
{
if(d[u] < d[v]) swap(u, v);
for(int j = 20; j >= 0; j--)
if(d[up[u][j]] >= d[v])
u = up[u][j];
if(u == v) return u;
for(int j = 20; j >= 0; j--)
if(up[u][j] != up[v][j])
u = up[u][j], v = up[v][j];
return up[u][0];
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, 2 * n) f[i] = i;
while(m--)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
e.push_back({u, v, w});
}
sort(e.begin(), e.end());
cnt = n;
for(auto x: e)
{
int u = x.u, v = x.v, w = x.w;
int fu = find(u), fv = find(v);
if(fu != fv)
{
val[++cnt] = w;
f[fu] = f[fv] = cnt;
g[cnt].push_back(fu);
g[cnt].push_back(fv);
}
}
//注意图可能不联通。然后越大的点越可能是根节点
for(int i = cnt; i >= 1; i--)
if(!d[i])
dfs(i, 0);
int q; scanf("%d", &q);
while(q--)
{
int u, v;
scanf("%d%d", &u, &v);
if(find(u) != find(v)) puts("-1");
else printf("%d\n", val[lca(u, v)]);
}
return 0;
}
Life is a Game(krusal重构树+倍增优化)
首先过程就是从起点开始,走一条最短的边,然后不断往外扩展。
这个过程非常符合krusal重构树的过程
从叶子节点开始,如果能跳到父亲就跳,之后当前子树的叶子都能达到。
暴力跳会超时,考虑怎么优化
考虑跳一次,需要满足k+sum[u] >= val[fa]
也就是k >= val[fa] - sum[u]
也就是说,只要k大于等于一个值即可跳,那么如果要连跳两步,那么就是要都大于等于,也就是大于最大值。
于是可以倍增优化,d[i][j]表示从i跳2^j步需要多少,初始化就是上面说的,求数组就取最大值。
然后注意一个细节,向上跳是可能跳到0节点的,要判断一下
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
int f[N], a[N], val[N], sum[N], n, m, q, cnt;
int up[N][25], d[N][25];
vector<int> g[N];
struct Edge
{
int u, v, w;
bool operator < (const Edge& rhs) const
{
return w < rhs.w;
}
};
vector<Edge> e;
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
void dfs(int u, int fa)
{
up[u][0] = fa;
_for(j, 1, 20) up[u][j] = up[up[u][j - 1]][j - 1];
sum[u] = a[u];
for(int v: g[u])
{
dfs(v, u);
sum[u] += sum[v];
}
}
void dfs2(int u, int fa)
{
d[u][0] = val[fa] - sum[u];
_for(j, 1, 20) d[u][j] = max(d[u][j - 1], d[up[u][j - 1]][j - 1]);
for(int v: g[u]) dfs2(v, u);
}
int main()
{
scanf("%d%d%d", &n, &m, &q);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, 2 * n) f[i] = i;
while(m--)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
e.push_back({u, v, w});
}
sort(e.begin(), e.end());
cnt = n;
for(auto [u, v, w]: e)
{
u = find(u); v = find(v);
if(u != v)
{
val[++cnt] = w;
f[u] = f[v] = cnt;
g[cnt].push_back(u);
g[cnt].push_back(v);
}
}
dfs(cnt, 0);
dfs2(cnt, 0);
while(q--)
{
int u, k;
scanf("%d%d", &u, &k);
for(int j = 20; j >= 0; j--)
if(k >= d[u][j] && up[u][j]) //这里是有可能跳到0节点的,要去掉
u = up[u][j];
printf("%d\n", sum[u] + k);
}
return 0;
}
周四
D. Ball(枚举顺序+bitset优化)
很容易想到枚举两个点ab,dis[a][b]为质数,然后找第三个点c,使得一条边小于等于这个数一条边大于等于这个数
这个看起来是n^3的,如果bitset优化就不会超时
怎么迅速找到边小于当前和大于当前的呢,枚举顺序!
即把边从小到大枚举,那么小于等于当前边的就在之前枚举过了,大于等于当前边的还没有枚举
用bitset表示两个点的边有没有遍历过,那么此时需要一个为0一个为1,因此异或后看有多少个1即可
注意曼哈顿距离会到2e5,我一开始开1e5RE了一发
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a) ; i < (b); i++)
#define _for(i, a, b) for(int i = (a) ; i <= (b); i++)
using namespace std;
typedef long long ll;
const int M = 2e5 + 10;
const int N = 2e3 + 10;
bool vis[M];
vector<int> p;
bitset<N> g[N];
int x[N], y[N], n, m;
struct Edge
{
int u, v, w;
bool operator < (const Edge& rhs) const
{
return w < rhs.w;
}
};
vector<Edge> e;
void init()
{
vis[0] = vis[1] = 1;
_for(i, 2, 2e5)
{
if(!vis[i]) p.push_back(i);
for(int x: p)
{
if(i * x > 2e5) break;
vis[i * x] = 1;
if(i % x == 0) break;
}
}
}
int main()
{
init();
int T; scanf("%d", &T);
while(T--)
{
e.clear();
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%d%d", &x[i], &y[i]), g[i].reset();
_for(i, 1, n)
_for(j, i + 1, n)
e.push_back({i, j, abs(x[i] - x[j]) + abs(y[i] - y[j])});
sort(e.begin(), e.end());
ll ans = 0;
for(auto [u, v, w] : e)
{
if(!vis[w]) ans += (g[u] ^ g[v]).count();
g[u][v] = g[v][u] = 1;
}
printf("%lld\n", ans);
}
return 0;
}
简单瞎搞题(bitset)
看作分组背包,每一组必须选一个。用bitset优化即可。
注意要区分必须选还是可以不选,这两种的dp方程是不一样的
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
bitset<N> f[110];
int n;
int main()
{
scanf("%d", &n);
f[0][0] = 1;
_for(i, 1, n)
{
int l, r;
scanf("%d%d", &l, &r);
_for(j, l, r) f[i] |= f[i - 1] << (j * j);
}
printf("%d\n", f[n].count());
return 0;
}
H Path(分层图最短路+Set)
走过特殊边后就有一种能力,对于相邻的边,费用可以减k,对于不相邻的边,费用为0
那么可以想到分层图,第一层是正常图,第二层是当前由特殊能力的图。
每次跑完特殊边就可以跳到第二层。
考虑怎么处理不相邻的边费用为0,用一个set维护一个还未得到最短路的点,然后每次遍历这个集合,得到了就删去。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
struct node
{
int v; ll w; int ty;
bool operator < (const node& rhs) const
{
return w > rhs.w;
}
};
vector<node> g[N];
int vis[N], id, n, m, s, k;
ll d[N << 1];
void solve()
{
set<int> S;
priority_queue<node> q;
_for(i, 1, 2 * n) d[i] = 1e18;
_for(i, 1, n) S.insert(i); //费用为0的边只会跳到普通层
d[s] = id = 0;
q.push({s, 0});
while(!q.empty())
{
auto [u, w, ty] = q.top(); q.pop();
if(u <= n) S.erase(u);
else S.erase(u - n);
if(u > n) //当前在特殊层
{
id++;
for(auto [v, w2, ty2]: g[u - n]) vis[v] = id;
vector<int> del;
for(int x: S)
if(vis[x] != id)
{
del.push_back(x);
d[x] = d[u]; //跑到普通层去
q.push({x, d[x], 0});
}
for(int x: del) S.erase(x);
for(auto [v, w2, ty2]: g[u - n])
{
if(ty2) v += n;
if(d[u] + w2 - k < d[v])
{
d[v] = d[u] + w2 - k;
q.push({v, d[v], ty2});
}
}
}
else
{
for(auto [v, w2, ty2]: g[u])
{
if(ty2) v += n;
if(d[u] + w2 < d[v])
{
d[v] = d[u] + w2;
q.push({v, d[v], ty2});
}
}
}
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d%d%d", &n, &m, &s, &k);
_for(i, 1, n) vis[i] = 0, g[i].clear();
while(m--)
{
int u, v, w, ty;
scanf("%d%d%d%d", &u, &v, &w, &ty);
g[u].push_back({v, w, ty});
}
solve();
_for(i, 1, n)
{
ll cur = min(d[i], d[i + n]);
printf("%lld ", cur == 1e18 ? -1 : cur);
}
puts("");
}
return 0;
}
P4768 [NOI2018] 归程(krusal重构树)
先考虑是怎么一个过程,显然是先坐车到一个尽可能大的连通块,然后在这个联通块中走最短路。走尽可能大的一个联通块,可以用krusal重构树,即从当前点尽可能往上跳。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 4e5 + 10;
struct node
{
int v; ll w;
bool operator < (const node& rhs) const
{
return w > rhs.w;
}
};
vector<node> g[N];
int f[N], val[N], n, m, cnt;
int up[N][25];
ll d[N], s[N];
struct Edge
{
int u, v; ll w;
bool operator < (const Edge& rhs) const
{
return w > rhs.w;
}
};
vector<Edge> e;
void solve()
{
priority_queue<node> q;
_for(i, 1, n) d[i] = 1e18;
d[1] = 0;
q.push({1, d[1]});
while(!q.empty())
{
node x = q.top(); q.pop();
int u = x.v;
if(d[u] != x.w) continue;
for(auto [v, w]: g[u])
if(d[v] > d[u] + w)
{
d[v] = d[u] + w;
q.push({v, d[v]});
}
}
}
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
void dfs(int u, int fa)
{
up[u][0] = fa;
_for(j, 1, 20) up[u][j] = up[up[u][j - 1]][j - 1];
if(u > n) s[u] = 1e18;
else s[u] = d[u];
for(auto [v, w]: g[u])
{
dfs(v, u);
s[u] = min(s[u], s[v]);
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
e.clear();
_for(i, 1, 2 * n) g[i].clear(), f[i] = i;
while(m--)
{
int u, v, l, a;
scanf("%d%d%d%d", &u, &v, &l, &a);
g[u].push_back({v, l});
g[v].push_back({u, l});
e.push_back({u, v, a});
}
solve();
cnt = n;
sort(e.begin(), e.end());
_for(i, 1, 2 * n) g[i].clear();
for(auto [u, v, w]: e)
{
u = find(u); v = find(v);
if(u != v)
{
val[++cnt] = w;
f[u] = f[v] = cnt;
g[cnt].push_back({u, 0});
g[cnt].push_back({v, 0});
}
}
dfs(cnt, 0);
int q, k, S; ll lastans = 0;
scanf("%d%d%d", &q, &k, &S);
while(q--)
{
int v0, v, p0, p;
scanf("%d%d", &v0, &p0);
v = (v0 + k * lastans - 1) % n + 1;
p = (p0 + k * lastans) % (S + 1);
for(int j = 20; j >= 0; j--)
if(val[up[v][j]] > p)
v = up[v][j];
printf("%lld\n", lastans = s[v]);
}
}
return 0;
}
周五
P7834 [ONTAK2010] Peaks 加强版(krusal重构树+主席树+dfs序)
krusal重构树主要用到两个性质,一个是两点的lca原图生成树中两点路径的最大/最小边权,一个是在重构树上从叶子节点往上跳,给一个x,如果父亲节点权值小于等于x就可以跳,最后跳到一个节点,其对应子树的叶子节点,就是在原图中走边权不超过x的边所能走到的最大联通分量。
这题要需要求这个联通分量的第k大,这就是主席树经典问题了,还需要用dfs序转化到区间上。
对dfs序建立主席树,每次询问一个区间。
这题其实就是询问子树第k大的值+krusal重构树,两题套在了一起
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
int a[N], f[N], val[N], siz[N], n, m, q, cnt;
int up[N][25], L[N], R[N], p[N], idx;
int ls[N << 4], rs[N << 4], s[N << 4], root[N << 4];
struct Edge
{
int u, v, w;
bool operator < (const Edge& rhs) const
{
return w < rhs.w;
}
};
vector<Edge> e;
vector<int> g[N];
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
void dfs(int u, int fa)
{
up[u][0] = fa;
_for(j, 1, 20) up[u][j] = up[up[u][j - 1]][j - 1];
L[u] = ++idx;
p[idx] = u;
if(!g[u].size()) siz[u] = 1;
for(int v: g[u]) dfs(v, u), siz[u] += siz[v];
R[u] = idx;
}
void add(int pre, int& k, int l, int r, int x)
{
k = ++idx;
ls[k] = ls[pre]; rs[k] = rs[pre]; s[k] = s[pre] + 1;
if(l == r) return;
int m = l + r >> 1;
if(x <= m) add(ls[pre], ls[k], l, m, x);
else add(rs[pre], rs[k], m + 1, r, x);
}
int ask(int pre, int k, int l, int r, int num)
{
if(l == r) return l;
int x = s[rs[k]] - s[rs[pre]], m = l + r >> 1;
if(num <= x) return ask(rs[pre], rs[k], m + 1, r, num);
else return ask(ls[pre], ls[k], l, m, num - x);
}
int main()
{
scanf("%d%d%d", &n, &m, &q);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, 2 * n) f[i] = i;
while(m--)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
e.push_back({u, v, w});
}
cnt = n;
sort(e.begin(), e.end());
for(auto [u, v, w]: e)
{
u = find(u); v = find(v);
if(u != v)
{
val[++cnt] = w;
f[u] = f[v] = cnt;
g[cnt].push_back(u);
g[cnt].push_back(v);
}
}
dfs(cnt, 0);
idx = 0;
_for(i, 1, 2 * n - 1)
{
if(a[p[i]]) add(root[i - 1], root[i], 1, 1e9, a[p[i]]);
else root[i] = root[i - 1];
}
int lastans = 0;
while(q--)
{
int u, x, k;
scanf("%d%d%d", &u, &x, &k);
u = (u ^ lastans) % n + 1;
x = x ^ lastans;
k = (k ^ lastans) % n + 1;
for(int j = 20; j >= 0; j--)
if(up[u][j] && val[up[u][j]] <= x)
u = up[u][j];
if(siz[u] < k)
{
puts("-1");
lastans = 0;
}
else printf("%d\n", lastans = ask(root[L[u] - 1], root[R[u]], 1, 1e9, k));
}
return 0;
}
J. Circular Billiard Table(思维)
算出圆形角是2倍角,那么和360求一下最小公倍数即可
对于分数,先把分母乘掉再计算
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b) { return !b ? a : gcd(b, a % b); }
ll lcm(ll a, ll b) { return a * b / gcd(a, b); }
int main()
{
int T; scanf("%d", &T);
while(T--)
{
ll a, b;
scanf("%lld%lld", &a, &b);
ll g = gcd(a, b);
a /= g; b /= g;
ll ans = b;
ans *= lcm(2 * a, 360) / (2 * a);
printf("%lld\n", ans - 1);
}
return 0;
}
D. Period(kmp树+倍增)
问一个点被禁掉后还有多少个board。要遍历所有的board就是从长度n开始一直取next。为了加速,可以倍增,即迅速跳到一个离被禁的点最近的点,然后从这里开始有多少board就是答案。在kmp树上倍增即可。因为对board的限制是双向的,也就是说离边界最近的,如果在左边那就在左边,在右边就转化到左边。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
int Next[N], d[N], up[N][25], n;
char s[N];
vector<int> g[N];
void get_next()
{
Next[0] = -1;
int i = 0, j = -1;
while(i < n)
{
if(j == -1 || s[i] == s[j])
{
i++; j++;
Next[i] = j;
g[Next[i]].push_back(i);
}
else j = Next[j];
}
}
void dfs(int u, int fa)
{
up[u][0] = fa;
_for(j, 1, 20) up[u][j] = up[up[u][j - 1]][j - 1];
d[u] = d[fa] + 1;
for(int v: g[u]) dfs(v, u);
}
int main()
{
scanf("%s", s);
n = strlen(s);
get_next();
dfs(0, 0);
int q; scanf("%d", &q);
while(q--)
{
int x; scanf("%d", &x);
if(n - x + 1 < x) x = n - x + 1;
int u = n;
for(int j = 20; j >= 0; j--)
if(up[u][j] >= x)
u = up[u][j];
u = up[u][0];
printf("%d\n", d[u] - 1);
}
return 0;
}
后面看了别人的做法发现做麻烦了,直接求前缀和即可。即把所有的board的标记下来
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
int Next[N], sum[N], n;
char s[N];
void get_next()
{
Next[0] = -1;
int i = 0, j = -1;
while(i < n)
{
if(j == -1 || s[i] == s[j])
{
i++; j++;
Next[i] = j;
}
else j = Next[j];
}
}
int main()
{
scanf("%s", s);
n = strlen(s);
get_next();
int x = n;
while(x)
{
sum[x]++;
x = Next[x];
}
_for(i, 1, n) sum[i] += sum[i - 1];
int q; scanf("%d", &q);
while(q--)
{
int x; scanf("%d", &x);
if(n - x + 1 < x) x = n - x + 1;
printf("%d\n", sum[x - 1]);
}
return 0;
}
G. Shinyruo and KFC(暴力+复杂度计算)
这题关键是利用ai的求和是小于等于1e5,这意味着两两不同的ai只有根号1e5,相同的数可以一起算,用快速幂。
然后注意计算组合数时,阶乘的逆元要预处理,不要现场算会T
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int N = 1e5 + 10;
int n, m;
map<int, int> mp;
ll f[N], invf[N];
ll binpow(ll a, ll b)
{
ll res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
ll inv(ll x) { return binpow(x, mod - 2); }
ll C(ll n, ll m)
{
if(m > n) return 0;
return f[n] * invf[m] % mod * invf[n - m] % mod;
}
int main()
{
f[0] = 1;
_for(i, 1, 1e5) f[i] = f[i - 1] * i % mod;
invf[(int)1e5] = inv(f[(int)1e5]);
for(int i = 1e5 - 1; i >= 0; i--) invf[i] = invf[i + 1] * (i + 1) % mod;
scanf("%d%d", &n, &m);
_for(i, 1, n)
{
int x; scanf("%d", &x);
mp[x]++;
}
vector<pair<int, int>> ve;
for(auto x: mp) ve.push_back(x);
_for(i, 1, m)
{
ll res = 1;
for(auto [x, cnt] : ve)
res = res * binpow(C(i, x), cnt) % mod;
printf("%lld\n", res);
}
return 0;
}
周六
P2762 太空飞行计划问题(最大权闭合子图)
给一个有向图,每个点有点权。一个点选了它的后继必须选,则称为闭合的,要最大化点权,那就是最大权闭合子图,是一个经典问题
解决方法是网络流,汇点向所有正权点连容量为权值的边,所有负权点向汇点连容量为权值的边,原图的边保留,容量为正无穷。
则正点权和-最小割就是答案
怎么理解呢,我们首先把全部正权点选了,现在考虑怎么删去一些节点
减去的值=正权点不选的值+负权点的值,也恰好是两侧边的容量。
如果是s和t不联通,表示所有选择的正权点,它的后继的边都被割断了,也就是价值被剪掉了,符合条件。
也就是说s和t不联通是符合题目条件的,那么就要删最少的边使得s和t不联通,那就是说花最小的代价满足题目的限制关系。也就是最小割。
怎么输出方案呢,考虑最后一次找增广路径,在残量网络中,还存在的边是还没有被割的边,那么在分层时d数组是有值的,那么只要考虑d数组即可
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 110;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N];
int d[N], cur[N], a[N][N];
int sum, n, m, s, t;
void add(int from, int to, int flow)
{
edge.push_back(Edge{from, to, flow});
g[from].push_back(edge.size() - 1);
edge.push_back(Edge{to, from, 0});
g[to].push_back(edge.size() - 1);
}
bool bfs()
{
memset(d, 0, sizeof(d));
queue<int> q;
q.push(s);
d[s] = 1;
while(!q.empty())
{
int u = q.front(); q.pop();
for(auto x: g[u])
{
Edge e = edge[x];
if(!d[e.to] && e.flow)
{
d[e.to] = d[u] + 1;
q.push(e.to);
}
}
}
return d[t];
}
ll dfs(int u, ll in)
{
if(u == t) return in;
ll out = 0;
for(int& i = cur[u]; i < g[u].size(); i++)
{
Edge& e = edge[g[u][i]];
if(d[u] + 1 == d[e.to] && e.flow)
{
ll f = dfs(e.to, min((ll)e.flow, in));
e.flow -= f;
edge[g[u][i] ^ 1].flow += f;
out += f; in -= f;
if(in == 0) break;
}
}
return out;
}
void build()
{
scanf("%d%d", &m, &n);
s = 0; t = m + n + 1;
_for(i, 1, m)
{
int x; scanf("%d", &x);
sum += x;
add(s, i, x);
char tools[10000];
memset(tools, 0, sizeof tools);
cin.getline(tools, 10000);
int ulen = 0, tool;
while (sscanf(tools + ulen, "%d", &tool)==1)
{
add(i, tool + m, 1e9);
a[i][tool] = 1;
if(tool == 0) ulen++;
else while(tool) tool /= 10, ulen++;
ulen++;
}
}
_for(i, 1, n)
{
int x; scanf("%d", &x);
add(i + m, t, x);
}
}
int main()
{
build();
int ans = 0;
while(bfs())
{
memset(cur, 0, sizeof(cur));
ans += dfs(s, 1e18);
}
_for(i, 1, m) if(d[i]) printf("%d ", i); puts("");
_for(i, 1, n) if(d[i + m]) printf("%d ", i); puts("");
printf("%d\n", sum - ans);
return 0;
}
Luxury cruise ship(思维)
365肯定是越多越好,所以考虑枚举365的个数,剩下7和31用dp
打个表可以发现,对于31和7,只要超过200就一定能表示
所以365的个数要不是取到最大,要不是少一个
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e3 + 10;
int f[N];
int main()
{
memset(f, 0x3f, sizeof f);
f[0] = 0;
int w[] = {7, 31};
_for(i, 1, 1e3)
rep(j, 0, 2)
if(i - w[j] >= 0)
f[i] = min(f[i], f[i - w[j]] + 1);
int T; scanf("%d", &T);
while(T--)
{
ll x; scanf("%lld", &x);
if(x <= 365)
{
printf("%d\n", f[x] > 1e9 ? -1 : f[x]);
continue;
}
if(f[x % 365] <= 1e9) printf("%lld\n", x / 365 + f[x % 365]);
else printf("%lld\n", x / 365 - 1 + f[(x % 365) + 365]);
}
return 0;
}
Static Query on Tree(树剖+线段树染色)
树剖很久没写了,vp没回忆起来。
看作dfs序的扩展,除了dfs序维护子树区间,树剖可以dfs序维护x到y的路径。同时涉及到子树和路径,就可以用树剖。
C操作对应子树,AB操作对应点到根的路径。那么接下来就是一个染色问题,问有多少点同时染了三种颜色,那么就是线段树染色了。
比较坑的是每次询问过后要清空线段树,为了保证线段树,就把前面全部的修改操作重复一遍,只是改成清空操作,但是注意修改操作的时候会push_down,那么清空的操作也要push_down,即把左右儿子清0,我写完后170多行,但其实就错了这个点,卡了很久,忘了清空的时候也push_down
#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
int ta[N << 2], tab[N << 2], tabc[N << 2];
int lazya[N << 2], lazyb[N << 2], lazyc[N << 2];
int d[N], fa[N], top[N], id[N], siz[N], son[N], cnt;
vector<int> g[N];
vector<pair<int, int>> A, B, C;
int n, q;
void dfs1(int u, int father)
{
d[u] = d[father] + 1;
fa[u] = father;
siz[u] = 1;
for(int v: g[u])
{
dfs1(v, u);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int u, int fa, int t)
{
top[u] = t;
id[u] = ++cnt;
if(siz[u] == 1) return;
dfs2(son[u], u, t);
for(int v: g[u])
{
if(v == fa || v == son[u]) continue;
dfs2(v, u, v);
}
}
void add(int u, int v, int op)
{
while(top[u] != top[v])
{
if(d[top[u]] < d[top[v]]) swap(u, v);
if(!op) A.push_back({id[top[u]], id[u]});
else B.push_back({id[top[u]], id[u]});
u = fa[top[u]];
}
if(d[u] < d[v]) swap(u, v);
if(!op) A.push_back({id[v], id[u]});
else B.push_back({id[v], id[u]});
}
void updateA(int k, int l, int r)
{
ta[k] = r - l + 1;
lazya[k] = 1;
}
void updateB(int k, int l, int r)
{
tab[k] = ta[k];
lazyb[k] = 1;
}
void updateC(int k, int l, int r)
{
tabc[k] = tab[k];
lazyc[k] = 1;
}
void up(int k)
{
ta[k] = ta[l(k)] + ta[r(k)];
tab[k] = tab[l(k)] + tab[r(k)];
tabc[k] = tabc[l(k)] + tabc[r(k)];
}
void down(int k, int l, int r)
{
int m = l + r >> 1;
if(lazya[k]) updateA(l(k), l, m), updateA(r(k), m + 1, r), lazya[k] = 0;
if(lazyb[k]) updateB(l(k), l, m), updateB(r(k), m + 1, r), lazyb[k] = 0;
if(lazyc[k]) updateC(l(k), l, m), updateC(r(k), m + 1, r), lazyc[k] = 0;
}
void change(int k, int l, int r, int L, int R, int op)
{
if(L <= l && r <= R)
{
if(!op) updateA(k, l, r);
else if(op == 1) updateB(k, l, r);
else updateC(k, l, r);
return;
}
down(k, l, r);
int m = l + r >> 1;
if(L <= m) change(l(k), l, m, L, R, op);
if(R > m) change(r(k), m + 1, r, L, R, op);
up(k);
}
void cla(int k)
{
ta[k] = tab[k] = tabc[k] = lazya[k] = lazyb[k] = lazyc[k] = 0;
}
void Clear(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R)
{
cla(k);
return;
}
cla(l(k)); cla(r(k));
int m = l + r >> 1;
if(L <= m) Clear(l(k), l, m, L, R);
if(R > m) Clear(r(k), m + 1, r, L, R);
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &q);
_for(i, 1, n) g[i].clear(), son[i] = d[i] = top[i] = fa[i] = id[i] = siz[i] = 0;
cnt = 0;
_for(i, 2, n)
{
int x; scanf("%d", &x);
g[x].push_back(i);
}
dfs1(1, 0);
dfs2(1, 0, 1);
while(q--)
{
A.clear(); B.clear(); C.clear();
int na, nb, nc, x;
scanf("%d%d%d", &na, &nb, &nc);
while(na--)
{
scanf("%d", &x);
add(1, x, 0);
}
while(nb--)
{
scanf("%d", &x);
add(1, x, 1);
}
while(nc--)
{
scanf("%d", &x);
C.push_back({id[x], id[x] + siz[x] - 1});
}
for(auto [l, r]: A) change(1, 1, n, l, r, 0);
for(auto [l, r]: B) change(1, 1, n, l, r, 1);
for(auto [l, r]: C) change(1, 1, n, l, r, 2);
printf("%d\n", tabc[1]);
for(auto [l, r]: A) Clear(1, 1, n, l, r);
for(auto [l, r]: B) Clear(1, 1, n, l, r);
for(auto [l, r]: C) Clear(1, 1, n, l, r);
}
}
return 0;
}