[CF1491H] Yuezheng Ling and Dynamic Tree
前言
据说这是 lxl 的题啊。
你这分块不是也是正解吗?
不,我这是部分分暴力。
题目
讲解
分块就完事了。
借用重链剖分的思想,考虑维护一个 \(tp_i\) 表示 \(i\) 跳出块外的位置。
散块怎么改复杂度都没问题,所以我们开整块。
对于一个整块,其大小为 \(\sqrt{n}\),所以整块被修改了至多 \(\sqrt{n}\) 次之后,里面的所有元素跳一次就可以出块,所以维护整块修改次数即可,超过 \(\sqrt{n}\) 次之后就是分块版本的区间加了,很简单。
对于询问,类似重链剖分,先跳后面的,如果它一步可以跳出块或者两个点的 \(tp\) 相同就暴力一步一步跳,否则跳 \(tp\),显然时间也是 \(O(\sqrt{n})\)。
总时间复杂度 \(O(n\sqrt{n})\)。
注意每次改的时候从左往右改,改散块的时候前面那个部分改了后面也要改。
前面的 \(tp\) 变了,后面的 \(tp\) 也可能跟着变,这一点非常重要,我 \(\tt \color{red}WA\) 了很久。
代码
没卡常也不需要卡常的代码
//12252024832524
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 100010;
int n,Q,B;
int a[MAXN];
LL Read()
{
LL x = 0,f = 1; char c = getchar();
while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}
int tp[MAXN],cnt[MAXN],lz[MAXN];
int ID(int x){if(x <= 1) return x-1;return (x-2)/B+1;}
int FID(int x){return B*(x-1)+2;}
int pre(int i){return Max(1,a[i]-lz[ID(i)]);}
void up(int i)
{
if(ID(pre(i))^ID(i)) tp[i] = pre(i);
else tp[i] = tp[pre(i)];//没出块,整个块的都tp没问题
}
void solve(int l,int r,int x)//左闭右开!
{
for(int i = l;i < r;++ i)
{
a[i] = Max(a[i]-x,1);
up(i);
}
}
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
n = Read(); Q = Read();
B = ceil(sqrt(n));
for(int i = 2;i <= n;++ i) a[i] = Read();
for(int i = 2;i <= n;++ i) up(i);
while(Q --> 0)
{
int opt = Read(),u = Read(),v = Read();
if(opt == 1)
{
int U = ID(u),V = ID(v),x = Read();
if(U+1 >= V) solve(u,v+1,x),solve(FID(U),Min(n+1,FID(V+1)),0);//散块要改后面1
else
{
solve(u,FID(U+1),x);
for(int i = U+1;i < V;++ i)
{
++cnt[i];
if(cnt[i] >= B) lz[i] = Min(lz[i]+x,n);//此时tp作废
else solve(FID(i),FID(i+1),x);
}
solve(FID(V),v+1,x);
solve(FID(V),Min(n+1,FID(V+1)),0);//散块要改后面2
}
}
else
{
while(u^v)
{
if(u<v) swap(u,v);
//一步出去 或者 在同一个块里面且出去一样
if((ID(pre(u))^ID(u)) || (ID(u) == ID(v) && tp[u] == tp[v])) u = pre(u);
else u = tp[u];//一步出不去,必定没觉醒,没觉醒之前,tp[u] 是对的
}
Put(u,'\n');
}
}
return 0;
}