1. 程式人生 > >[BZOJ3038]上帝造題的七分鐘2 樹狀陣列+並查集

[BZOJ3038]上帝造題的七分鐘2 樹狀陣列+並查集

考試的時候用了兩個樹狀陣列去優化,暴力修改,樹狀陣列維護修改後區間差值還有最終求和,最後騙了40分。。

這道題有好多種做法,求和好說,最主要的是開方。這道題過的關鍵就是掌握一點:在資料範圍內,最多開方五六次就會變成1,這樣以後再修改就不用修改了。

①  線段樹打標記

②  分塊打標記

③  樹狀陣列+並查集

因為我考試的時候用的樹狀陣列,所以直接打的第三種,相對來說程式碼量也少一些。

思路:開始時父親都指向自己,如果變成1,就把父親指向下一個位置即可。修改的時候相當於跳著修改。程式碼當中會有註解。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
#define pos(i,a,b) for(int i=(a);i<=(b);i++)
#define pos2(i,a,b) for(int i=(a);i>=(b);i--)
#define N 201000
#define LL long long
int n,m;
LL c[N],cha[N];
LL a[N];
LL fa[N];
LL lowbit(int x)
{
    return x&(-x);
}
void add(LL i,LL x)
{
     while(i<=n)
     {
        c[i]+=x;
        i+=lowbit(i);
     }
}
LL tot(int i)
{
    LL sum=0;
    while(i>0)
    {
      sum+=c[i];
      i-=lowbit(i);
    }
    return sum;
}
LL sum1(int i,int j)
{
   return tot(j)-tot(i-1);
}
LL find(int x)
{
    if(fa[x]!=x)
      fa[x]=find(fa[x]);
    return fa[x];
}
int main()
{
    //freopen("god.in","r",stdin);
    //freopen("god.out","w",stdout);
    scanf("%d",&n);
    pos(i,1,n+10)
      fa[i]=i;
    pos(i,1,n)
    {
      scanf("%lld",&a[i]);
      add(i,a[i]);
      if(a[i]<=1)
        fa[i]=find(find(i)+1);//插入時如果小於等於1,就指向下一位 
    }
    scanf("%d",&m);
    pos(i,1,m)
    {
       int k,l,r;
       scanf("%d%d%d",&k,&l,&r);
       if(l>r)
         swap(l,r);
       if(k==0)
       {
          for(LL j=find(l);j<=r;j=find(j+1))//迴圈時直接跳著迴圈 
          {
              
              LL tmp=(LL)sqrt(a[j]);
              add(j,tmp-a[j]);//相當於把節點修改為更改之後的值 
              a[j]=tmp;
              if(a[j]<=1)
                fa[j]=find(j+1);//壓縮路徑 
          }
       }
       else
           printf("%lld\n",sum1(l,r));
    }
    //while(1);
    return 0;
}