P9989 [Ynoi Easy Round 2023] TEST_69 思考记录
Part.1 思路:
第一道 Ynoi!!!!
最初拿到这道题,思路非常明确,我们注意到这道题与 P4145 的解题思路类似,P4145 运用了 \(\sqrt{x}\) 缩小极快的性质,我们只需维护区间和,区间 max,每次在合适的位置进行剪枝,再暴力开方即可。
void change(int x,int l,int r) {
if(tree[x].ma<=1) {
return;
}
if(tree[x].l==tree[x].r) {
tree[x].k=tree[x].ma=sqrt(tree[x].k);
return;
}
int mid=(tree[x].l+tree[x].r)>>1;
if(l<=mid) {
change(lc(x),l,r);
}
if(r>mid) {
change(rc(x),l,r);
}
pushup(x);
}
这就是最重要的修改代码,非常简单易懂。
但是这道题与 P4145 并不相同,\(\gcd{x}\) 相对于 \(\sqrt{x}\) 更加难以维护(其实是废话)。但,我们注意到,\(\gcd{x}\) 每次操作结束后,都必定还是 \(x\) 的因数,所以我们可以发现,\(x\) 的变化次数不会超过它的 因数个数,复杂度是 \(\log{x}\) 的,我们可以抓住这个性质找到突破口。
简单的说,我们只需要维护区间 \(lcm\),每次递归判断 \(x\) 能否整除区间 \(lcm\) 如果能,直接结束递归就行了,因为此时继续 \(\gcd\) 这个区间内的数值也不会有任何变化了。
不是,这真这么简单吗?
Part.2 代码实现:
首先,我们用结构体封装线段树的主题。
struct node {
int l,r,k,lm;
}tree[N<<2];
//注意线段树要开四倍空间。
接着,pushup 和建树也没有什么可以说的。
void pushup(int x) {
tree[x].k=(tree[lc(x)].k+tree[rc(x)].k)%mod;
//注意取模。
tree[x].lm=lcm(tree[lc(x)].lm,tree[rc(x)].lm);
}
void build(int x,int l,int r) {
tree[x].l=l;
tree[x].r=r;
if(l==r) {
tree[x].k=tree[x].lm=a[l];
return;
}
int mid=(l+r)>>1;
build(lc(x),l,mid);
build(rc(x),mid+1,r);
pushup(x);
}
change 函数相比 P4145,唯一的区别就是将剪枝的部分修改一下,其他部分几乎不变。
void change(int x,int l,int r,int v) {
if(v%tree[x].lm==0) {
return;
}
if(tree[x].l==tree[x].r) {
tree[x].lm=gcd(tree[x].lm,v);
tree[x].k=tree[x].lm%mod;
return;
}
int mid=(tree[x].l+tree[x].r)>>1;
if(l<=mid) {
change(lc(x),l,r,v);
}
if(r>mid) {
change(rc(x),l,r,v);
}
pushup(x);
}
查询操作就是最普通的求区间和,这个不多说。
int query(int x,int l,int r) {
if(l<=tree[x].l&&tree[x].r<=r) {
return tree[x].k;
}
int z=0,mid=(tree[x].l+tree[x].r)>>1;
if(l<=mid) {
z+=query(lc(x),l,r)%mod;
}
if(r>mid) {
z+=query(rc(x),l,r)%mod;
}
return z%mod;
}
在经过以上所有的步骤后,你会得到这个。

浙公网安备 33010602011771号