[题解]P7334 [JRKSJ R1] 吊打

P7334 [JRKSJ R1] 吊打

看到开方,想到 GSS4 的套路,\(10^9\) 开五次方就成 \(1\) 了,开个线段树,对于非全 \(1\) 的区间直接往里递归,在叶子结点暴力修改。但加上平方后还能吗?

我们发现,平方再开方值是不变的,开方再平方就不一定。也即开方是不可逆的。

所以,我们可以只记录节点 \(x\) 平方的次数 \(c[x]\)。平方时直接 \(c[x]\) 自增 \(1\);开方时若 \(c[x]>0\) 则自减 \(1\),否则就继续递归,若到叶子节点就直接暴力修改。

根据神秘公式(扩展欧拉定理),若 \(a,m\) 互质,则 \(a^b\equiv a^{b\bmod \varphi(m)}\pmod m\),所以有 \(x^{2^y}\equiv x^{2^y\bmod 998244353\color{red}\bf{-1}}\pmod{998244353}\),这样就能快速幂了。

考虑分析时间复杂度。

区间平方显然是 \(O(n\log n)\) 的。

区间开方,若走到一个节点时 \(c[x]\ne 0\),那么和普通线段树一样是 \(n\log n\) 的。

额外的复杂度是直接对叶子节点开方,每次需要递归 \(O(\log n)\) 个节点,开到 \(1\) 需要的次数是 \(O(\log\log V)\),所以额外的开销是 \(O(n\log n\log\log V)\)

最后统计答案是 \(O(n\log P)\) 的。

综上总时间为 \(O(n\log n\log\log V+n\log P)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define lc (x<<1)
#define rc (x<<1|1)
using namespace std;
const int N=2e5+5,P=998244353;
inline void chmx(int &x,int y){x=max(x,y);}
int n,m,a[N],ans;
inline int qp(int x,int n,int p){
	int a=1;
	while(n){
		if(n&1) a=a*x%p;
		x=x*x%p,n>>=1;
	}
	return a;
}
struct SEG{
	int val[N<<2],c[N<<2];//叶子结点开根后的值,累计平方次数
	bitset<N<<2> flg;//区间是否全1
	inline void pu(int x){
		if(c[lc]&&c[rc]){
			int z=min(c[lc],c[rc]);
			c[lc]-=z,c[rc]-=z,c[x]+=z;
		}
		flg[x]=flg[lc]&&flg[rc];
	}
	inline void pd(int x){
		if(c[x]) c[lc]+=c[x],c[rc]+=c[x],c[x]=0;
	}
	inline void build(int x,int l,int r){
		if(l==r) return val[x]=a[l],void();
		int mid=(l+r)>>1;
		build(lc,l,mid),build(rc,mid+1,r),pu(x);
	}
	inline void sqr(int x,int a,int b,int l,int r){//区间开根
		if(flg[x]) return;
		if(a<=l&&r<=b&&c[x]) return c[x]--,void();
		if(l==r) return val[x]=sqrt(val[x]),flg[x]=(val[x]==1),void();
		int mid=(l+r)>>1;
		pd(x);
		if(a<=mid) sqr(lc,a,b,l,mid);
		if(b>mid) sqr(rc,a,b,mid+1,r);
		pu(x);
	}
	inline void pwr(int x,int a,int b,int l,int r){//区间平方
		if(a<=l&&r<=b) return c[x]++,void();
		int mid=(l+r)>>1;
		pd(x);
		if(a<=mid) pwr(lc,a,b,l,mid);
		if(b>mid) pwr(rc,a,b,mid+1,r);
		pu(x);
	}
	inline void solve(int x,int l,int r){
		if(l==r) return (ans+=qp(val[x],qp(2,c[x],P-1),P))%=P,void();
		int mid=(l+r)>>1;
		pd(x),solve(lc,l,mid),solve(rc,mid+1,r);
	}
}tr;
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	tr.build(1,1,n);
	for(int i=1,op,l,r;i<=m;i++){
		cin>>op>>l>>r;
		if(op==1) tr.sqr(1,l,r,1,n);
		else tr.pwr(1,l,r,1,n);
	}
	tr.solve(1,1,n);
	cout<<ans<<"\n";
	return 0;
}

另一种思路是不考虑原数组的初值,开方时允许 \(c\) 变成负数。

由于只有开方是不可逆的,所以我们只关心最多开了多少次,即 \(c\) 的历史最小值。同样快速幂统计答案。

总时间 \(O(n\log\log V+n\log n+n\log P)\),更优秀一些。

一开始想的是这种做法,但我不会写历史最值线段树,就先咕咕咕——了!

posted @ 2025-11-27 15:32  Sinktank  阅读(6)  评论(0)    收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2025 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.