日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

上古时期(大雾)的数据结构pdf

發(fā)布時(shí)間:2023/12/3 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 上古时期(大雾)的数据结构pdf 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

分塊+點(diǎn)分治+Treap

byWYCby\ WYCby?WYC


Part1 分塊


概念

就是將nnn個(gè)數(shù)分成若干個(gè)塊,然后要處理的時(shí)候整塊一起的加上局部的直接暴力。

如果將塊的大小分配好一般每次都是O(n)O(\sqrt n)O(n?)的。

而且因?yàn)槭直┝?#xff0c;所以有很多優(yōu)秀的性質(zhì)。


實(shí)現(xiàn)方法

怎么暴力的還用講實(shí)現(xiàn)方法,好吧,講一些基礎(chǔ)的

要求區(qū)間求和,區(qū)間修改

對(duì)于每個(gè)塊維護(hù)一個(gè)所以值的和和一個(gè)懶惰

區(qū)間求和時(shí),將整塊的直接加上去,局部的暴力加上去

區(qū)間修改時(shí),將整塊的懶惰標(biāo)記,局部的暴力加上去

對(duì)于局部修改時(shí),如果有懶惰就直接暴力將整個(gè)塊都修改,然后去掉懶惰。

時(shí)間復(fù)雜度每個(gè)都是O(n)O(\sqrt n)O(n?)

是不是十分優(yōu)秀


例題

在例題中體現(xiàn)優(yōu)秀的性質(zhì)


P4168-[Violet]蒲公英

題目大意

詢問區(qū)間眾數(shù)

解題思路

將數(shù)字離散化,然后分塊。對(duì)于數(shù)組vi,j,kv_{i,j,k}vi,j,k?,表示i~ji\sim jij個(gè)塊,kkk的個(gè)數(shù)。對(duì)于詢問(l,r)(l,r)(l,r),將整塊的直接累計(jì),然后局部的直接暴力。

時(shí)間復(fù)雜度:O(NT2+MNT)O(NT^2+\frac{MN}{T})O(NT2+TMN?),根據(jù)LYD的說法,讓T≈n3T\approx \sqrt[3]nT3n?的話,時(shí)間復(fù)雜度就可以在O(N53)O(N^{\frac{5}{3}})O(N35?)級(jí)別。

代碼寫優(yōu)美些或者加點(diǎn)優(yōu)化就可以過。

code

#include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #define Tn 40 #define N 40010 using namespace std; int n,m,L[Tn],R[Tn],v[Tn][Tn][N],l,r,num[N]; int z[N],w[N],a[N],cnt,k[N],t,T,pos[N],x; bool cmp(int x,int y)//排序 {return z[x]<z[y];} void begins()//預(yù)處理 {for(int i=1;i<=t;i++){L[i]=(i-1)*T+1;R[i]=i*T;}if(R[t]<n) t++,L[t]=R[t-1]+1,R[t]=n;//計(jì)算邊界for(int i=1;i<=t;i++)for(int j=i;j<=t;j++)for(int k=L[i];k<=R[j];k++)v[i][j][a[k]]++;//計(jì)算v數(shù)組 } int ask(int l,int r) {int p=pos[l],q=pos[r];if(p-q<=2){memset(k,0,sizeof(k));for(int i=l;i<=r;i++)k[a[i]]++;}else{p++;q--;for(int i=1;i<=cnt;i++)k[i]=v[p][q][i];//直接累計(jì)for(int i=l;i<L[p];i++)//暴力統(tǒng)計(jì)k[a[i]]++;for(int i=R[q]+1;i<=r;i++)//暴力統(tǒng)計(jì)*2k[a[i]]++;}int mark=1;for(int i=2;i<=cnt;i++)//找眾數(shù)if(k[i]>k[mark]) mark=i;return w[mark]; } int main() {scanf("%d%d",&n,&m);t=(int)pow(double(n),1.0/3);T=n/t;for(int i=1;i<=n;i++){scanf("%d",&z[i]);num[i]=i;}sort(num+1,num+1+n,cmp);z[0]=-1;for(int i=1;i<=n;i++)//離散化{if(z[num[i]]!=z[num[i-1]]) cnt++,w[cnt]=z[num[i]];;a[num[i]]=cnt;}T=n/t;begins();for(int i=1;i<=m;i++){scanf("%d%d",&l,&r);l=(l+x-1)%n+1;r=(r+x-1)%n+1;if(l>r) swap(l,r);printf("%d\n",x=ask(l,r));} }

優(yōu)秀性質(zhì)1

對(duì)于塊你可以構(gòu)建二維的,就十分優(yōu)秀。

如這道題中vi,j,kv_{i,j,k}vi,j,k?對(duì)于塊進(jìn)行了二維


P4879-ycz的妹子

題目大意

有若干種操作

  • Cxy:ax?=yC\ x\ y:a_x-=yC?x?y:ax??=y
  • Ixy:I\ x\ y:I?x?y:加一個(gè)(原本有的話就改變)ax=ya_x=yax?=y
  • Q:Q:Q:詢問所以數(shù)的和
  • Dx:D\ x:D?x:刪除第xxx個(gè)(有的才算)數(shù)。
  • 解題思路

    我們發(fā)現(xiàn)只有單點(diǎn)修改,和全部求和。所以ansansans表示全部的和,然后前三個(gè)操作就搞定了。
    第四個(gè)操作維護(hù)一個(gè)分塊,記錄每塊有的數(shù)的個(gè)數(shù),然后找到目標(biāo)在那個(gè)塊,之后這個(gè)塊里暴力尋找,這個(gè)操作時(shí)間負(fù)責(zé)度O(n)O(\sqrt n)O(n?)

    總體時(shí)間復(fù)雜度:O(nn)O(n\sqrt n)O(nn?)

    codecodecode

    #include<cstdio> #include<cmath> #include<iostream> #define ll long long #define N 500000 #define T 1000 using namespace std; ll n,m,a[N+10],ans,no[N+10],x,y,t; ll L[T],R[T],num[T],pos[N+10]; char c; int main() {scanf("%lld%lld",&n,&m);t=sqrt(N);for(ll i=1;i<=N;i++){pos[i]=(i-1)/t+1;if(i>n)no[i]=1;}for(ll i=1;i<=n;i++){scanf("%lld",&a[i]);num[pos[i]]++;ans+=a[i];}for(ll i=1;i<=m;i++){cin>>c;if(c=='Q')printf("%lld\n",ans);else if(c=='I'){scanf("%lld%lld",&x,&y);ans-=a[x];a[x]=y;ans+=y;n=max(n,x);if(no[x]){no[x]=0;num[pos[x]]++;}}else if(c=='C'){scanf("%lld%lld",&x,&y);if(no[x]) continue;ans-=y;a[x]-=y;}else if(c=='D'){scanf("%lld",&x);ll k=1;while(x-num[k]>0)x-=num[k],k++;for(int i=(k-1)*t+1;i<=min(k*t,n);i++){if(!no[i]) --x;if(!x){num[k]--;no[i]=1;ans-=a[i];a[i]=0;break;}}}} }

    優(yōu)秀性質(zhì)2

    一個(gè)塊一個(gè)塊的跑,利用儲(chǔ)存塊的信息快速鎖定目標(biāo)


    P3203-[HNOI2010]彈飛綿羊

    這個(gè)就強(qiáng)大了

    題目大意

    nnn個(gè)裝置。到第iii個(gè)裝置會(huì)被往前彈aia_iai?個(gè)。
    兩種操作
    修改aia_iai?和詢問從iii出發(fā)要多少次彈射可以彈出去。

    解題思路

    first,本題正解LCT。然而分塊可以輕松過掉

    分塊。對(duì)于每個(gè)點(diǎn),維護(hù)要多少步彈出該塊和彈出去后彈到哪里。
    詢問就直接根據(jù)兩個(gè)數(shù)據(jù),修改就直接重構(gòu)整個(gè)塊。

    時(shí)間復(fù)雜度:O(nn)O(n\sqrt n)O(nn?)

    code

    #include<cstdio> #include<cmath> #define N 200010 #define T 500 using namespace std; int n,m,x,t,a[N],L[T],R[T],step[N],to[N],pos[N]; void pre_work()//預(yù)處理 {for(int i=1;i<=t;i++)//塊邊界{L[i]=(i-1)*t+1;R[i]=i*t;}if(R[t]!=n) t++,L[t]=R[t-1]+1,R[t]=n;for(int i=1;i<=t;i++)for(int j=R[i];j>=L[i];j--){if(j+a[j]<=R[i]) step[j]=step[j+a[j]]+1,to[j]=to[j+a[j]];else step[j]=1,to[j]=j+a[j];pos[j]=i;}//初始數(shù)據(jù) } int ask(int x)//詢問 {int ans=0;while(x<=n)ans+=step[x],x=to[x];return ans; } void change(int i)//重構(gòu)塊 {for(int j=R[i];j>=L[i];j--)if(j+a[j]<=R[i]) step[j]=step[j+a[j]]+1,to[j]=to[j+a[j]];else step[j]=1,to[j]=j+a[j]; } int main() {scanf("%d",&n);t=sqrt(n);for(int i=1;i<=n;i++)scanf("%d",&a[i]);pre_work();scanf("%d",&m);for(int i=1;i<=m;i++){scanf("%d",&x);if(x==1){scanf("%d",&x);x++;printf("%d\n",ask(x));}else{scanf("%d",&x);x++;scanf("%d",&a[x]);change(pos[x]);}} }

    優(yōu)秀性質(zhì)3

    本題最強(qiáng)大的地方是在于當(dāng)他修改時(shí),直接暴力重構(gòu)了整個(gè)塊,十分強(qiáng)大


    性質(zhì)總結(jié)

    對(duì)于分塊,最強(qiáng)大的操作就是暴力。因?yàn)闊o論怎么暴力,只要每次保證不用重構(gòu)太多塊,基本都是O(n)O(\sqrt n)O(n?)

    然后就算根據(jù)每個(gè)塊的信息找一遍,也依據(jù)是O(n)O(\sqrt n)O(n?)

    而且n2=n\sqrt n^2=nn?2=n所以我們可以猥瑣欲為的將數(shù)據(jù)當(dāng)做可以n2n^2n2的來看


    分塊-終章

    “局部暴力,大段維護(hù)”。當(dāng)然分塊十分簡(jiǎn)單,性質(zhì)也十分優(yōu)秀,也很好寫。

    but!but!but!,時(shí)間復(fù)雜度終究是O(nn)O(n\sqrt n)O(nn?)的,如果出題人讓你用O(nlogn)O(n\ log\ n)O(n?log?n)也沒有辦法

    還是老老實(shí)實(shí)寫別的數(shù)據(jù)結(jié)構(gòu)對(duì)吧


    Part2-點(diǎn)分治


    概念

    首先,點(diǎn)分治無論在速度還是處理問題方面都會(huì)被樹上啟發(fā)式合并吊打,而且我也不知道為什么他會(huì)被分進(jìn)數(shù)據(jù)結(jié)構(gòu)里,但是不重要!

    點(diǎn)分治在樹上進(jìn)行分治,每次將樹分治成若干棵不同的子樹處理。

    實(shí)現(xiàn)方法

    一般用于處理樹上路徑長(zhǎng)度的問題

    這里舉LuoguLuoguLuogu的模板P3806P3806P3806

    一棵樹(無向),mmm個(gè)詢問求長(zhǎng)度為kkk的路徑存不存在。

    對(duì)于以ppp為根的子樹

    然后考慮路徑種類,其實(shí)只有兩種

  • 經(jīng)過根節(jié)點(diǎn)
  • 包含在某一棵子樹中
  • 對(duì)于1,我們將路徑分成兩半.

    求出根節(jié)點(diǎn)里每個(gè)點(diǎn)的距離did_idi?,和每個(gè)節(jié)點(diǎn)位于那個(gè)子樹bib_ibi?

    要求滿足bx≠byb_x\neq b_ybx??=by?dx+dy=kd_x+d_y=kdx?+dy?=k

    這時(shí)候定義函數(shù)Calc(p)Calc(p)Calc(p)表示以ppp為根的子樹上上述路徑的條數(shù)

    而路徑二就直接分治讓子樹再處理一遍就好了。

    現(xiàn)在是考慮Calc(p)Calc(p)Calc(p)如何定義的問題。

    因?yàn)閿?shù)據(jù)很小,用lonxlon_xlonx?表示是否存在長(zhǎng)度為xxx的路徑

    然后我們對(duì)于每顆子樹,先用之前的lonlonlon計(jì)算一下詢問

    之后在將這棵子樹加入lonlonlon數(shù)組

    然后在子樹之中繼續(xù)處理

    時(shí)間復(fù)雜度:O(TNlogN)O(TN\ log \ N)O(TN?log?N)TTT為樹的層數(shù)

    可是如果樹是一條鏈,那么時(shí)間就會(huì)退化為O(N2logN)O(N^2\ log\ N)O(N2?log?N)

    但是如果我們每次以子樹的重心作為新分治的根,那么最多只會(huì)有logNlog\ Nlog?N層。

    時(shí)間就可以穩(wěn)在O(Nlog2N)O(N\ log^2\ N)O(N?log2?N)


    Part 3:Treap

    概念

    TreapTreapTreap就是平衡樹的一種,所謂平衡樹就是要求穩(wěn)定在lognlog\ nlog?n層左右的BSTBSTBST,為什么要穩(wěn)定層數(shù),我們等會(huì)再講。

    實(shí)現(xiàn)方法

    TreapTreapTreap的實(shí)現(xiàn)方法之前要先將BSTBSTBST

    BSTBSTBST

    所謂BST,就是二叉查找樹。

    二叉查找樹有個(gè)優(yōu)秀的性質(zhì),那就是對(duì)于每個(gè)節(jié)點(diǎn),左子節(jié)點(diǎn)的權(quán)值比它小,右子節(jié)點(diǎn)比它大。

    這樣我們就可以愉快的干很多事,比如查找前驅(qū),后繼,排名啊

    明人不說暗話,先來將實(shí)現(xiàn)方法

    開始先構(gòu)建兩個(gè)節(jié)點(diǎn)?inf-inf?infinfinfinf,以任意一個(gè)為根

    int New(int new_val){val[++tot]=new_val;dat[tot]=rand();cnt[tot]=size[tot]=1;return tot;}void Build(){New(-INF);New(INF);root=1;r[1]=2;Updata(root);}

    1[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-I4PJVqvd-1576912899795)(https://i.loli.net/2019/01/20/5c446542aac73.png)]

    插入andandand刪除:

    插入一個(gè)數(shù)時(shí),先從根開始。對(duì)于走到的每一個(gè)點(diǎn),如果比它小就往左,不然就往右走。知道找到一個(gè)空余的位置就插入。

    void Insert(int &p,int num){if(p==0){p=New(num);return;}//如果有空位了if(num==val[p]){cnt[p]++;Updata(p);return;}//已經(jīng)有這個(gè)數(shù)了if(num<val[p]){Insert(l[p],num);}//小于往左else{Insert(r[p],num);}//大于往右Updata(p);//更新數(shù)據(jù)}

    刪除也是根據(jù)那個(gè)走法找到位置,然后刪除

    刪除時(shí)注意將子節(jié)點(diǎn)接上去

    void Remove(int val){int &p=root;while(p){if(val==a[p].val) break;p=val<a[p].val?a[p].l:a[p].r;}if(p==0) return;if(a[p].l==0){p=a[p].r}//沒左接右else if(a[p].r==0){p=a[p].l;}//沒右接左else{//求個(gè)后繼int next=a[p].r;while(a[next].l>0) next=a[next].l;//next沒有左子樹,直接刪除Romove(a[next].val);//令節(jié)點(diǎn)next代替節(jié)點(diǎn)p的位置a[next].l=a[p].r;a[next].r=a[p].r;p=next;} }

    求前驅(qū)/后繼

    valvalval后繼就是比valvalval大的最小的數(shù)。

    對(duì)于求后繼,我們可以用一步一步往下走的方法,并在路上更新答案ansansans,直到走到valvalval的位置或結(jié)束時(shí)。

    這時(shí)有3種結(jié)果

  • 沒有valvalval這個(gè)節(jié)點(diǎn)。那就直接取ansansans
  • valvalval,但沒有右子樹。也是直接取ansansans
  • 找到valvalval,且也有右子樹。走到右邊,然后一直往左走,就是valvalval的后繼
  • 前驅(qū)同理

    int GetPre(int num){int ans=1;int p=root;while(p){if(num==val[p]){//找到val節(jié)點(diǎn)if(l[p]>0){p=l[p];//往左一步while(r[p]>0) p=r[p];//一直往右ans=p;//統(tǒng)計(jì)}break;}if(val[p]<num&&val[p]>val[ans]) ans=p;p=num<val[p]?l[p]:r[p];//走}return val[ans];}int GetNext(int num){int ans=2;int p=root;while(p){if(num==val[p]){if(r[p]>0){p=r[p];while(l[p]>0) p=l[p];ans=p;}break;}if(val[p]>num&&val[p]<val[ans]) ans=p;p=num<val[p]?l[p]:r[p];}return val[ans];}

    查詢一個(gè)數(shù)的排名/一個(gè)排名的數(shù)

    一個(gè)數(shù)的排名就直接走到那個(gè)點(diǎn),順路根據(jù)樹的大小統(tǒng)計(jì)前面有多少個(gè)點(diǎn)就好了。

    一個(gè)排名的數(shù),就根據(jù)子樹大小來走就ok了。

    int GetRankByVal(int p,int num){if(p==0) return 0;if(num==val[p]) return size[l[p]]+1;if(num<val[p]) return GetRankByVal(l[p],num);return GetRankByVal(r[p],num)+size[l[p]]+cnt[p];}int GetValByRank(int p,int rank){if (p==0) return INF;if(size[l[p]]>=rank) return GetValByRank(l[p],rank);if(size[l[p]]+cnt[p]>=rank) return val[p];return GetValByRank(r[p],rank-size[l[p]]-cnt[p]);}

    這就是BSTBSTBST的全部了。我們來考慮它的弊端。

    每次時(shí)間就會(huì)為O(T)O(T)O(T)

    如果我以單調(diào)的插入數(shù),那么每次就會(huì)退化為O(N)O(N)O(N)

    那么我們就要讓這個(gè)BSTBSTBST平衡


    TreapTreapTreap

    TreapTreapTreap是其中一種讓BSTBSTBST保持在lognlog\ nlog?n層左右的一個(gè)數(shù)據(jù)結(jié)構(gòu)

    具體實(shí)現(xiàn)方法就是通過旋轉(zhuǎn),對(duì)于一個(gè)東西

    可以旋轉(zhuǎn)為
    [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-dTwPYOyz-1576912899796)(https://i.loli.net/2019/01/20/5c446ed9d8f17.png)]

    總之就是分為左旋和右旋

    void zig(int &p){int q=l[p];l[p]=r[q];r[q]=p;p=q;Updata(r[p]);Updata(p);}void zag(int &p){int q=r[p];r[p]=l[q];l[q]=p;p=q;Updata(l[p]);Updata(p);}

    只會(huì)我們對(duì)每個(gè)點(diǎn),我們定義一個(gè)特征值。然后我們通過旋轉(zhuǎn)讓特征值滿足二叉堆的性質(zhì)。

    只要讓特征值時(shí)隨機(jī)的,基本都可以維持在lognlog\ nlog?n層附近。

    好了,放codecodecode

    #include<cstdio> #include<algorithm> #define INF 2147483647/3 #define N 100010 using namespace std; int n,root; struct Treap_node{int l[N],r[N];int val[N],dat[N];int cnt[N],size[N];int tot;int New(int new_val){val[++tot]=new_val;dat[tot]=rand();cnt[tot]=size[tot]=1;return tot;}void Updata(int p){size[p]=size[l[p]]+size[r[p]]+cnt[p];}void Build(){New(-INF);New(INF);root=1;r[1]=2;Updata(root);}int GetRankByVal(int p,int num){if(p==0) return 0;if(num==val[p]) return size[l[p]]+1;if(num<val[p]) return GetRankByVal(l[p],num);return GetRankByVal(r[p],num)+size[l[p]]+cnt[p];}int GetValByRank(int p,int rank){if (p==0) return INF;if(size[l[p]]>=rank) return GetValByRank(l[p],rank);if(size[l[p]]+cnt[p]>=rank) return val[p];return GetValByRank(r[p],rank-size[l[p]]-cnt[p]);}void zig(int &p){int q=l[p];l[p]=r[q];r[q]=p;p=q;Updata(r[p]);Updata(p);}void zag(int &p){int q=r[p];r[p]=l[q];l[q]=p;p=q;Updata(l[p]);Updata(p);}void Insert(int &p,int num){if(p==0){p=New(num);return;}if(num==val[p]){cnt[p]++;Updata(p);return;}if(num<val[p]){Insert(l[p],num);if(dat[p]<dat[l[p]]) zig(p);}else{Insert(r[p],num);if(dat[p]<dat[r[p]]) zag(p);}Updata(p);}int GetPre(int num){int ans=1;int p=root;while(p){if(num==val[p]){if(l[p]>0){p=l[p];while(r[p]>0) p=r[p];ans=p;}break;}if(val[p]<num&&val[p]>val[ans]) ans=p;p=num<val[p]?l[p]:r[p];}return val[ans];}int GetNext(int num){int ans=2;int p=root;while(p){if(num==val[p]){if(r[p]>0){p=r[p];while(l[p]>0) p=l[p];ans=p;}break;}if(val[p]>num&&val[p]<val[ans]) ans=p;p=num<val[p]?l[p]:r[p];}return val[ans];}void Remove(int &p,int num){if(p==0) return;if(num==val[p]){if(cnt[p]>1){cnt[p]--;Updata(p);return;}if(l[p]||r[p]){if(r[p]==0||dat[l[p]]>dat[r[p]])zig(p),Remove(r[p],num);elsezag(p),Remove(l[p],num);Updata(p);}else p=0;return;}num<val[p]?Remove(l[p],num):Remove(r[p],num);Updata(p);} }a; int main() {a.Build();scanf("%d",&n);while(n--){int opt,x;scanf("%d%d",&opt,&x);switch(opt){case 1:a.Insert(root,x);break;case 2:a.Remove(root,x);break;case 3:printf("%d\n",a.GetRankByVal(root,x)-1);break;case 4:printf("%d\n",a.GetValByRank(root,x+1));break;case 5:printf("%d\n",a.GetPre(x));break;case 6:printf("%d\n",a.GetNext(x));break;}} }

    雖然基本都可以用setsetset搞定,但是還是可以做很多NBNBNB的好東西的。


    Part4:樹鏈剖分

    概念

    將樹分割成若干條鏈來處理,將樹上的問題轉(zhuǎn)換為線性問題

    實(shí)現(xiàn)方法

    實(shí)現(xiàn)概念

    學(xué)樹鏈剖分之前我們先來getgetget一些前置知識(shí)

    重兒子:子樹最大的一個(gè)兒子

    輕兒子:其他兒子

    重邊:連接重兒子的邊

    輕邊:其他邊

    重路徑:重邊的路徑

    還有一個(gè)重要性質(zhì):從根到任何一個(gè)點(diǎn)的輕邊數(shù)不超過lognlog\ nlog?n

    證明:

    若走到一條輕邊,那么子樹大小至少縮小一半,那么最多走log?n\log nlogn條輕邊子樹大小就會(huì)縮小為1

    證畢

    所以我們對(duì)于一條路徑的輕邊數(shù)量也是logloglog級(jí)的。

    所以我們可以對(duì)于重路徑將樹進(jìn)行劃分。

    我們來看例題:

    要求支持子樹修改,子樹求和,路徑修改,路徑求和。

    對(duì)于子樹操作很簡(jiǎn)單,我們按照dfsdfsdfs序加入線段樹,然后線段樹區(qū)間修改就可以做到子樹修改,可以路徑問題如何解決?

    這時(shí)候我們就可以用樹鏈剖分了。我們考慮將dfsdfsdfs修改一下。

    我們可以每次優(yōu)先走重兒子,那么重路徑在線段樹上的位置就是連續(xù)的,對(duì)于一段重路徑我們就可以進(jìn)行區(qū)間修改了,而輕邊有不會(huì)太多,所以一次操作時(shí)間復(fù)雜度是O(log2n)O(log^2\ n)O(log2?n)

    代碼實(shí)現(xiàn)

    我們先跑兩次dfsdfsdfs預(yù)處理

    第一遍:

    sonx:xson_x:xsonx?:x的重兒子

    depx:xdep_x:xdepx?:x的深度

    sizx:x?siz_x:x?sizx?:x?的子樹大小

    fx:xf_x:xfx?:x的父節(jié)點(diǎn)

    第二遍:

    segx:x?seg_x:x?segx?:x?在線段樹上的位置

    idz:id_z:idz?:線段樹zzz位置上的節(jié)點(diǎn)

    topx:xtop_x:xtopx?:x所在重路徑最淺的節(jié)點(diǎn)

    之后我們就預(yù)處理完了。

    之后我們對(duì)于路徑操作可以向倍增求LCALCALCA一樣,將一條路徑拆分為兩條路徑,不過變?yōu)槊看翁?span id="ozvdkddzhkzd" class="katex--inline">topxtop_xtopx?,然后跳的過程重進(jìn)行用線段樹進(jìn)行區(qū)間修改。

    codecodecode

    #include<cstdio> #include<algorithm> using namespace std; const int N=200000; int tot,cnt,n,m,s,p,ls[N],pw[N],id[N]; int siz[N],dep[N],f[N],son[N],seg[N],top[N]; struct treenode{int l,r,lazy,val; }; struct LineTree{treenode t[N*2];void build(int x,int l,int r){t[x].l=l;t[x].r=r;if(l==r){t[x].val=pw[id[l]]%p;return;}int mid=(l+r)>>1;build(x*2,l,mid);build(x*2+1,mid+1,r);t[x].val=(t[x*2].val+t[x*2+1].val)%p;}void downdata(int x){if(t[x].lazy){(t[x*2].lazy+=t[x].lazy)%=p;(t[x*2].val+=t[x].lazy*(t[x*2].r-t[x*2].l+1))%=p;(t[x*2+1].lazy+=t[x].lazy)%=p;(t[x*2+1].val+=t[x].lazy*(t[x*2+1].r-t[x*2+1].l+1))%=p;t[x].lazy=0;}}void change(int x,int l,int r,int num){if(t[x].l==l&&t[x].r==r){(t[x].val+=num*(t[x].r-t[x].l+1))%=p;(t[x].lazy+=num)%=p;return;}downdata(x);int mid=(t[x].l+t[x].r)>>1;if(r<=mid) change(x*2,l,r,num);else if(l>mid) change(x*2+1,l,r,num);else change(x*2,l,mid,num),change(x*2+1,mid+1,r,num);t[x].val=(t[x*2].val+t[x*2+1].val)%p;}int find(int x,int l,int r){if(t[x].l==l&&t[x].r==r)return t[x].val;downdata(x);int mid=(t[x].l+t[x].r)>>1;if(r<=mid) return find(x*2,l,r);else if(l>mid) return find(x*2+1,l,r);else return (find(x*2,l,mid)+find(x*2+1,mid+1,r))%p; } }LT;//線段樹 struct edge_node{int to,next; }a[N*2]; void addl(int x,int y) {a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot; } void dfs1(int x,int fa)//第一遍預(yù)處理 {siz[x]=1;for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(y==fa) continue;dep[y]=dep[x]+1;f[y]=x;dfs1(y,x);siz[x]+=siz[y];if(siz[y]>siz[son[x]])son[x]=y;} } void dfs2(int x,int fa)//第二遍預(yù)處理 {seg[x]=++cnt;id[cnt]=x;if(son[x]){top[son[x]]=top[x];dfs2(son[x],x);}for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(y==fa||y==son[x]) continue;top[y]=y;dfs2(y,x);} } void path_change(int x,int y,int z)//路徑修改 {while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]]) swap(x,y);//哪個(gè)深度大跳哪個(gè)LT.change(1,seg[top[x]],seg[x],z);//路徑修改x=f[top[x]];//跳}if(dep[x]>dep[y]) swap(x,y);LT.change(1,seg[x],seg[y],z);return; } int path_ask(int x,int y)//路徑查詢—同上 {int ans=0;while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]]) swap(x,y);(ans+=LT.find(1,seg[top[x]],seg[x]))%=p;x=f[top[x]];}if(dep[x]>dep[y]) swap(x,y);(ans+=LT.find(1,seg[x],seg[y]))%=p;return ans; } int main() {scanf("%d%d%d%d",&n,&m,&s,&p);for(int i=1;i<=n;i++)scanf("%d",&pw[i]);for(int i=1;i<n;i++){int x,y;scanf("%d%d",&x,&y);addl(x,y);addl(y,x);}dfs1(s,0);top[s]=s;dfs2(s,0);LT.build(1,1,n);for(int i=1;i<=m;i++){int t,x,y,z;scanf("%d%d",&t,&x);if(t==1){scanf("%d%d",&y,&z);path_change(x,y,z);}if(t==2){scanf("%d",&y);printf("%d\n",path_ask(x,y));}if(t==3){scanf("%d",&z);LT.change(1,seg[x],seg[x]+siz[x]-1,z);}if(t==4){printf("%d\n",LT.find(1,seg[x],seg[x]+siz[x]-1));}} }

    Part 5:練習(xí)題

    luogu3870?[TJOI2009]luogu3870-[TJOI2009]luogu3870?[TJOI2009]開關(guān)【分塊】

    luogu4008?[NOI2003]luogu4008-[NOI2003]luogu4008?[NOI2003]文本編輯器【分塊】

    luogu2634?[luogu2634-[luogu2634?[國(guó)家集訓(xùn)隊(duì)]]]聰聰可可【點(diǎn)分治】

    luogu4178?Treeluogu4178-Treeluogu4178?Tree【點(diǎn)分治】

    luogu3714?[BJOI2017]luogu3714-[BJOI2017]luogu3714?[BJOI2017]樹的難題【點(diǎn)分治】

    luogu3345?[ZJOI2015]?luogu3345-[ZJOI2015]?luogu3345?[ZJOI2015]?幻想鄉(xiāng)戰(zhàn)略游戲【點(diǎn)分治】

    luogu1503??luogu1503-?luogu1503??鬼子進(jìn)村【Treap?Treap?Treap?

    luogu1533?luogu1533-luogu1533?可憐的狗狗【Treap?Treap?Treap?

    luogu3313?[SDOI2014]luogu3313-[SDOI2014]luogu3313?[SDOI2014]旅行【主席樹,,,樹鏈剖分】

    luogu2486?[SDOI2011]?luogu2486-[SDOI2011]?luogu2486?[SDOI2011]?染色【樹鏈剖分】

    luogu2146?[NOI2015]luogu2146-[NOI2015]luogu2146?[NOI2015]軟件包管理器【樹鏈剖分】

    總結(jié)

    以上是生活随笔為你收集整理的上古时期(大雾)的数据结构pdf的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。