BZOJ3211 花神游历各国 区间开根号

花神喜欢步行游历各国,顺便虐爆各地竞赛。花神有一条游览路线,它是线型的,也就是说,所有游历国家呈一条线的形状排列,花神对每个国家都有一个喜欢程度(当然花神并不一定喜欢所有国家)。
每一次旅行中,花神会选择一条旅游路线,它在那一串国家中是连续的一段,这次旅行带来的开心值是这些国家的喜欢度的总和,当然花神对这些国家的喜欢程序并不是恒定的,有时会突然对某些国家产生反感,使他对这些国家的喜欢度Q变为sqrt(Q)(可能是花神虐爆了那些国家的OI,从而感到乏味)。
现在给出花神每次的旅行路线,以及开心度的变化,请求出花神每次旅行的开心值。

输入格式
第一行是一个整数N,表示有个国家;
第二行有N个空格隔开的整数,表示每个国家的初始喜欢度Qi;
第三行是一个整数M,表示有条信息要处理;
第四行到最后,每行三个整数x,l,r,当x=1时询问游历国家到的开心值总和,也就是x=2,当时国家到中每个国家的喜欢度q变为sqrt(q)。

输出格式
每次时,每行一个整数。表示这次旅行的开心度

input
4
1 100 5 5
5
1 1 2
2 1 2
1 1 2
2 2 3
1 1 4

output
101
11
11

 

对于全部数据
1<=N<=100000
1<=M<=2*100000
1<=l<=r<=n
0<=Qi<=1e9

注:建议使用 sqrt 函数,且向下取整.

Sol1:
看来对点修改,想到了树状数组。
然后这种成段修改,用Bit操作还是有点烦的,但是因为发现一个正整数
其实开不了几次根号,就会变得<=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],j;
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 {
        	
        	    j=getf(a);
            	while(j<=b)
        	    {
        		int v_=int(sqrt(v[j]));
                add(j,v_-v[j]);
                v[j]=v_;
                if (v[j]<=1)
                {
                
                    fa[j]=j+1;
                    j=getf(j);
                    
                }
                else
                    j++;
        	
                
            }
        }
    }
    return 0;
}

  

 Sol2:思路同上,利用线段树来进行单点修改。

#include <cstdio>
#include <iostream>
#include <cmath>
#include <stack>
#include <algorithm>
#include <cstring>
#include <climits>
#define MAXN 2000000+10
#define LL long long
using namespace std;
LL n,m,a[MAXN],num[MAXN<<2],sit[MAXN];
int fa[MAXN];
int j;
int find(int x)
{
    if(fa[x]!=x) fa[x]=find(fa[x]);
    return fa[x];
}
void build(int rt,int l,int r)
{
    if(l==r)
    {
        num[rt]=a[l];
        sit[l]=rt;
        return ;
    }
    int m=(l+r)>>1;
    build(rt<<1,l,m);
    build(rt<<1|1,m+1,r);
    num[rt]=num[rt<<1]+num[rt<<1|1];
}
void update(int x,int l,int r,int rt)
{
    if(l==r&&l==x)
    {
        num[rt]=sqrt(num[rt]);
        return;
    }
    int m=(l+r)>>1;
    if(x<=m) update(x,l,m,rt<<1); else
    if(x>m)  update(x,m+1,r,rt<<1|1);
    num[rt]=num[rt<<1]+num[rt<<1|1];
}
LL query(int L,int R,int l,int r,int rt)
{
    if(L<=l&&r<=R)
        return num[rt];
    int m=(l+r)>>1;LL ans=0;
    if(L<=m) ans+=query(L,R,l,m,rt<<1);
    if(R>m) ans+=query(L,R,m+1,r,rt<<1|1);
    return ans;
}

int main()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        fa[i]=i;
    }
    fa[n+1]=n+1;
    build(1,1,n);
    scanf("%lld",&m);
    for(int i=1;i<=m;i++)
    {
        int o,a,b;
        scanf("%d%d%d",&o,&a,&b);
        if(a>b) swap(a,b);
        if(o==2)
        {
        	j=find(a);
        	while(j<=b)
        	{
        		update(j,1,n,1);
        		if(num[sit[j]]==1||num[sit[j]]==0)
        		//如果已变成1或0了,则这个数字不要再进行开根号运算了 
                    {
             	        	fa[j]=j+1;
                            j=find(j);
                    }
                else //否则j就只能移动它后面的一个数字上 
                     j++;
        	}
        	/*
            for(int j=find(a-1)+1;j<=find(b);)
            {   
                update(j,1,n,1);
                if(num[sit[j]]==1||num[sit[j]]==0)
                    fa[j-1]=j;
                j=find(j)+1;
            }
            */
        }else
        printf("%lld\n",query(a,b,1,n,1));
    }
    return 0;
}

  

Sol3:使用线段树来进行成段更新

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
#define p1 (p<<1)
#define p2 (p<<1|1)
const int N=100005;
int n,m,i,p,x,y,a[N],add[N<<2];
long long ans,t[N<<2];
void build(int l,int r,int p)
{
if(l==r)
{
   t[p]=a[l];
   if(a[l]<=1) 
      add[p]=1;
   else 
        add[p]=0;
return;
}
int mid=(l+r)>>1;
build(l,mid,p1);
build(mid+1,r,p2);
t[p]=t[p1]+t[p2];
add[p]=add[p1]&add[p2];
}
void update(int l,int r,int x,int y,int p)
{
if(add[p]) return;
if(l==r)
{
t[p]=(int)(sqrt(t[p]));
if(t[p]<=1) add[p]=1;
return;
}
int mid=(l+r)>>1;
if(x<=mid)
     update(l,mid,x,y,p1);
if(y>mid) 
     update(mid+1,r,x,y,p2);
add[p]=add[p1]&add[p2];
t[p]=t[p1]+t[p2];
}
void solve(int l,int r,int x,int y,int p)
{
if(x<=l&&r<=y)
{
    ans+=t[p];
    return;
}
int mid=(l+r)>>1;
if(x<=mid) solve(l,mid,x,y,p1);
if(y>mid) solve(mid+1,r,x,y,p2);
}
int main()
{
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
build(1,n,1);
scanf("%d",&m);
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&p,&x,&y);
if(p==1)
{
ans=0;
solve(1,n,x,y,1);
printf("%lld\n",ans);
} 
else
update(1,n,x,y,1);
}
return 0;
}

  Sol4:分块算法

 

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
  
int n,k,opt,l,r,c,p[50010];
ll t[50010],w[50010];bool f[50010];
  
void Work(int now)
{
    w[p[now]]-=t[now];//从总和中减去当前位置上的值 
    t[now]=sqrt(t[now]);//当前位置上的值开根号 
    w[p[now]]+=t[now];//再加回去 
}
  
void Calc(int now)
{
    if(f[now])//如果当前位置已全为0了 
        return;
    f[now]=true;//假设这一块全为0了,这一句非常重要,因为如果不全为0,后面会改过去的 
    w[now]=0;
    for(int i=(now-1)*k+1;i<=now*k;i++)
    {
        t[i]=sqrt(t[i]);
           w[now]+=t[i];
        if(t[i]>1)
           f[now]=false;
    }
}
  
void Change(int l,int r,int c)
{
    if(p[l]==p[r])
        for(int i=l;i<=r;i++)
            Work(i);
    else{
        for(int i=l;i<=p[l]*k;i++)
            Work(i);
        for(int i=(p[r]-1)*k+1;i<=r;i++)
            Work(i);
        for(int i=p[l]+1;i<=p[r]-1;i++)
            Calc(i);
    }
}
  
ll Query(int l,int r)
{
    ll ans=0;
    if(p[l]==p[r])
        for(int i=l;i<=r;i++)  //暴力统计 
            ans+=t[i];
    else{
        for(int i=l;i<=p[l]*k;i++)
            ans+=t[i];
        for(int i=(p[r]-1)*k+1;i<=r;i++)
            ans+=t[i];
        for(int i=p[l]+1;i<=p[r]-1;i++)
            ans+=w[i];
    }
    return ans;
}
  
int main(){
    scanf("%d",&n);k=sqrt(n);
    for(int i=1;i<=n;i++)p[i]=(i-1)/k+1;
    for(int i=1;i<=n;i++)
        scanf("%d",&t[i]),w[p[i]]+=t[i];
    for(int i=1;i<=n;i++){
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(opt)
            printf("%lld\n",Query(l,r));
        else //表示将位于[l,r]的之间的数字都开方
             Change(l,r,c);
    }
    return 0;
}

  

posted @ 2021-04-22 20:29  我微笑不代表我快乐  阅读(73)  评论(0编辑  收藏  举报