首页 > 代码库 > BZOJ 3211 花神游历各国 树状数组+并查集

BZOJ 3211 花神游历各国 树状数组+并查集

题目大意:给定一个序列,提供下列操作:

1.将[l.r]区间内每个数a[i]变为sqrt(a[i])

2.查询[l,r]区间的和

根号是不支持区间修改的,于是我们选择单点修改区间查询的树状数组,但是这样是O(n^2)的,怎么办?

我们发现一个数x最多开loglogx次根号就会变为1 也就是一个int范围内的数只要开5次根号就会变为1 于是修改的总时间复杂度为O(nloglogn)

但是单次修改怎么办?我们维护一个并查集,一旦一个数为1或0,我们就把这个位置的father设为它右面的那个位置即可 这样可以均摊O(n)时间找到一个数后面第一个>1的数

此外此题加入读入优化可以快一倍 令人很费解0.0

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 100100
using namespace std;
typedef long long ll;
int n,m,a[M],fa[M];
ll c[M];
inline int getc() {
    static const int L = 1 << 15;
    static char buf[L], *S = buf, *T = buf;
    if (S == T) {
        T = (S = buf) + fread(buf, 1, L, stdin);
        if (S == T)
            return EOF;
    }
    return *S++;
}
inline int getint() {
    int c;
    while(!isdigit(c = getc()) && c != '-');
    bool sign = c == '-';
    int tmp = sign ? 0 : c - '0';
    while(isdigit(c = getc()))
        tmp = (tmp << 1) + (tmp << 3) + c - '0';
    return sign ? -tmp : tmp;
}
int Find(int x)
{
	if(!fa[x]||fa[x]==x)
		return fa[x]=x;
	return fa[x]=Find(fa[x]);
}
void Update(int x,int y)
{
	for(;x<=n;x+=x&-x)
		c[x]+=y;
}
ll Get_Ans(int x)
{
	ll re=0;
	for(;x;x-=x&-x)
		re+=c[x];
	return re;
}
void Modify(int x,int y)
{
	int i=x;
	for( i=x ; i<=y ; i=Find(i+1) )
	{
		int temp=(int)sqrt(a[i]);
		Update(i,temp-a[i]);
		a[i]=temp;
		if(a[i]<=1)
			fa[i]=Find(i+1);
	}
}
int main()
{
	int i,p,x,y;
	cin>>n;
	for(i=1;i<=n;i++)
	{
		a[i]=getint();
		if(a[i]==1||!a[i])
			fa[i]=i+1;
		Update(i,a[i]);
	}
	m=getint();
	for(i=1;i<=m;i++)
	{
		p=getint();
		x=getint();
		y=getint();
		if(p==1)
			printf("%lld\n", Get_Ans(y)-Get_Ans(x-1) );
		else
			Modify(x,y);
	}
}


BZOJ 3211 花神游历各国 树状数组+并查集