洛谷 P4145 上帝造题的七分钟 2 / 花神游历各国

线段树解法

本题对区间求根号,因为不存在 s q r t ( ∑ i = 1 n a i ) = ∑ i = 1 n s q r t ( a i ) sqrt(\sum_{i=1}^{n}a_{i})=\sum_{i=1}^{n}sqrt(a_{i}) sqrt(i=1nai)=i=1nsqrt(ai) 的优秀性质,所以每一次的区间修改都要递归到叶子节点,也无法打出懒标记。因为父亲节点的值需要子节点的值来确定,无法一次性算出。

可行性解释

那这种递归到叶节点的修改方式复杂度岂不是爆炸?还不如不建树呢?其实不是这样的,对于一个 i n t int int 范围内的整数,因为每次开方后向下取整,其开方次数不会大于 6 6 6 次便会使这个数的值为1,便不需要再开方了。那么每个数至多开方 6 6 6 次。所以我们可以将每个节点赋予一个值 v v v ,代表这个区间有多少个 1 1 1 。如果这个区间的1的数量等于这个区间的数的数量,说明该区间内全是 1 1 1 ,则不需要为这个区间进行开方操作,直接返回即可。

具体实现

看代码吧。

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int n,m;
struct node{
	ll l,r,w,v;//w为区间和,v为1数量和
}t[maxn<<2];
ll a[maxn];
inline void pushup(int k)
{//同时对父亲节点两个数据进行更新
	t[k].w=t[k<<1].w+t[k<<1|1].w;
	t[k].v=t[k<<1].v+t[k<<1|1].v;
}
inline void build(int k,int l,int r)
{
	t[k].l=l,t[k].r=r;
	if(l==r){
		t[k].w=a[l];
		t[k].v=(t[k].w==1)?1:0;//初始判断是否有1
		return;
	}
	int mid=l+r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	pushup(k);
}
inline void update(int k,int l,int r)
{
	int x=t[k].l,y=t[k].r;
	if(t[k].v==y-x+1)return;//区间全为1,不用更新了,直接返回
	if(x==y){//得更新到叶子节点
		t[k].w=sqrt(t[k].w);
		t[k].v=(t[k].w==1)?1:0;
		return;
	}
	int mid=x+y>>1;
	if(l<=mid)update(k<<1,l,r);
	if(mid<r)update(k<<1|1,l,r);
	pushup(k);
}
inline ll query(int k,int l,int r)
{
	int x=t[k].l,y=t[k].r;
	if(l<=x&&y<=r)return t[k].w;
	int mid=x+y>>1;
	ll ans=0;
	if(l<=mid)ans+=query(k<<1,l,r);
	if(mid<r)ans+=query(k<<1|1,l,r);
	return ans;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	build(1,1,n);
	cin>>m;
	for(int i=1;i<=m;i++){
		int op,x,y;
		cin>>op>>x>>y;
		if(x>y){
			int tmp=x;x=y;y=tmp;
		}
		if(!op){
			update(1,x,y);
		}
		else{
			cout<<query(1,x,y)<<"\n";
		}
	} 
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值