zoukankan      html  css  js  c++  java
  • 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
    ",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
    ",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
    ",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
    ",Query(l,r));
            else //表示将位于[l,r]的之间的数字都开方
                 Change(l,r,c);
        }
        return 0;
    }
    

      

  • 相关阅读:
    BNU 51002 BQG's Complexity Analysis
    BNU OJ 51003 BQG's Confusing Sequence
    BNU OJ 51000 BQG's Random String
    BNU OJ 50999 BQG's Approaching Deadline
    BNU OJ 50998 BQG's Messy Code
    BNU OJ 50997 BQG's Programming Contest
    CodeForces 609D Gadgets for dollars and pounds
    CodeForces 609C Load Balancing
    CodeForces 609B The Best Gift
    CodeForces 609A USB Flash Drives
  • 原文地址:https://www.cnblogs.com/cutemush/p/14691376.html
Copyright © 2011-2022 走看看