HDU 4027 —— Can you answer these queries?(线段树)

两个操作:

0,x,y,表示将区间[x,y]里每个值开平方然后向下取整

1,x,y,区间[x,y]求和

很明显是线段树,可一开始看着开平方不知道怎么下手,分析了下发现其实每个数能被开方的次数不超过6,6以后无论哪个数被开方都只能得到1

所以可以用一个h[o]表示结点o内所有数被开方的相同次数,-1表示区间内次数不同

再用一个f[o]表示当前区间还有多少次开方没有推给子节点

sum[o][i]表示o结点内所有数都被开方i次之后的和,这个是可以预处理掉的

ans[o]就表示结点和了

如果碰到h[o]非-1的话,就ans[o]=sum[o][h[o]],

是-1的话就只能是ans[o] = ans[LEFT(o)] + ans[RIGHT(o)],

而h[o]的计算也很简单,看看它左右子节点的h值是否相同,相同就是该值,否则就是-1

//HDU 4027 562MS 22628K 2608 B G++
#include<cstdio>
#include<cstring>
#include<cmath>
#define LL __int64
#define LEFT(a) ((a)<<1)
#define RIGHT(a) (((a)<<1)|1)
#define MID(a,b) (((a)+(b))>>1)
#define MAXN 400010
LL ans[MAXN], sum[MAXN][7], a[MAXN>>2];
int ct, n, q, t, x, y, l[MAXN], r[MAXN], h[MAXN], f[MAXN];
int getnum(){
	int s=0;
	char c=getchar();
	while(c<48 || c>57)	c=getchar();
	while(c>=48 && c<=57){
		s = s*10+c-48;
		c = getchar();
	}
	return s;
}
LL get64(){
	LL s=0;
	char c=getchar();
	while(c<48 || c>57)	c=getchar();
	while(c>=48 && c<=57){
		s = s*10+c-48;
		c = getchar();
	}
	return s;
}
void cal(int o){
	int ll=LEFT(o), rr=RIGHT(o);
	for(int i=0; i<7; i++)	sum[o][i]=sum[ll][i]+sum[rr][i];
	ans[o]=ans[ll]+ans[rr];
}
void build(int o, int ll, int rr){
	l[o]=ll;
	r[o]=rr;
	if(ll<rr){
		int m = MID(ll, rr);
		build(LEFT(o), ll, m);
		build(RIGHT(o), m+1, rr);
		cal(o);
	}
	else{
		ans[o]=sum[o][0]=a[ll];
		for(int i=1; i<7; i++)	sum[o][i] = (LL)(sqrt(sum[o][i-1]));
	}
}
void init(){
	for(int i=1; i<=n; i++)	a[i]=get64();
	build(1, 1, n);
}
void maintain(int o){
	int ll=LEFT(o), rr=RIGHT(o);
	ans[o] = ans[ll]+ans[rr];
	if(h[ll]==h[rr])	h[o]=h[ll];
	else	h[o]=-1;
}
void pushdown(int o);
void set(int o, int ll, int rr, int v);
void pushdown(int o){
	int m = MID(l[o],r[o]);
	set(LEFT(o), l[o], m, f[o]);
	set(RIGHT(o), m+1, r[o], f[o]);
	f[o]=0;
}
void set(int o, int ll, int rr, int v){
	if(h[o]==6)	return;//如果已经开方6次了,就没必要再往下算了
	if(l[o]==ll && r[o]==rr){
		if(~h[o]){//判断h[o]是否为-1
			h[o]+=v;
			if(h[o]>6)	h[o]=6;
			ans[o] = sum[o][h[o]];
			f[o]+=v;
		}
		else{
			f[o]+=v;
			pushdown(o);
			maintain(o);
		}
	}
	else{
		if(f[o])	pushdown(o);
		int m = MID(l[o], r[o]);
		if(m>=rr)	set(LEFT(o), ll, rr, v);
		else if(m<ll)	set(RIGHT(o), ll, rr, v);
		else{
			set(LEFT(o), ll, m, v);
			set(RIGHT(o), m+1, rr, v);
		}
		maintain(o);
	}
}
LL query(int o, int ll, int rr){
	if(l[o]==ll && r[o]==rr)	return ans[o];
	if(f[o])	pushdown(o);
	LL tmp;
	int m = MID(l[o],r[o]);
	if(m>=rr)	tmp = query(LEFT(o), ll, rr);
	else if(m<ll)	tmp = query(RIGHT(o), ll, rr);
	else{
		tmp = query(LEFT(o), ll, m);
		tmp += query(RIGHT(o), m+1, rr);
	}
	maintain(o);
	return tmp;
}
int main(){
	ct=0;
	while(~scanf("%d", &n)){
		printf("Case #%d:\n", ++ct);
		memset(f, 0, sizeof(f));
		memset(h, 0, sizeof(h));
		init();
		q=getnum();
		while(q--){
			t=getnum();
			x=getnum();
			y=getnum();
			if(x>y){
				x^=y; y^=x; x^=y;
			}
			if(t)	printf("%I64d\n", query(1, x, y));
			else	set(1, x, y, 1);
		}
		puts("");
	}
	return 0;
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值