【CODEVS 2492】上帝造题的七分钟 2

【题目】

题目传送门上帝造题的七分钟 2

题目描述 Description

XLk觉得《上帝造题的七分钟》不太过瘾,于是有了第二部。

"第一分钟,X说,要有数列,于是便给定了一个正整数数列。
第二分钟,L说,要能修改,于是便有了对一段数中每个数都开平方(下取整)的操作。
第三分钟,k说,要能查询,于是便有了求一段数的和的操作。
第四分钟,彩虹喵说,要是noip难度,于是便有了数据范围。
第五分钟,诗人说,要有韵律,于是便有了时间限制和内存限制。
第六分钟,和雪说,要省点事,于是便有了保证运算过程中及最终结果均不超过64位有符号整数类型的表示范围的限制。
第七分钟,这道题终于造完了,然而,造题的神牛们再也不想写这道题的程序了。"
——《上帝造题的七分钟·第二部》
所以这个神圣的任务就交给你了。

输入描述 Input Description

第一行一个整数 n,代表数列中数的个数。
第二行 n 个正整数,表示初始状态下数列中的数。
第三行一个整数 m,表示有 m 次操作。
接下来 m 行每行三个整数 k , l , r,k = 0 表示给 [ l , r ] 中的每个数开平方(下取整),k = 1 表示询问 [ l , r ] 中各个数的和。
UPD:注意数据中有可能 l > r,所以遇到这种情况请交换 l 和 r。

输出描述 Output Description

对于询问操作,每行输出一个回答。

样例输入 Sample Input

10
1 2 3 4 5 6 7 8 9 10
5
0 1 10
1 1 10
1 1 5
0 5 8
1 4 8

样例输出 Sample Output

19
7
6

数据范围及提示 Data Size & Hint

对于30%的数据,1 ≤ n , m ≤ 1000,数列中的数不超过32767。
对于100%的数据,1 ≤ n , m ≤ 100000,1 ≤ l , r ≤ n,数列中的数大于 0,且不超过 10^{12}
注意 l 有可能大于 r,遇到这种情况请交换 l , r。

【分析】

莫名喜欢这道题的题目描述

首先看到题目,这样的题不就是典型的线段树(或树状数组)模板吗

但是有个问题,这里的修改是对一个数开方而不是加减乘除那些,不能用 lazy tag,而直接枚举又要超时,怎么办呢

这个时候我们就要知道一个性质:一个数(假设为 x)开方 log log x 次后会变成 1

而对于已经变为 1 的数,再对它开方就已经没有用了

于是我们想到用并查集来记录每一个数(假设为 p)右边第一个不为 1 的数(假设为 q),每次修改到 p 就直接从 q 开始继续修改,中间部分都跳过(因为中间部分都是 1)

时间复杂度:O(n * log n * log log n)

【代码】

先发线段树的代码:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define M 4*N
using namespace std;
int n,m,father[N];
long long a[N],sum[M];
int find(int x)
{
	if(father[x]==x)  return x;
	return  father[x]=find(father[x]);
}
void merge(int x,int y)
{
	x=find(x);
	y=find(y);
	if(x!=y)
	  father[x]=y;
}
void build(int root,int l,int r)
{
	if(l==r)
	{
		sum[root]=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(root<<1,l,mid);
	build(root<<1|1,mid+1,r);
	sum[root]=sum[root<<1]+sum[root<<1|1];
}
void modify(int root,int l,int r,int x)
{
	if(l==r)
	{
		sum[root]=sqrt(sum[root]);
		if(sum[root]==1)  merge(l,l+1);
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)  modify(root<<1,l,mid,x);
	else  modify(root<<1|1,mid+1,r,x);
	sum[root]=sum[root<<1]+sum[root<<1|1];
}
void change(int l,int r)
{
	int i;
	for(i=find(l);i<=r;i=find(i+1))
	  modify(1,1,n,i);
}
long long query(int root,int l,int r,int x,int y)
{
	if(l>=x&&r<=y)
	  return sum[root];
	int mid=(l+r)>>1;
	long long ans=0;
	if(x<=mid)  ans+=query(root<<1,l,mid,x,y);
	if(y>mid)  ans+=query(root<<1|1,mid+1,r,x,y);
	return ans;
}
int main()
{
        int s,i,l,r;
        scanf("%d",&n);
        for(i=1;i<=n;++i)
        {
        	father[i]=i;
        	scanf("%lld",&a[i]);
	}
	father[n+1]=n+1;
        build(1,1,n);
        scanf("%d",&m);
        for(i=1;i<=m;++i)
        {
    	        scanf("%d%d%d",&s,&l,&r);
            	if(l>r)  swap(l,r);
            	if(s==0)  change(l,r);
            	if(s==1)  printf("%lld\n",query(1,1,n,l,r));
	}
        return 0;
}

然后是树状数组

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
using namespace std;
int n,m,father[N];
long long a[N],tree[N];
int find(int x)
{
	if(father[x]==x)  return x;
	return  father[x]=find(father[x]);
}
void merge(int x,int y)
{
	x=find(x);
	y=find(y);
	if(x!=y)
	  father[x]=y;
}
int lowbit(int x)
{
	return x&(-x);
}
void add(int i,long long x)
{
	while(i<=n)
	{
		tree[i]+=x;
		i+=lowbit(i);
	}
}
long long query(int i)
{
	long long ans=0;
	while(i>=1)
	{
		ans+=tree[i];
		i-=lowbit(i);
	}
	return ans;
}
void change(int l,int r)
{
	int i;
	for(i=find(l);i<=r;i=find(i+1))
	{
		add(i,-a[i]);
		a[i]=sqrt(a[i]);
		add(i,a[i]);
		if(a[i]==1)
		  merge(i,i+1);
	}
}
int main()
{
        int s,i,l,r;
        scanf("%d",&n);
        for(i=1;i<=n;++i)
        {
    	        father[i]=i;
            	scanf("%lld",&a[i]);
    	        add(i,a[i]);
	}
	father[n+1]=n+1;
        scanf("%d",&m);
        for(i=1;i<=m;++i)
        {
    	        scanf("%d%d%d",&s,&l,&r);
    	        if(l>r)  swap(l,r);
    	        if(s==0)  change(l,r);
            	if(s==1)  printf("%lld\n",query(r)-query(l-1));
	}
        return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_dreams/article/details/81452055