【noip模拟】Div.2

11.17 字符串

\(C\) 记为 \(1\)\(T\) 记为 \(-1\)。考虑暴力贪心:从前往后扫一遍,若前缀和 \(<0\) 就删掉当前字符,然后在从后往前扫一边。

直接想不太能做,考虑把它数学化。记区间 \([l,r]\) 的前后缀和为 \(pre,suf\),第一次扫删掉的数就是 \(-\min\{pre\}\),第二次扫时后缀和 \(suf'_{j}=suf_{j}+(-\min\{pre\}+\min_{i<j}\{pre_{i}\})\),最终答案为 \(-\min\{pre\}-\min\{suf'\}=-\min_{i<j}\{pre_{i}+suf_{j}\}\),即为区间和减最大字段和,线段树维护即可

11.16 棋盘

发现转移是个矩阵且具有可加性(\([r,i,j]\) 记录从第 \(1\) 行第 \(i\) 列到第 \(r\) 行第 \(j\) 的方案数),直接用线段树维护可以做到 \(O(qn^{2}\log q)\)。注意转移有跳两行的情况,因此还要多维护一个矩阵。

题目的 push,pop 操作本质上是个队列,可以使用双栈模拟队列的 trick。路径可逆,记录前缀积即可。时间复杂度 \(O(qn^{2})\)

code
char readc() { char c; while(!isupper(c=getchar())); return c; }

const int N = 25, Q = 1e5+5, mod = 998244353;
int n,q,l=1,mid,r,a[Q][N][N],b[Q][N][N];
// [l,mid] [mid,r]; a: mid->l,r; b: mid-1->l,mid+1->r;
bool s[Q][N];

void ckadd(int &x,int y) { x = x+y<mod ? x+y : x+y-mod; }

bool input() { char c; while((c=getchar())!='.'&&c!='#'); return c=='.'; }

void init(int f[][N][N],int u) { For(i,1,n) f[u][i][i] = s[u][i]; }
void tran(int f[][N][N],int u,int x,int y) {
	memset(f[u],0,sizeof f[u]);
	For(i,1,n) if( s[u][i] ) For(j,1,n) {
		if( i > 2 ) ckadd(f[u][i][j],f[x][i-2][j]);
		if( i+2 <= n ) ckadd(f[u][i][j],f[x][i+2][j]);
		if( i > 1 ) ckadd(f[u][i][j],f[y][i-1][j]);
		if( i+1 <= n ) ckadd(f[u][i][j],f[y][i+1][j]);
	}
}
void rebuild() {
	mid = r;
	memset(a[r],0,sizeof a[r]), init(a,r);
	rFor(i,r-1,l) tran(a,i,i+1,i+2<=r?i+2:0);
	if( l == r ) return;
	memset(b[r-1],0,sizeof b[r-1]), init(b,r-1);
	rFor(i,r-2,l) tran(b,i,i+1,i+2<r?i+2:0);
}

signed main() {
	freopen("chess.in","r",stdin);
	freopen("chess.out","w",stdout);
	read(n,q);
	while( q-- ) {
		char op = readc();
		if( op == 'A' ) {
			++r; For(i,1,n) s[r][i] = input();
			if( mid < l ) rebuild();
			else {
				if( r == mid+1 ) tran(a,r,r-1,0), init(b,r);
				else tran(a,r,r-1,r-2), tran(b,r,r-1,r-2>mid?r-2:0);
			}
		} else if( op == 'D' ) {
			if( ++l > mid && l <= r ) rebuild();
		} else {
			int x,y; read(x,y);
			if( l > r || !s[l][x] || !s[r][y] ) { write(0); continue; }
			if( l == r ) { write(x==y); continue; }
			LL ans = 0;
			For(i,1,n) (ans += (LL)a[l][x][i] * a[r][y][i]) %=mod; // -> mid ->
			if( l < mid && mid < r ) For(i,1,n) { // -> mid-1 -> mid+1 ->
				if( i > 1 ) (ans += (LL)b[l][x][i] * b[r][y][i-1]) %=mod;
				if( i < n ) (ans += (LL)b[l][x][i] * b[r][y][i+1]) %=mod;
			}
			write(ans);
		}
	}
	return ocl();
}

11.16 军队

难点在于矩形加,预处理每行值 \(<10\) 的元素个数。

考虑扫描线,扫到每一行时统计该行答案。朴素的想法是记录区间最大值和最小值并用此剪枝,但很容易被卡到 \(O(nm)\)
考场上的想法是每次修改打时间戳,如果一个节点的时间戳小于当前修改的时间,那么可以它对应区间的答案不变,不需要再次计算,否则递归下去。看上去很玄学但跑得很快(并且空间是 \(O(n)\) 的),求证明时间复杂度/hack。

正解是记录每个区间最小的 \(10\) 个值及其出现次数,归并合并,时间复杂度 \(O(n\log n)\),空间复杂度 \(O(20n)\)

另:题解说询问可以 \(O(1)\),求做法

11.11 树上路径

如果树上两条路径没有点交,那么一定存在一条边能将这两条路径分到两边。除了这点考场上全想到了。。。

枚举这条边,换根 DP 求出两边的直径,那么小于这两边直径的方案都可行,以 \(L(a,b),L(c,d)\) 建坐标系,就是类似矩形并的东西,求后缀 \(\max\) 之和即可

换根 DP 时需要求出不同子树深度前三大值,记一个比较妙的写法:

void ckmax(int a[],int n,int x) { Rep(i,0,n) if( x > a[i] ) swap(x,a[i]); }

11.11 第 k 大查询

考场上想了一车 \(\log\) 做法,试图魔改单调栈失败。。。
先看对题,要求第 \(k\) 大,不要想当然写成第 \(k\) 小(甚至能过小样例)

难点是 \(O(nk)\) 处理每个数前后第 \(i\in[1,k]\) 个比它大的数的位置。
元素是一个排列,因此不存在相等的情况,考虑从大到小枚举元素,用链表便于删除的特性维护比它大的元素位置(即未枚举到的数),对于每个数直接在链表对应位置上向前后跳 \(k\) 下,然后删除该元素即可。

注意空间是 \(O(n+k)\)

11.8 NOIP 2018

考场上一直想的是二分答案/三分其中一种换多少个,问题在于值域过大和不一定单峰

考虑二分 \(mid\) 表示把 \(\le mid\) 的货币全部兑换,解下方程可以 \(O(1)\) 计算需要多少花费和能兑换的货币数量,需要特判边界。
由于值域过大,二分的上界不能为 \(n\),我的做法是先通过 long double 二分出只换某种货币是最多能换多少个,从而得到更紧的上界。也有其他做法

code
LL a,b,c,d,n;

LL maxx(LD k, LD b) {
    LD l = 0, r = sqrt(2*n/k)+1;
    while( l+1e-9 < r ) {
    	LD x = (l+r)/2;
    	k*x/2+b-k/2<n/x ? l=x : r=x;
    }
    return ceil(k * l + b);
}
pair<LL,LL> calc(LL b,LL k,LL x) {
	LL i = (x-b+k)/k;
	return i>0 ? MP(k*i*i/2+b*i-k*i/2,i) : MP(0ll,0ll);
}
bool check(LL mid) { return calc(a,b,mid).fi+calc(c,d,mid).fi <= n; }

void MAIN() {
	read(a,b,c,d,n);
	LL l = 0, r = min(min(maxx(b,a),maxx(d,c))+1,n);
	while( l < r ) {
		LL mid = l+r+1>>1;
		check(mid) ? l=mid : r=mid-1;
	}
	auto x = calc(a,b,l), y = calc(c,d,l);
	write(x.se+y.se+(n-x.fi-y.fi>=min(b*x.se+a,d*y.se+c)));
} signed main() {
	freopen("money.in","r",stdin);
	freopen("money.out","w",stdout);
	int T; read(T); while( T-- ) MAIN();
	return ocl();
}

11.5 校门外歪脖树上的鸽子

考场正解,结果 \(1\)h 没冲完爆 \(0\) 了。。。

添加两个点 \([0,0],[n+1,n+1]\),然后每次操作可以看成对一条链上每个点的左/右兄弟操作(类似 zkw 线段树),用树剖+两颗线段树分别维护左/右兄弟。时间复杂度 \(O(m\log^{2}n)\)

这题带来两个启示:

  • 学 DS 时注重它的思想、复杂度由什么保证/拖累、如果有特殊性质能不能优化,在考场上能省很多时间/避免写假做法,但切记不要钻牛角尖,实在想不通就跳出来
  • 考场上写 DS 前三思,写的时候心态一定要平和。这题考场上想的就是 rush,结果细节上一堆 bug

11.4 排水系统

设原图设 \(u\) 的流量为 \(f\),出度 \(out\)\((u,v)\) 堵塞的概率为 \(p\)

考虑整体计算。发现如果 \((u,v)\) 堵塞,那么 \(u\) 的流量不变,\(v\) 的减少,\(u\) 的其他出边增加,且转移不变。

可以看作 \(v\) 的流量凭空减少 \(p(\frac{f}{out}+\frac{f}{out(out-1})\)\(u\) 的流量凭空增加 \(p\frac{f}{out-1}\)(即原本流到 \(v\)\(\frac{f}{out}\) 流到了其他 \(out-1\) 条出边)

等价于每个点初始有流量,对所有边进行类似的处理,然后拓扑排序即可。时间复杂度 \(O(n+k)\)

11.4 子集

每连续 \(k\) 个数一定是每个子集一个,因此可以映射到 \([1,k]\)

分类讨论:

  • \(\frac{n}{k}\) 为偶数:将 \((1,k),(2,k-1)\cdots(k,1)\) 配对,均分给每个子集

  • \(\frac{n}{k}\) 为奇数:

    • \(k\) 是偶数:考虑每个子集拿出 \(3\) 个元素转化成 \(\frac{n}{k}\) 是偶数的情况。拿出的 \(3\) 个元素如下
    \(1\) \(2\) \(3\)
    \(1\) \(\frac{k}{2}+1\) \(k\)
    \(\frac{k}{2}+2\) \(1\) \(k-1\)
    \(2\) \(\frac{k}{2}+2\) \(k-2\)
    \(\frac{k}{2}+3\) \(2\) \(k-3\)
    \(\cdots\) \(\cdots\) \(\cdots\)
    \(k\) \(\frac{k}{2}\) \(2\)
    \(\frac{k}{2}+1\) \(k\) \(1\)

    关键是构造出两个元素和为等差数列。对于相邻两个子集,令其中一个元素相等,另一个差 \(1\) 即可。

    • \(k\) 是奇数:每个子集的元素和 \(\frac{n(n+1)}{2k}\) 为小数,无解

10.20 色球

显然的双向链表维护栈,问题是翻转时需要改变链表的前后,当时的想法是像 LCT 一样打标记。T3 推出了矩阵的式子,随便维护就有 70pts,想着 rush 完去想 T3,结果一调就是 2h。。。
调完发现 T 的厉害,仔细一想了 LCT 的复杂度由 Splay 树高 \(\log\) 保证,可以每次暴力下放翻转标记。gg

sol 1

直接上平衡树 \(O(n\log n)\)/块链 \(O(n\sqrt{n})\) 维护序列

sol 2

启发式合并栈 \(O(n\log n)\)

sol 3

双向链表,通过和上一个元素比较可以知道哪个指针是指下一个的,因此不需要打翻转标记,\(O(n)\)

10.19 光线追踪

可见我思维之僵化,一张图秒懂

sol

10.16 o

考场用暴力艹过去了,正解暂鸽

10.12 小说

记修改的袋子为 \(p\)\(res_p\) 为用 \(i\ne p\) 的袋子可以拼出的容量数

显然将 \(v_p\leftarrow\sum_{i\ne p}v_i+1\) 可以使答案变为 \(2res_p+1\),问题变为找最大的 \(res_p\)

暴力想法是枚举 \(p\),用其他袋子背包,bitset 优化一下可以做到 \(O(\frac{n^3v}{\omega})\),能艹过去
正解是先对 \(n\) 个袋子背包(记录方案数),然后对当前枚举的 \(p\) 退背包。问题是方案数可能爆 long long,发现我们只想知道某个容量的方案数是否非 \(0\),可以把方案数对一个或多个大质数取模。时间复杂度 \(O(n^2v)\)

修改后的值 \(v_p'\) 满足原来能拼成的容量 \(x\)\(x+v_p'\) 不能拼成时答案最小,用 \(v_i,-v_i\ (i\ne p)\)\(0/1\) 背包,取不能拼出的最小值即可

10.9 T1 出了个大阴间题

花费中的 \(b_1+b_2\) 与合并顺序无关,由方案数可以直接算,考虑 DP 出合并最大值的方案数和花费中的 \(a\)

\(mx[s]\)\(\max_{i\in s}\{a_i\}\),发现集合 \(s\) 能合并出的最大值最多为 \(mx[s]+1\),因此可以设 \(f[s,i]\) 为集合 \(s\) 合并出最大值 \(mx[s]+i\) 的花费,\(g[s,i]\) 为方案数。
转移时枚举下一个合并的元素,判断 \(a\) 能否 \(+1\) 即可

时间复杂度 \(O(2^{n}n)\)

10.7 朝鲜时蔬

这是一道传统题

简述题意:
\(sum(S)=\sum_{x\in S}x,f_k(S)=\sum_{T\subseteq S,|T|=k}[sum(T)|sum(S)]\)
给定集合 \(S=\{1,2,3,\cdots,n\}\),求 \(\sum_{T\subseteq S,|T|=m}[f_k(T)=\max\{f_k(T),T\subseteq S,|T|=m\}]\)

sol 写的很清楚了,核心是考虑 \(\max\{f_k(T),T\subseteq S,|T|=m\}\) 是多少,合法的 \(T\) 有哪些性质,通过推不等式、整除分块来加速计算。

一些 trick:

  • \(a<b\Rightarrow b\nmid a\Rightarrow b\nmid(a+b)\)
  • 计数 \(a,b>0,a+b\le n,a\ne b\):枚举 \(a+b\) 再拆分

\[\sum_{i=1}^n\frac{i-1-[2|i]}2 \]

对于更多数的情况容斥即可

  • \(a|b\),设 \(xa=a+b\)

\[\begin{cases} & xa=a+b\\ & yb=a+b \end{cases} \Rightarrow \frac1x+\frac1y=1 \]

注意细节
// nn = n%mod
LL Pow(LL x,LL y=mod-2)
	{ LL res=1; for(;y;y>>=1,x=x*x%mod)if(y&1)res=res*x%mod; return res; }
LL s(LL n) { n %=mod; return n * (n+1) %mod * Pow(2) %mod; }
LL s(LL l,LL r) { return (s(r) - s(l-1) +mod) %mod; }
LL s2(LL n) { n %=mod; return n * (n+1) %mod * (2*n+1) %mod * Pow(6) %mod; }
LL s2(LL l,LL r) { return (s2(r) - s2(l-1) +mod) %mod; }
LL cnt(LL n,int x) { return n / x %mod; }
LL cnt(LL l,LL r,int x) { return (cnt(r,x) - cnt(l-1,x) +mod) %mod; }

LL solve11() { return nn; }
LL solve21() {
	LL res = 0;
	for(LL l = 1, r; l <= n; l = r+1) {
		r = min(n,n/(n/l));
		(res += cnt(l,r,1) * (n/l %mod)) %=mod;
	}
	return (res-nn+mod)%mod;
}
LL solve22() { return nn * (nn-1) %mod * Pow(2) %mod; }
LL solve31() { return n / 3 %mod; }
LL solve32() {
	LL res = 0;
	for(LL l = 1, r; l <= n; l = r+1) {
		r = min(n,n/(n/l));
		(res += (s(l,r)-cnt(l,r,1)-cnt(l,r,2)) %mod * (n/l %mod)) %=mod;
	}
	return (res+mod) * Pow(2) %mod;
}
LL solve33() { return nn * (nn-1) %mod * (nn-2) %mod * Pow(6) %mod; }
LL solve41() {
	if( n == 4 || n == 5 ) return 1;
	return (n/6 + n/9 + n/10 + n/12 + n/15 + n/21) %mod;
}
LL solve42() {
	if( n == 4 || n == 5 || n == 6 ) return 1;
	if( n == 7 ) return 3;
	if( n == 8 ) return 6;
	if( n == 9 ) return 9;
	if( n == 10 ) return 10;
	return (n/11 + n/29) %mod;
}
LL solve43() {
	if( n == 4 ) return 1;
	if( n == 5 ) return 5;
	LL res = 0;
	for(LL l = 1, r; l <= n; l = r+1) {
		r = min(n,n/(n/l));
		(res += (s2(l,r) - 6*s(l,r) + 5*cnt(l,r,1) + 3*cnt(l,r,2) +
				4*cnt(l,r,3)) %mod * (n/l %mod)) %=mod;
	}
	return (res+mod) * Pow(12) %mod;
}
LL solve44()
	{ return nn * (nn-1) %mod * (nn-2) %mod * (nn-3) %mod * Pow(24) %mod; }

9.23 糖果

先考虑 \(a\) 都相同的情况,\(n\le10^{18}\) 提示需要 \(\log n\) 算法,考虑倍增 DP。
\(h[i,j]\) 表示前 \(2^i\) 种糖果选出 \(j\) 个排列的方案数,则

\[h[i,j]=\sum_{k=0}^jh[i-1,k]\times h[i-1,j-k]\times\binom jk \]

\(n\) 种糖果的方案数可以由 \(h\) 合并得到。

如果 \(a\) 不同,可以先将 \(a\)\(m\)\(\min\),这样 \(a\) 的值域变为 \(m\),通过 \(a\) 有循环节,可以 \(O(p+m)\) 时间内算出每个值有多少个品种。
然后对于不同的 \(a\) 分别进行上面的 DP,最后背包合并起来即可。

值得一提的是三处 DP 转移相同,因此可以将代码写的很好看

抄的 std DP 部分
void dp(LL *x,LL *y) {
	static LL z[N]; mem(z,0,m);
	For(i,0,m) For(j,0,i) z[i] = (z[i] + x[j]*y[i-j]%mod*C[i][j])%mod;
	memcpy(x,z,sizeof(LL)*(m+1));
}
signed main() {
	f[0] = 1;
	For(i,1,m) {
		mem(g,0,m), mem(h,0,m); g[0] = 1;
		For(j,0,i) h[j] = 1;
		if( cnt[i] & 1 ) dp(g,h);
		For(j,1,60) {
			dp(h,h);
			if( cnt[i] & (1ll<<j) ) dp(g,h); // 有cnt[i]个a=i
		}
		dp(f,g); // g:cnt[i]个i的答案
	}
	write(f[m]);
}

9.21 舞动的夜晚

二分图最大匹配的可行边

先任意求一组最大匹配,然后建新图:
匹配边 \((i,j)\)\(j\rightarrow i\),非匹配边 \((i,j)\)\(i\rightarrow j\)
左部匹配点 \(i\)\(i\rightarrow s\),右部匹配点 \(i\)\(t\rightarrow i\)
左部非匹配点 \(i\)\(s\rightarrow i\),右部非匹配点 \(i\)\(i\rightarrow t\)

则边 \((i,j)\) 是可行边的条件为 \((i,j)\) 在原最大匹配中或 \(i,j\) 在同一 SCC 中

正确性:
考虑匈牙利算法找增广路:从左部点出发,沿非匹配边走到右部点,再沿匹配边走到左部点 \(\cdots\cdots\)
如果在原最大匹配的基础上跑,那么一定会在左部点结束(否则就找到增广路了),将沿途边的匹配情况取反匹配数不变,即其中的非匹配边都是可行边,而这些点形成了 \(s\rightarrow\) 左部点 \(\rightarrow\) 右部点 \(\rightarrow\cdots\rightarrow s\) 的环,一定在同一 SCC 中。

9.21 Lesson5!

DAG 删点最长路

[POI2014]RAJ-Rally

9.20 2D

9.18 底垫

显然要离线询问,从小到大扫右端点。
先计算总爆零题数(即子区间的线段并集长度之和),答案再除以子区间数即可。

用 ODT 维护值域上每个数最后一次出现的时间(相同的相邻数合为一段),设当前扫到了 \(i\),可以快速得出新加入的区间会更新哪些段。

记其中一段的长度为 \(k\),时间为 \(t\),这段的贡献为:
\(l\in[t+1,i]\) 贡献 \((i-l+1)(r-i+1)k\)(左端点 \(\in[l,i]\),右端点 \(\in[i,r]\) 的子区间线段并集长度 \(+k\)
\(l\in[1,t]\) 贡献 \((i-t)(r-i+1)k\)(左端点 \(\in[1,t]\) 的子区间线段并集中已有这段)
拆一下式子,BIT 维护每个左端点 \(l,r,lr\) 的系数和常数项即可。

时间复杂度 \(O(n\log n)\)

9.14 午餐

9.13 树

根号全家桶:根号分治+分块平衡复杂度+根号重构节省空间

先用 dfs 序把子树转化为区间;将 \(y\) 加上 \(dep[v]\) 把与 \(v\) 距离转化为与根距离

  • \(x\le\sqrt n\)

模数、余数只有 \(\sqrt n\) 种,枚举模数后维护每个余数被加了多少,每个询问对每个模数查询一次
需要 \(q\) 次区间加,\(q\sqrt n\) 次单点查询,采用 \(O(\sqrt n)-O(1)\) 的朴素分块

  • \(x>\sqrt n\)

每个修改只会对 \(\sqrt n\) 个深度造成影响,枚举深度后进行修改,每个询问只会在枚举到它的深度时查询
如果直接把修改挂在深度上会造成 \(q\sqrt n\) 的空间复杂度,事实上只要每隔 \(\sqrt n\) 个深度算出所有修改对接下来 \(\sqrt n\) 个深度中哪个造成影响即可(显然在连续 \(\sqrt n\) 个深度中每个修改最多对一个造成影响)
需要 \(q\sqrt n\) 次区间加,\(q\) 次单点查询,采用 \(O(1)-O(\sqrt n)\) 的分块维护差分

时间复杂度 \(O((n+q)\sqrt n)\),空间复杂度 \(O(n+q)\)

code
#define MT make_tuple
int _ceil(int x,int y) { return (x+y-1) / y; }

const int N = 3e5+5, B = 547, M = 555;
int n,q;
VI to[N];

int ind,mxd,dfn[N],dep[N],siz[N],ans[N],be[N],le[M],ri[M];

void dfs(int u,int fa) {
	dfn[u] = ++ind, dep[u] = dep[fa]+1, ckmax(mxd,dep[u]), siz[u] = 1;
	for(int v : to[u]) if( v != fa ) dfs(v,u), siz[u] += siz[v];
}

namespace solve1 {
vector<PII> q;
vector<tuple<int,int,int,int>> a[M];
int dep[N],val[N],add[M][M];
void bf(int l,int r,int d,int x)
	{ For(i,l,r) if( dep[i] == d ) val[i] += x; }
void modify(int l,int r,int d,int x) {
	int bl = be[l], br = be[r];
	if( bl == br ) return bf(l,r,d,x);
	bf(l,ri[bl],d,x), bf(le[br],r,d,x);
	For(i,bl+1,br-1) add[i][d] += x;
}
int query(int i) { return val[i] + add[be[i]][dep[i]]; }
void main() { For(mod,1,B) {
	mem(val,0,n), memset(add,0,sizeof add);
	For(i,1,n) dep[dfn[i]] = ::dep[i] %mod;
	int i = 0; for(auto j : a[mod]) { int t,u,d,x; tie(t,u,d,x) = j;
		for(; i < q.size() && q[i].fi < t; ++i)
			ans[q[i].fi] += query(dfn[q[i].se]);
		modify(dfn[u],dfn[u]+siz[u]-1,d,x);
	}
	for(; i < q.size(); ++i) ans[q[i].fi] += query(dfn[q[i].se]);
}}
}

namespace solve2 {
vector<PII> q[N];
vector<tuple<int,int,int,int,int>> a;
vector<tuple<int,int,int>> b[B];
int val[N],sum[M];
void modify(int l,int x) { val[l] += x, sum[be[l]] += x; }
void modify(int l,int r,int x) { modify(l,x), modify(r+1,-x); }
int query(int r) {
	int br = be[r], res = 0;
	For(i,1,br-1) res += sum[i];
	For(i,le[br],r) res += val[i];
	return res;
}
void main() { For(d,1,mxd) {
	if( d % B == 1 ) {
		For(i,0,B-1) b[i].clear();
		for(auto i : a) { int t,u,x,y,z; tie(t,u,x,y,z) = i;
			int j = _ceil(d-y,x) * x + y;
			if( j < d+B ) b[j%B].pb(MT(t,u,z));
		}
	}
	mem(val,0,n), mem(sum,0,be[n]);
	int i = 0; for(auto j : b[d%B]) { int t,u,x; tie(t,u,x) = j;
		for(; i < q[d].size() && q[d][i].fi < t; ++i)
			ans[q[d][i].fi] += query(dfn[q[d][i].se]);
		modify(dfn[u],dfn[u]+siz[u]-1,x);
	}
	for(; i < q[d].size(); ++i) ans[q[d][i].fi] += query(dfn[q[d][i].se]);
}}
}

signed main() {
	read(n,q);
	For(i,2,n) { int x,y; read(x,y); to[x].pb(y), to[y].pb(x); }
	dfs(1,0);
	For(i,1,q) {
		int op,u,x,y,z; read(op,u);
		if( op == 1 ) {
			read(x,y,z); y = (y + dep[u]) %x;
			if( x <= B ) solve1::a[x].pb(MT(i,u,y,z));
			else solve2::a.pb(MT(i,u,x,y,z));
			ans[i] = -1;
		} else solve1::q.pb(MP(i,u)), solve2::q[dep[u]].pb(MP(i,u));
	}
	For(i,1,n) be[i] = (i-1)/B+1;
	For(i,1,be[n]) le[i] = ri[i-1]+1, ri[i] = ri[i-1]+B; ri[be[n]] = n;
	solve1::main(), solve2::main();
	For(i,1,q) if( ~ans[i] ) write(ans[i]);
	return iocl();
}
posted @ 2021-09-22 16:39  401rk8  阅读(182)  评论(2编辑  收藏  举报