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