1. 程式人生 > >bzoj 3211:花神遊歷各國/3038 上帝造題的七分鐘2(luogu 4145)

bzoj 3211:花神遊歷各國/3038 上帝造題的七分鐘2(luogu 4145)

演算法:並查集+樹狀陣列/分塊/線段樹 

難度:(NOIP+)

簡述題目:區間開方,區間求和區間求和很容易想到樹狀陣列/線段樹,可是區間開方怎麼搞呢?暴力O(n*n),TLE到飛    我們可以發現,一個10^12的數,最多開6次方(向下取整)可以變成1,變成1之後,無論開多少次方都是1,所以就可以跳過這個數,這個可以用並查集搞,開始時父親都指向自己,如果變成1,就把父親指向下一個位置即可。修改的時候相當於跳著修改!

程式碼如下:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <queue>
#include <cmath>
#define ll long long
#define N 200015
using namespace std;
ll a[N],c[N];
int fa[N];
int findf(int x)
{
    if(x==fa[x]) return x;
    return fa[x]=findf(fa[x]);
}
int lowbit(int x)
{
    return x & (-x);
}
int l,r,k,n;
void add(int x,ll val)
{
    while(x<=n)
    {
        c[x]+=val;
        x+=lowbit(x);
    }
}
ll query(int x)
{
    ll ret=0;
    while(x)
    {
        ret+=c[x];
        x-=lowbit(x);
    }
    return ret;
}
int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n+5/*防止findf(n+1)  RE*/;i++)
    {
        fa[i]=i;
    }
    for(int i = 1;i <= n;i++)
    {
        scanf("%lld",&a[i]);
        add(i,a[i]);//忘記add,我在玩什麼? 
        if(a[i]<=1)
        {
            fa[i]=findf(findf(i)+1);
        }
    }
    int m;
    scanf("%d",&m);
    while(m--)
    {
        scanf("%d%d%d",&k,&l,&r);
        if(l>r) swap(l,r);
        if(k==2)
        {
            for(int i = findf(l);i<=r;i=findf(i+1/*指向下一位*/))
            {
                ll tmp=sqrt(a[i]);
                add(i,tmp-a[i]);//(sqrt(x)-x)+x=sqrt(x)
                a[i]=tmp;
                if(a[i]<=1)
                {
                    fa[i]=findf(i+1);//壓縮路徑 
                }    
            }
        }else
        {
            printf("%lld\n",query(r)-query(l-1));
        }
    }
    return 0 ;
}