bzoj 3211:花神遊歷各國/3038 上帝造題的七分鐘2(luogu 4145)
阿新 • • 發佈:2018-12-12
演算法:並查集+樹狀陣列/分塊/線段樹
難度:(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 ; }