BZOJ3211 花神游历各国 并查集 树状数组
欢迎访问~原文出处——博客园-zhouzhendong
去博客园看该题解
题目传送门 - BZOJ3211
题意概括
有n个数形成一个序列。
m次操作。
有两种,分别是:
1. 区间开根(取整)
2. 区间求和
题解
这题做法大概我知道的有两种,一种是线段树,一种是并查集+树状数组。
两者都基于一个事实:任何一个数被开根很少的次数就变成1了,然后不变了。所以我们可以暴力解决这个开根的问题。
线段树就打一下lazy标记就可以了。
这里主要讲并查集和树状数组怎么做。
树状数组维护前缀和。
并查集的作用是跳过那些1的点。
如果第i个数变成了1,那么我们就让它认第i+1个点为爸爸。
那么对于第i个数,我们只需要求一下祖先就可以找到从第i个数开始的第一个会变的数了。
复杂度很小的。。
但是一开始Tle了……
注意:有一个非负整数叫做0。
0的处理和1等价。
代码
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstdlib>
using namespace std;
typedef long long LL;
const int N=100005;
int n,m,fa[N],v[N];
LL tree[N];
int lowbit(int x){
	return x&-x;
}
void add(int x,int d){
	for (;x<=n;x+=lowbit(x))
		tree[x]+=d;
}
LL sum(int x){
	LL ans=0;
	for (;x>0;x-=lowbit(x))
		ans+=tree[x];
	return ans;
}
int getf(int k){
	return fa[k]==k?k:fa[k]=getf(fa[k]);
}
int main(){
	scanf("%d",&n);
	memset(tree,0,sizeof tree);
	for (int i=1;i<=n;i++){
		scanf("%d",&v[i]);
		add(i,v[i]);
		fa[i]=i;
	}
	fa[n+1]=n+1;
	scanf("%d",&m);
	while (m--){
		int op,a,b;
		scanf("%d%d%d",&op,&a,&b);
		if (op==1)
			printf("%lld\n",sum(b)-sum(a-1));
		else {
			for (int i=getf(a);i<=b;i=getf(i+1)){
				int v_=floor(sqrt(v[i]));
				add(i,v_-v[i]);
				v[i]=v_;
				if (v[i]<=1)
					fa[i]=getf(i+1);
			}
		}
	}
	return 0;
}

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号