补题清理计划 #1
P5490 【模板】扫描线 & 矩形面积并
将横坐标和纵坐标离散化,枚举横坐标,纵坐标建线段树。
线段树的第 \(i\) 个位置记的是 \(y_i-y_{i-1}\),所以你最开始处理那个东西的纵坐标要加一,不过这都是细节了。
把起始点的离散化坐标挂打加标记的 vector 里,终止点的离散化坐标挂打减标记的 vector 里。
然后线段树维护的是区间 \(+1\) 区间 \(-1\) 区间非 \(0\) 个数。这里线段树的维护比较有启发意义,维护区间最小值和最小值的出现次数。若最小值为 \(0\) 则要减去 \(0\) 段的答案,否则就不用减去。
注意下传的时候并不用下传最小值出现次数,只用下传最小值,因为下传节点是完全包含被下传节点的,只有这一点需要注意。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 200005;
LL n, X1[MAXN], Y1[MAXN], X2[MAXN], Y2[MAXN], Tmp1[MAXN], tot1 = 0, Tmp2[MAXN], tot2 = 0, g1[MAXN], g2[MAXN], ans = 0;
vector<LL> G1[MAXN], G2[MAXN];
struct Segm
{
LL L, R, minn, tag, cntmin, anss;
} Sgt[MAXN << 2];
void PushUp(LL o)
{
if(Sgt[o << 1].minn < Sgt[o << 1 | 1].minn) { Sgt[o].minn = Sgt[o << 1].minn; Sgt[o].cntmin = Sgt[o << 1].cntmin; }
else if(Sgt[o << 1].minn > Sgt[o << 1 | 1].minn) { Sgt[o].minn = Sgt[o << 1 | 1].minn; Sgt[o].cntmin = Sgt[o << 1 | 1].cntmin; }
else { Sgt[o].minn = Sgt[o << 1].minn; Sgt[o].cntmin = Sgt[o << 1].cntmin + Sgt[o << 1 | 1].cntmin; }
if(Sgt[o].minn == 0) Sgt[o].anss = (Tmp2[Sgt[o].R] - Tmp2[Sgt[o].L - 1]) - Sgt[o].cntmin;
else Sgt[o].anss = (Tmp2[Sgt[o].R] - Tmp2[Sgt[o].L - 1]);
return;
}
void PushDown(LL o)
{
if(!Sgt[o].tag) return;
Sgt[o << 1].tag += Sgt[o].tag; Sgt[o << 1 | 1].tag += Sgt[o].tag;
Sgt[o << 1].minn += Sgt[o].tag; Sgt[o << 1 | 1].minn += Sgt[o].tag;
if(Sgt[o << 1].minn == 0) Sgt[o << 1].anss = (Tmp2[Sgt[o << 1].R] - Tmp2[Sgt[o << 1].L - 1]) - Sgt[o << 1].cntmin;
else Sgt[o << 1].anss = (Tmp2[Sgt[o << 1].R] - Tmp2[Sgt[o << 1].L - 1]);
if(Sgt[o << 1 | 1].minn == 0) Sgt[o << 1 | 1].anss = (Tmp2[Sgt[o << 1 | 1].R] - Tmp2[Sgt[o << 1 | 1].L - 1]) - Sgt[o << 1 | 1].cntmin;
else Sgt[o << 1].anss = (Tmp2[Sgt[o << 1 | 1].R] - Tmp2[Sgt[o << 1 | 1].L - 1]);
Sgt[o].tag = 0;
return;
}
void BuildTree(LL o, LL L, LL R)
{
Sgt[o].L = L; Sgt[o].R = R;
if(L == R)
{
Sgt[o].minn = 0; Sgt[o].tag = 0; Sgt[o].cntmin = g2[L]; Sgt[o].anss = 0;
return;
}
LL mid = (L + R) >> 1;
BuildTree(o << 1, L, mid); BuildTree(o << 1 | 1, mid + 1, R);
PushUp(o);
return;
}
void Update1(LL o, LL Ql, LL Qr)
{
if(Ql <= Sgt[o].L && Sgt[o].R <= Qr)
{
Sgt[o].tag ++; Sgt[o].minn ++;
if(Sgt[o].minn == 0) Sgt[o].anss = (Tmp2[Sgt[o].R] - Tmp2[Sgt[o].L - 1]) - Sgt[o].cntmin;
else Sgt[o].anss = (Tmp2[Sgt[o].R] - Tmp2[Sgt[o].L - 1]);
return;
}
PushDown(o);
LL mid = (Sgt[o].L + Sgt[o].R) >> 1;
if(Ql <= mid) Update1(o << 1, Ql, Qr);
if(mid < Qr) Update1(o << 1 | 1, Ql, Qr);
PushUp(o);
return;
}
void Update2(LL o, LL Ql, LL Qr)
{
if(Ql <= Sgt[o].L && Sgt[o].R <= Qr)
{
Sgt[o].tag --; Sgt[o].minn --;
if(Sgt[o].minn == 0) Sgt[o].anss = (Tmp2[Sgt[o].R] - Tmp2[Sgt[o].L - 1]) - Sgt[o].cntmin;
else Sgt[o].anss = (Tmp2[Sgt[o].R] - Tmp2[Sgt[o].L - 1]);
return;
}
PushDown(o);
LL mid = (Sgt[o].L + Sgt[o].R) >> 1;
if(Ql <= mid) Update2(o << 1, Ql, Qr);
if(mid < Qr) Update2(o << 1 | 1, Ql, Qr);
PushUp(o);
return;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n;
for(LL i = 1; i <= n; i ++)
{
cin >> X1[i] >> Y1[i] >> X2[i] >> Y2[i];
Tmp1[++ tot1] = X1[i]; Tmp1[++ tot1] = X2[i];
Tmp2[++ tot2] = Y1[i]; Tmp2[++ tot2] = Y2[i];
}
sort(Tmp1 + 1, Tmp1 + tot1 + 1); tot1 = unique(Tmp1 + 1, Tmp1 + tot1 + 1) - Tmp1 - 1;
sort(Tmp2 + 1, Tmp2 + tot2 + 1); tot2 = unique(Tmp2 + 1, Tmp2 + tot2 + 1) - Tmp2 - 1;
for(LL i = 1; i <= tot1; i ++) g1[i] = Tmp1[i] - Tmp1[i - 1];
for(LL i = 1; i <= tot2; i ++) g2[i] = Tmp2[i] - Tmp2[i - 1];
for(LL i = 1; i <= n; i ++)
{
X1[i] = lower_bound(Tmp1 + 1, Tmp1 + tot1 + 1, X1[i]) - Tmp1; X1[i] ++;
X2[i] = lower_bound(Tmp1 + 1, Tmp1 + tot1 + 1, X2[i]) - Tmp1; X2[i] ++;
Y1[i] = lower_bound(Tmp2 + 1, Tmp2 + tot2 + 1, Y1[i]) - Tmp2; Y1[i] ++;
Y2[i] = lower_bound(Tmp2 + 1, Tmp2 + tot2 + 1, Y2[i]) - Tmp2;
G1[X1[i]].push_back(i); G2[X2[i]].push_back(i);
}
BuildTree(1, 1, tot2);
for(LL i = 2; i <= tot1; i ++)
{
for(LL j = 0; j < G2[i].size(); j ++) // Delete
Update2(1, Y1[G2[i][j]], Y2[G2[i][j]]);
for(LL j = 0; j < G1[i].size(); j ++) // Add
Update1(1, Y1[G1[i][j]], Y2[G1[i][j]]);
ans += Sgt[1].anss * g1[i];
}
cout << ans << '\n';
return 0;
}
P4137 Rmq Problem / mex
好题。首先主席树上二分的做法是显然的。直接建主席树找最左边的 \(0\) 即可。
然后离线扫描线线段树二分的做法是有启发性的。注意到我们向右扫描线的过程中,若出现 \(i<j\) 且 \(a_i=a_j\),那么 \(i\) 一定是不优的,这时我们在值域线段树上把 \(a_i\) 位置的值改为 \(j\)。于是在动态扫描线的过程中,我们的询问变成了:找到一个最大的 \(p\),满足 \(\min_{i=1}^{p}\geq l\),那么此时 \(p+1\) 就是答案。
这个东西显然可以线段树维护最小值后线段树二分实现,不做赘述。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
int n, m, A[MAXN], ans[MAXN];
struct Qry
{
int L, id;
}; vector<Qry> v[MAXN];
struct Node
{
int L, R, minn;
} Sgt[MAXN << 2];
void PushUp(int o)
{
Sgt[o].minn = min(Sgt[o << 1].minn, Sgt[o << 1 | 1].minn);
return;
}
void BuildTree(int o, int L, int R)
{
Sgt[o].L = L; Sgt[o].R = R;
if(L == R) return;
int mid = (L + R) >> 1;
BuildTree(o << 1, L, mid); BuildTree(o << 1 | 1, mid + 1, R);
PushUp(o);
return;
}
void Update(int o, int Pos, int x)
{
if(Sgt[o].L == Sgt[o].R)
{
Sgt[o].minn = x;
return;
}
int mid = (Sgt[o].L + Sgt[o].R) >> 1;
if(Pos <= mid) Update(o << 1, Pos, x);
else Update(o << 1 | 1, Pos, x);
PushUp(o);
return;
}
int Query(int o, int Pos)
{
if(Sgt[o].L == Sgt[o].R) return Sgt[o].L;
if(Sgt[o << 1].minn >= Pos) return Query(o << 1 | 1, Pos);
else return Query(o << 1, Pos);
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> A[i];
for(int i = 1; i <= m; i ++)
{
int L, R; cin >> L >> R;
v[R].push_back(Qry{L, i});
}
BuildTree(1, 0, 200000);
for(int R = 1; R <= n; R ++)
{
Update(1, A[R], R);
for(int i = 0; i < v[R].size(); i ++)
{
int L = v[R][i].L;
ans[v[R][i].id] = Query(1, L);
}
}
for(int i = 1; i <= m; i ++) cout << ans[i] << '\n';
return 0;
}
blog 说不知道哪来的题 1
给定一棵 \(n\) 个点的树,然后有 \(q\) 次查询,每次查询区间,表示询问当这个树仅剩点和边的编号在 \([l,r]\) 之间时连通块的个数。
注意到是树,所以答案为 \(cntv - cnte\)。我们很容易知道 \(cntv = r-l+1\),这时候只需要对 \(cnte\) 计数。我们转化一下变为对于每一条边 \(\min(id_i,u_i,v_i)\geq l\) 且 \(\max(id_i,u_i,v_i)\leq r\)。
于是我们建立二维坐标系,把 \(\max\) 和 \(\min\) 设为 \(x\) 轴和 \(y\) 轴,离线二维数点即可。
P10814 【模板】离线二维数点
方法 1:
对于序列维扫描线,对于值域维施 BIT。每个询问挂两个点,容斥做,因为数据范围不允许我们使用主席树。所以也不允许我们使用常规的挂右端点,看左端点的扫描线。
方法 2:
对于值域维扫描线。这样只需要询问序列编号在 \([l,r]\) 中间的即可,树状数组查询。
想说的是,由于这题值域维的限制较小,我们就可以对这维扫描线就不用容斥,而且 BIT 也好写。所以有时候可以考虑换维扫描线。
UOJ 637. 【美团杯2021】A. 数据结构
好题。均摊矩形扫描线。
考虑对于一个数 \(x\),什么时候他会不被统计在贡献里。非常简单,就是 \(x-1\) 全部不加一,而 \(x\) 全部加一。
接下来我们在二维平面上刻画这个限制。设区间左端点为 \(x\) 轴,区间右端点为 \(y\) 轴。
对于条件 \(1\),我们考虑相邻两个数值为 \(x-1\) 的点。设两个位置为 \(i\) 和 \(j\),那么左端点取 \([i+1,j-1]\) 且右端点取 \([i+1,j-1]\) 即可。就是一个矩形,实际上是一个三角但是不重要。值得一题的是,这些矩形没有交点,这也是我们算法正确性的保证之一。特别的,我们还要考虑 \(0\) 和 \(n+1\) 这两个点,如果你真正理解我在干啥了应该不难想到要考虑这两个。(我们算的实质上是极大不包含 \(x-1\) 的区间)
对于条件 \(2\),我们考虑最左边和最右边的两个为 \(x\) 的点,记为 \(i\) 和 \(j\),那么我们要求矩形 \(x\leq i\) 且 \(y\geq j\)。(如果没有 \(x\) 这个数只需要执行条件 \(1\) 即可,但是也必须要执行条件 \(1\)!)
注意一下还要考虑权值为 \(0\) 和 \(n+1\) 的情况。
这样我们完成了对矩形的限制。注意一下我们显然最多有 \(O(\sum cnt_i)=O(n)\) 个矩形。而且如果被条件 \(2\) 限制,就会切得当前最多只剩下两个矩形。不过这不太重要。
然后我们把询问区间抽象成为一个点,这时候,我们要求的就是一个点被多少个矩形包含了。扫描线中,我们可以用现合法矩形减去不合法矩形,使用两个 BIT 维护底和顶即可。
于是原本是 4-side 的限制,我们通过扫描线变成了 2-side,又通过容斥将其化为 1-side 问题,于是我们就可以 BIT 处理了。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2000005;
int n, m, A[MAXN], X1[MAXN], X2[MAXN], Y1[MAXN], Y2[MAXN], cntt = 0, tot = 0, BIT1[MAXN], BIT2[MAXN], ans[MAXN];
struct Qry
{
int R, id;
}; vector<Qry> Q[MAXN];
vector<int> v[MAXN];
vector<int> G1[MAXN], G2[MAXN];
inline int lowbit(int x) { return x & (-x); }
void Update1(int Pos, int x)
{
while(Pos <= n + 1) { BIT1[Pos] += x; Pos += lowbit(Pos); }
return;
}
int Query1(int Pos)
{
int res = 0;
while(Pos > 0) { res += BIT1[Pos]; Pos -= lowbit(Pos); }
return res;
}
void Update2(int Pos, int x)
{
while(Pos <= n + 1) { BIT2[Pos] += x; Pos += lowbit(Pos); }
return;
}
int Query2(int Pos)
{
int res = 0;
while(Pos > 0) { res += BIT2[Pos]; Pos -= lowbit(Pos); }
return res;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 0; i <= n + 1; i ++) v[i].push_back(0);
for(int i = 1; i <= n; i ++)
{
cin >> A[i];
v[A[i]].push_back(i);
}
for(int i = 0; i <= n + 1; i ++) v[i].push_back(n + 1);
for(int i = 1; i <= n + 1; i ++) // 矩形切割
{
if(v[i].size() == 2)
{
for(int j = 1; j < v[i - 1].size(); j ++)
{
int nl = v[i - 1][j - 1], nr = v[i - 1][j]; nl ++; nr --; if(nl > nr) continue;
tot ++; X1[tot] = nl; X2[tot] = nr; Y1[tot] = nl; Y2[tot] = nr;
G1[X1[tot]].push_back(tot); G2[X2[tot] + 1].push_back(tot);
}
}
else
{
int gl = v[i][1], gr = v[i][(int)(v[i].size() - 2)];
for(int j = 1; j < v[i - 1].size(); j ++)
{
int nl = v[i - 1][j - 1], nr = v[i - 1][j]; nl ++; nr --; if(nl > nr) continue;
if(nr >= gr && nl <= gl)
{
tot ++; X1[tot] = nl; X2[tot] = gl; Y1[tot] = gr; Y2[tot] = nr;
G1[X1[tot]].push_back(tot); G2[X2[tot] + 1].push_back(tot);
}
}
}
}
for(int i = 1; i <= m; i ++)
{
int L, R; cin >> L >> R;
Q[L].push_back(Qry{R, i});
}
int nowcnt = 0;
for(int i = 1; i <= n + 1; i ++)
{
for(int j = 0; j < G2[i].size(); j ++) { Update1(Y1[G2[i][j]], -1); Update2(Y2[G2[i][j]], -1); nowcnt --; }
for(int j = 0; j < G1[i].size(); j ++) { Update1(Y1[G1[i][j]], 1); Update2(Y2[G1[i][j]], 1); nowcnt ++; }
for(int j = 0; j < Q[i].size(); j ++) ans[Q[i][j].id] = (nowcnt - (Query1(n + 1) - Query1(Q[i][j].R)) - (Query2(Q[i][j].R - 1)));
}
for(int i = 1; i <= m; i ++) ans[i] = n + 1 - ans[i];
for(int i = 1; i <= m; i ++) cout << ans[i] << '\n';
return 0;
}
百度之星 2021 初赛第三场 1008
我们只考虑是中间一个子区间的情况,前缀应当也能归到子区间里吧(?)。一样的,运用 UOJ 637 的小 Trick。将 \(l\) 设为 \(x\) 轴,\(r\) 设为 \(y\) 轴。
考虑减少的充要条件,首先是两个相邻都为 \(x\) 的位置,设为 \(i\) 和 \(j\),区间 \([i+1,j-1]\) 是合法的,其次,询问区间必须要保证 \(l\in [1,i],r\in [j,n]\),于是我们还是把区间抽象成点,前面的限制是一个 2-side 矩形。是包含它的矩形中 \(r-l-1\) 最大的一个。但是你还是注意到这是 2-side,于是你可以只保留矩形的左上角代表这个矩形,那么你这个点的左上角都可以包含你,于是就是左上角的点的权值取 max,这个是经典问题,可以直接从左往右扫描线 BIT 维护 1-side 限制。
然后我们就做完了。
UVA1608 不无聊的序列 Non-boring sequences
这个就略显唐诗了。拆贡献,对于每个点,取极大区间,左端点取到上一个与自己相同的数的位置加一,右端点取到下一个与自己相同的数减一。看矩形面积并是否等于平面大小即可。
求能不能并出下三角,稍微魔改一下即可,不写了就这样了算过了。
blog 说不知道哪来的题 2
有一个长度为 \(n\) 的序列,有 \(m\) 次询问,每次询问一个区间,问区间上出现偶数次数的数的异或和为多少。
好题,首先我们会区间异或和(其等价于区间出现次数为奇数的异或和)。我们现在只需要求区间出现的数的异或和即可。类似 HH 的项链,扫描线的过程中,后面的一定比前面的优,把前面的贡献改 \(0\) 即可,老典题了。
主要是正难则反的思想。
某不知名地方的题目 1
省流:想到了所有结论,但 \(O(n^2)\) 调挂了。
博弈论题。首先结论都是好猜好证的。
设两点之间的距离为他们之间点的个数。则若 \(dist\equiv k \bmod 2\),那么答案就是 \(k\bmod 2\)。具体的画图自己推。
另两个情况就不太妙了,首先是 \(dist \bmod 2 = 0\) 且 \(k\bmod 2=1\) 这样一般情况下是先手胜 \(2\),我们把先手设为优势方。那么后手如果硬抗就是负 \(2\),要么就选择逃跑,逃跑成功的充要条件是两路径不相交,且两路径是两条链,这样后手负 \(1\),因为有一个 \(k\) 是奇数的先手加持。
而且我们发现,后手走的链一定是最长链,不然不优,这是显然的。我们的目的就是让两链不交的情况下找最长链。
然后是 \(dist \bmod 2 = 1\) 且 \(k\bmod 2=0\),这样是先手劣 \(1\)。同样先手可以选择用同样的策略逃跑来抹掉劣势。
现在你最麻烦的就是找到最长链。首先 \(O(n^2)\) 的写法就是由于优势方路径定长,且路径一定,我们可以找到最靠外的被 ban 点,以 \(x\) 为根做树形 dp 求向下最长链,遇到 ban 就不能经过。
然后比较难写的是 \(O(n\log^2 n)\) 做法。我们由熟知结论:距离一个点最远距离的点一定是树上一个直径的端点。那么由于我们 ban 的 dfn 区间是定的,那么有效区间就是子树 / 子树补,即一段 / 两段 dfn 区间。
那么我们再由熟知结论,两个连通块由一条边合并后,其直径为两连通块共四端点共六种拼法的最大值分别试一遍,使用线段树维护直径,四选二 PushUp,PushUp 可以传 pair 类型比较好写因为下面需要很多 PushUp。
反正写着写着就 5k 了,我也不知道为啥()
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 200005;
const LL MOD = 998244353;
LL n, m, ancc[MAXN][22], depth[MAXN], lg[MAXN], x, y, k, dist, ans = 1, bann = 0, inn[MAXN], outt[MAXN], id[MAXN], dfsTime = 0;
vector<LL> G[MAXN];
void DFS(LL u, LL fa)
{
ancc[u][0] = fa; depth[u] = depth[fa] + 1; inn[u] = (++ dfsTime); id[dfsTime] = u;
for(LL i = 1; i <= 21; i ++) ancc[u][i] = ancc[ancc[u][i - 1]][i - 1];
for(LL i = 0; i < G[u].size(); i ++)
{
LL v = G[u][i]; if(v == fa) continue;
DFS(v, u);
}
outt[u] = dfsTime;
return;
}
LL LCA(LL u, LL v)
{
if(depth[u] < depth[v]) swap(u, v);
while(depth[u] > depth[v]) u = ancc[u][lg[depth[u] - depth[v]]];
if(u == v) return u;
for(LL i = 21; i >= 0; i --)
{
if(ancc[u][i] == ancc[v][i]) continue;
u = ancc[u][i]; v = ancc[v][i];
}
return ancc[u][0];
}
LL Dist(LL u, LL v)
{
if(!u || !v) return 0;
return depth[u] + depth[v] - 2 * depth[LCA(u, v)];
}
LL Jump(LL u, LL jum)
{
for(LL i = 21; i >= 0; i --)
{
if(jum >= (1ll << i))
{
jum -= (1ll << i);
u = ancc[u][i];
}
}
return u;
}
struct Segm
{
LL L, R, xx, yy;
} Sgt[MAXN << 2];
pair<LL, LL> PushUp(pair<LL, LL> bs, pair<LL, LL> cs)
{
LL xx1 = bs.first, xx2 = cs.first, xx3 = bs.second, xx4 = cs.second, nowans = 0; pair<LL, LL> as = make_pair(0, 0);
LL dist1 = Dist(xx1, xx2), dist2 = Dist(xx1, xx3), dist3 = Dist(xx1, xx4);
LL dist4 = Dist(xx2, xx3), dist5 = Dist(xx2, xx4), dist6 = Dist(xx3, xx4);
if(xx1 && xx2 && dist1 > nowans) { nowans = dist1; as.first = xx1; as.second = xx2; }
if(xx1 && xx3 && dist2 > nowans) { nowans = dist2; as.first = xx1; as.second = xx3; }
if(xx1 && xx4 && dist3 > nowans) { nowans = dist3; as.first = xx1; as.second = xx4; }
if(xx2 && xx3 && dist4 > nowans) { nowans = dist4; as.first = xx2; as.second = xx3; }
if(xx2 && xx4 && dist5 > nowans) { nowans = dist5; as.first = xx2; as.second = xx4; }
if(xx3 && xx4 && dist6 > nowans) { nowans = dist6; as.first = xx3; as.second = xx4; }
return as;
}
void BuildTree(LL o, LL L, LL R)
{
Sgt[o].L = L; Sgt[o].R = R;
if(L == R)
{
Sgt[o].xx = id[L]; Sgt[o].yy = id[L];
return;
}
LL mid = (L + R) >> 1;
BuildTree(o << 1, L, mid); BuildTree(o << 1 | 1, mid + 1, R);
pair<LL, LL> Tmp = PushUp(make_pair(Sgt[o << 1].xx, Sgt[o << 1].yy), make_pair(Sgt[o << 1 | 1].xx, Sgt[o << 1 | 1].yy));
Sgt[o].xx = Tmp.first; Sgt[o].yy = Tmp.second;
return;
}
pair<LL, LL> Query(LL o, LL Ql, LL Qr)
{
if(Ql > Qr) return make_pair(0, 0);
if(Ql <= Sgt[o].L && Sgt[o].R <= Qr) return make_pair(Sgt[o].xx, Sgt[o].yy);
LL mid = (Sgt[o].L + Sgt[o].R) >> 1;
if(Ql <= mid && mid < Qr) return PushUp(Query(o << 1, Ql, Qr), Query(o << 1 | 1, Ql, Qr));
else if(Ql <= mid) return Query(o << 1, Ql, Qr);
else if(mid < Qr) return Query(o << 1 | 1, Ql, Qr);
}
int main()
{
freopen("color.in", "r", stdin); freopen("color.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m; for(LL i = 2; i <= n; i ++) lg[i] = lg[i >> 1] + 1;
for(LL i = 1; i < n; i ++)
{
LL u, v; cin >> u >> v;
G[u].push_back(v); G[v].push_back(u);
}
DFS(1, 0); BuildTree(1, 1, n);
for(LL i = 1; i <= m; i ++)
{
cin >> x >> y >> k; dist = Dist(x, y) - 1;
if(dist >= k)
{
ans = (ans * ((k % 2) + i * n % MOD) % MOD) % MOD;
continue;
}
if(dist % 2 == k % 2) ans = (ans * ((k % 2) + i * n % MOD) % MOD) % MOD;
else if(dist % 2 == 1 && k % 2 == 0) // 后手胜 1
{
LL jum = k / 2, jum2 = k / 2 + 1; pair<LL, LL> hh;
if(jum < Dist(y, LCA(x, y)))
{
bann = Jump(y, jum); pair<LL, LL> ff, gg;
if(bann == x) { ans = (ans * (i * n % MOD - 1) % MOD) % MOD; continue; }
ff = Query(1, 1, inn[bann] - 1); gg = Query(1, outt[bann] + 1, n); hh = PushUp(ff, gg);
}
else
{
jum -= Dist(y, LCA(x, y)); bann = Jump(x, Dist(x, LCA(x, y)) - jum);
if(bann == x) { ans = (ans * (i * n % MOD - 1) % MOD) % MOD; continue; }
jum2 -= Dist(y, LCA(x, y)); LL bann2 = Jump(x, Dist(x, LCA(x, y)) - jum2);
hh = Query(1, inn[bann2], outt[bann2]);
}
if(max(Dist(x, hh.first), Dist(x, hh.second)) >= k / 2) ans = (ans * (i * n % MOD) % MOD) % MOD;
else ans = (ans * (i * n % MOD - 1) % MOD) % MOD;
}
else // 先手胜 2
{
LL jum = (k + 1) / 2, jum2 = (k + 1) / 2 + 1; pair<LL, LL> hh;
if(jum < Dist(x, LCA(x, y)))
{
bann = Jump(x, jum); pair<LL, LL> ff, gg;
if(bann == y) { ans = (ans * (i * n % MOD + 2) % MOD) % MOD; continue; }
ff = Query(1, 1, inn[bann] - 1); gg = Query(1, outt[bann] + 1, n); hh = PushUp(ff, gg);
}
else
{
jum -= Dist(x, LCA(x, y)); bann = Jump(y, Dist(y, LCA(x, y)) - jum);
if(bann == y) { ans = (ans * (i * n % MOD + 2) % MOD) % MOD; continue; }
jum2 -= Dist(x, LCA(x, y)); LL bann2 = Jump(y, Dist(y, LCA(x, y)) - jum2);
hh = Query(1, inn[bann2], outt[bann2]);
}
if(max(Dist(y, hh.first), Dist(y, hh.second)) >= k / 2) ans = (ans * (i * n % MOD + 1) % MOD) % MOD;
else ans = (ans * (i * n % MOD + 2) % MOD) % MOD;
}
// cout << ans << '\n';
}
cout << (ans % MOD + MOD) % MOD << '\n';
return 0;
}

浙公网安备 33010602011771号