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;
}

  

posted @ 2017-12-11 12:39  zzd233  阅读(237)  评论(0编辑  收藏  举报