大根堆——線段樹合併
阿新 • • 發佈:2022-04-19
這個題在機房磨磨唧唧的做了兩週,總共六種方法,弄會兩種(且對於我目前而言最有價值的兩種)
真是挺不容易的,只能說cd大佬太強了,他那一句int res=t[rt].cn真的帥到我了
這個題一點定要自己先去想出純暴力DP再去想線段樹合併優化(或者可以get到啟發式合併的樹上LIS也行)
如果沒有自己思考的DP過程,就直接去看網上的線段樹合併題解,這個題就沒有意義了
純暴力DP(60分)
//大根堆 #include <cstdio> #include <iostream> #include <cstring> #include <algorithm> usingView Codenamespace std; const int mx=200000+1000; int n,len,dr; int v[mx],drt[mx],head[mx],fa[mx]; int f[1010][1010]; bool vis[mx]; struct Node{ int from; int to; int next; }e[mx]; void Insert(int u,int v){ e[++len].from=u; e[len].to=v; e[len].next=head[u]; head[u]=len; } void dfs(int u){for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){ vis[v]=1; dfs(v); for(int j=1;j<=n;++j){ f[u][j]+=f[v][j]; } //然後後面還要處理j>v[u] } } for(int j=n;j>=v[u];--j){ f[u][j]=max(f[u][j],f[u][v[u]-1]+1); //因為先下的葉子,這裡自然而然的就做到了初始化 } } void Solve(){ scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d%d",&v[i],&fa[i]); Insert(fa[i],i); //這種明確指明誰就是誰的爹倒是可以只建一條 drt[i]=v[i]; } sort(drt+1,drt+n+1); dr=unique(drt+1,drt+dr+1)-drt-1; for(int i=1;i<=n;++i){ v[i]=lower_bound(drt+1,drt+n+1,v[i])-drt; } vis[1]=1;dfs(1); int ans=0; for(int i=1;i<=n;++i){ ans=max(ans,f[1][i]); } //先交一下驗證思路 printf("%d\n",ans); } int main(){ Solve(); return 0; }
線段樹合併純淨版(標記永久化(我認為最有價值,最帥的方法))
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define ls(x) t[x].lson #define rs(x) t[x].rson using namespace std; const int mx=200000+1000; int n,len,dr,tot; int va[mx],drt[mx],head[mx],fa[mx]; int f[1010][1010]; bool vis[mx]; int root[mx]; struct Node{ int from; int to; int next; }e[mx]; struct Nodee{ int lson; int rson; int cn; int lazy; }t[mx*10]; void Insert(int u,int v){ e[++len].from=u; e[len].to=v; e[len].next=head[u]; head[u]=len; } void Pushup(int rt){ t[rt].cn=(t[ls(rt)].cn+t[rs(rt)].cn); } void Pushdown(int rt){ int la=t[rt].lazy; if(ls(rt)){ t[ls(rt)].cn+=la; t[ls(rt)].lazy+=la; } if(rs(rt)){ t[rs(rt)].cn+=la; t[rs(rt)].lazy+=la; } t[rt].lazy=0; } int merge(int r1,int r2,int l,int r,int r2max,int r1max){ r2max=max(r2max,t[r2].cn); r1max=max(r1max,t[r1].cn); if(!r1 || !r2){ if(r1){ t[r1].cn+=r2max; t[r1].lazy+=r2max; return r1; } if(r2){ t[r2].cn+=r1max; t[r2].lazy+=r1max; return r2; } return r1; } t[r1].cn=r1max+r2max; int mid=(l+r)>>1; Pushdown(r1); Pushdown(r2); ls(r1)=merge(ls(r1),ls(r2),l,mid,r2max,r1max); rs(r1)=merge(rs(r1),rs(r2),mid+1,r,r2max,r1max); return r1; } int query(int rt,int l,int r,int x){ if(!rt)return 0; if(l==r){ return t[rt].cn; } int res=t[rt].cn; Pushdown(rt); int mid=(l+r)>>1; if(x<=mid)res=max(res,query(ls(rt),l,mid,x)); else res=max(res,query(rs(rt),mid+1,r,x)); return res; } void Build(int &rt,int l,int r,int L,int R,int w){ if(!rt)rt=++tot; if(L<=l && R>=r){ t[rt].cn=max(t[rt].cn,w); return ; } Pushdown(rt); int mid=(l+r)>>1; if(L<=mid)Build(ls(rt),l,mid,L,R,w); if(R>mid) Build(rs(rt),mid+1,r,L,R,w); } void dfs(int u){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){ vis[v]=1; dfs(v); root[u]=merge(root[u],root[v],1,dr,0,0); } } int an=query(root[u],1,dr,va[u]-1)+1; Build(root[u],1,dr,va[u],dr,an); } void Solve(){ scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d%d",&va[i],&fa[i]); if(fa[i]==0)continue; Insert(fa[i],i); drt[i]=va[i]; } sort(drt+1,drt+n+1); dr=unique(drt+1,drt+n+1)-drt-1; for(int i=1;i<=n;++i){ va[i]=lower_bound(drt+1,drt+dr+1,va[i])-drt; } dfs(1);int ans=0; ans=query(root[1],1,dr,dr); printf("%d\n",ans); } int main(){ Solve(); return 0; }View Code
幾個廢話版,記錄心情
//大根堆 //它最後要一個最多的點數,樣例是5 //這裡注意,通過觀察樣例得知,你可以把它當成無向的 //你不是從已知樹裡找樹,而是樹中抽數出來構建樹 //1.樹上差分加線段樹合併 //2.啟發式合併 //樹上差分:一個點的真實權值是一個點子樹內所有差分後的權值之和 //ok,為了做這道題,我去A了一道樹上差分,還順帶複習了一遍lca //首先便是DP 以當前結點為根的一顆子樹,它所能選出的一個大根堆 我們可以去迴圈權值 // f[u][i] 以u為根,點的權值不大於i,所能找出的一個ans,根據題目的要求,其實每個點都可以做總根 //不對,這裡其實沒有總根這個概念,看你怎麼理解,你把 //其實就是用合併來模擬那個dp,然後特殊化一下就好了,這就是我的理解 //即: f[u][i] =max( sum f[v][i](這是不選) , sum f[v][i-1]+1(這是選上當前根結點) ) //然後就是樣例那種,你得去看怎麼處理i 大於u的權值的情況 //我們的 sum 就是可以用線段樹合併把它的子樹合併上來再查詢 //這樣,你先寫純暴力DP #include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define ls(x) t[x].lson #define rs(x) t[x].rson using namespace std; const int mx=200000+1000; int n,len,dr,tot; int va[mx],drt[mx],head[mx],fa[mx]; int f[1010][1010]; bool vis[mx]; int root[mx]; struct Node{ int from; int to; int next; }e[mx]; struct Nodee{ int lson; int rson; int cn; int lazy; }t[mx*10]; inline int read(){ int w=1; int x=0; char ch=getchar(); while(isdigit(ch)==false){ if(ch=='-')w=-1; ch=getchar(); } while(isdigit(ch)==true){ x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); } return x*w; } void Insert(int u,int v){ e[++len].from=u; e[len].to=v; e[len].next=head[u]; head[u]=len; } /*void dfs(int u){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){//這麼寫主要是防止資料有重邊 vis[v]=1; dfs(v); for(int j=1;j<=n;++j){ f[u][j]+=f[v][j]; } //然後後面還要處理j>va[u] } } for(int j=n;j>=va[u];--j){ f[u][j]=max(f[u][j],f[u][va[u]]+1); //因為先下的葉子,這裡自然而然的就做到了我想要的初始化 } //現在我們要做的就是用線段樹合併去優化這個n*n暴力DP //這個DP主要兩個操作,區間和以及區間max,區間和可以用合併並上去,區間max //首先你要明確,線段樹的本職工作就是區間max //重新來:區間和的操作,如果單純開出點來去查,空間炸,所以必然是要一層層合併上去 //那麼合併上去,還要有一個區間max,來與當前再次比較一下 //那麼好,區間max如果每次都不設lazy,直接更新下去,或者說 //還是,我先寫出不考慮lazy的情況,再說解決 //解決方法有三種:二分,差分,lazy永久化 }*/ void Pushup(int rt){ t[rt].cn=(t[ls(rt)].cn+t[rs(rt)].cn); } void Pushdown(int rt){ int la=t[rt].lazy; if(ls(rt)){ t[ls(rt)].cn+=la; t[ls(rt)].lazy+=la; } if(rs(rt)){ t[rs(rt)].cn+=la; t[rs(rt)].lazy+=la; } t[rt].lazy=0; } int merge(int r1,int r2,int l,int r,int r2max,int r1max){ // printf("r1=%d r2=%d\n",r1,r2); r2max=max(r2max,t[r2].cn); r1max=max(r1max,t[r1].cn); // printf("r1m=%d r2m=%d t[r1=%d].cn=%d t[r2=%d].cn=%d\n",r1max,r2max,r1,t[r1].cn,r2,t[r2].cn); //又是零出問題了,哪裡的零? if(r1==0 && r2==0) return r1;//這裡,因為兩個都零,會進r1的操作,但此刻我們會有值加上 //所以不對 或者可以這麼寫: /*if(!rt || !p){ if(rt) update(rt, radd);//這裡相當與單點merge的遞迴到了葉子,這裡是遞迴到了區間, if(p) update(p, ladd), rt = p;//加上另外的子樹的影響 //不知道y這裡有沒有用 return; } */ if(!r1){ t[r2].cn+=r1max; t[r2].lazy+=r1max; return r2; } if(!r2){ //這裡是遞迴到了葉子結點 //此刻旁邊有一個子樹,而需要把這個葉子節點的值加到這個子樹上,而之前顯然是 //沒有去加,它直接就跳出了,那麼是直接加?加什麼? //你再次迴歸定義,我這裡存的是max,而不是sum,所以說,所以說 //如果r2不是純葉子,它遞迴到這裡我需要給r1加上此刻r2的max! //那麼關於lazy的問題我也大概想通了,還是,想,此刻的r2結束了,但r1沒有結束 //而線段樹本身就是點代表區間,給這個區間加上了r2max,下面的細分也因加上 //但在此刻這裡顯然是不能加上,所以要用lazy //那上面也是同理 t[r1].cn+=r2max; t[r1].lazy+=r2max; return r1; } int mid=(l+r)>>1; Pushdown(r1); Pushdown(r2);//把雙方的lazy各自傳下去 // t[r1].cn+=t[r2].cn;//合併應該就是這麼莽著加就行了 //其它地方就是這麼加沒錯,不不不,這裡應該是此時此刻的雙max,這樣才完美符合的我的定義 //在上還是在下?回憶普通線段樹,在下! ls(r1)=merge(ls(r1),ls(r2),l,mid,r2max,r1max); rs(r1)=merge(rs(r1),rs(r2),mid+1,r,r2max,r1max); t[r1].cn=r1max+r2max; // printf("t[rt=%d].cn=%d\n",r1,t[r1].cn); return r1;//這裡千萬不要忘 } int query(int rt,int l,int r,int x){ if(!rt)return 0; if(l==r){ return t[rt].cn; } int res=0; res=t[rt].cn; // printf("res=%d\n",res); Pushdown(rt); int mid=(l+r)>>1; //現在就是,我怎麼保證查出去是最優解,是max //我們存,就是存的max! if(x<=mid)res=max(res,query(ls(rt),l,mid,x)); else res=max(res,query(rs(rt),mid+1,r,x)); return res; } void Build(int &rt,int l,int r,int L,int R,int w){ //你就把那個圖看成區間 if(!rt)rt=++tot; if(L<=l && R>=r){//在j>=va[u]內,可以選根點 t[rt].cn=max(t[rt].cn,w);//去用以根點為根比較更新一下最大值 return ; } Pushdown(rt); int mid=(l+r)>>1; if(L<=mid)Build(ls(rt),l,mid,L,R,w); if(R>mid) Build(rs(rt),mid+1,r,L,R,w); } void dfs(int u,int faa){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; //我也不知道現在建立這個東西,其實也可以叫權值線段樹 //只不過它儲存的是它的最優解 // if(vis[v]==0){ // printf("u=%d v=%d\n",u,v); if(v==faa)continue; /// vis[v]=1; dfs(v,u); root[u]=merge(root[u],root[v],1,dr,0,0); //先合併,就是說,如果這是個葉子結點,那叫無所謂 //如果不是葉子,就是那個每次+=的操作,其實就是一遍遍通過兒子 //把這個點的樹合併起來了,那Build好像就沒有必要le? //然後去外面建立這個點的線段樹 //外面主要是建立葉子的線段樹好讓它去合併 // } } // printf("u=%d\n",u); //sum也沒有處理完,我們也不能只是簡單的pushup //怎麼辦 int an=query(root[u],1,dr,va[u]-1)+1;//查詢那個max // printf("an=%d va[u]-1=%d\n",an,va[u]-1); //即:不選這個結點的最大ans //也不是不選,是u是頂層的max //好的現在壓力給到build了,我的merge只是簡單的把葉子去相加合併,並未進行Pushup //query直接跳躍空查max //現在就差用build把他們串聯起來,我就A了 Build(root[u],1,dr,va[u],dr,an);//判不判定是葉子再說,後面? //還是感覺有問題,完全沒有處理merge,也沒有感覺出它們所說的卡lazy //很顯然就是merge這裡有問題 } void Solve(){ scanf("%d",&n); for(int i=1;i<=n;++i){ int vv,pp; va[i]=read(); fa[i]=read(); if(fa[i]==0)continue; Insert(fa[i],i); Insert(i,fa[i]); //這種明確指明誰就是誰的爹應該可以只建一條 //而樹上差分那個板子題沒有指明誰是祖先,所以要雙向再vis,人為定邊 //不對,這個題因為我出來還有一些操作,讓我看看,沒影響 //首先如果建雙向邊要排除零 drt[i]=va[i]; } sort(drt+1,drt+n+1); dr=unique(drt+1,drt+n+1)-drt-1; for(int i=1;i<=n;++i){ va[i]=lower_bound(drt+1,drt+n+1,va[i])-drt; } //dp初始化,每個葉子節點 f[u][>=val[u]]=1; //不好初始化那就先寫 vis[1]=1; dfs(1,0); int ans=0; /* for(int i=1;i<=n;++i){ ans=max(ans,f[1][i]); }*/ //先交一下驗證思路,50分 有一個點沒A,不管了 ans=query(root[1],1,dr,dr); printf("%d\n",ans); } int main(){ Solve(); return 0; }View Code
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define ls(x) t[x].lson #define rs(x) t[x].rson using namespace std; const int mx=2000000+1000; int n,len,dr,tot; int va[mx],drt[mx],head[mx],fa[mx]; int f[1010][1010]; bool vis[mx]; int root[mx]; struct Node{ int from; int to; int next; }e[mx]; struct Nodee{ int lson; int rson; int cn; int lazy; }t[mx*10]; void Insert(int u,int v){ e[++len].from=u; e[len].to=v; e[len].next=head[u]; head[u]=len; } void Pushup(int rt){ t[rt].cn=(t[ls(rt)].cn+t[rs(rt)].cn); } void Pushdown(int rt){ int la=t[rt].lazy; if(ls(rt)){ t[ls(rt)].cn+=la; t[ls(rt)].lazy+=la; } if(rs(rt)){ t[rs(rt)].cn+=la; t[rs(rt)].lazy+=la; } t[rt].lazy=0; } int merge(int r1,int r2,int l,int r,int r2max,int r1max){ r2max=max(r2max,t[r2].cn); r1max=max(r1max,t[r1].cn); if(!r1 || !r2){ if(r1){ t[r1].cn+=r2max; t[r1].lazy+=r2max; return r1; } if(r2){ t[r2].cn+=r1max; t[r2].lazy+=r1max; return r2; } return r1; } int mid=(l+r)>>1; Pushdown(r1); Pushdown(r2); ls(r1)=merge(ls(r1),ls(r2),l,mid,r2max,r1max); rs(r1)=merge(rs(r1),rs(r2),mid+1,r,r2max,r1max); t[r1].cn=r1max+r2max; return r1; } int query(int rt,int l,int r,int x){ if(!rt)return 0; if(l==r){ return t[rt].cn; } int res=t[rt].cn; Pushdown(rt); int mid=(l+r)>>1; if(x<=mid)res=max(res,query(ls(rt),l,mid,x)); else res=max(res,query(rs(rt),mid+1,r,x)); return res; } void Build(int &rt,int l,int r,int L,int R,int w){ if(!rt)rt=++tot; if(L<=l && R>=r){ t[rt].cn=max(t[rt].cn,w); return ; } Pushdown(rt); int mid=(l+r)>>1; if(L<=mid)Build(ls(rt),l,mid,L,R,w); if(R>mid) Build(rs(rt),mid+1,r,L,R,w); } /*void dfs(int u){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){ vis[v]=1; dfs(v); for(int j=1;j<=n;++j){ f[u][j]+=f[v][j]; } //然後後面還要處理j>v[u] } } for(int j=n;j>=v[u];--j){ f[u][j]=max(f[u][j],f[u][v[u]-1]+1); //因為先下的葉子,這裡自然而然的就做到了我想要的初始化 } }*/ void dfs(int u){ Build(root[u],1,dr,va[u],dr,0); for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){ vis[v]=1; dfs(v); //這個題還剩最後一點遺留問題 //首先我們有一個純暴力DP //f[u][i]表示以u為根,最大值小於等於i 在這個樹上構成一個樹形lis的最佳方案個數 //當i大於u結點的權值的時候,u就成為中轉點了 //設u結點的權值為w //然後我們仍然易得,當i>=w時(注意這裡有等於,因為本身也要給它自己加上) //需要去比較一下 f[u][i] f[u][w-1]+1 //f[u][i] =max( sum f[v][i](這是不選) , sum f[v][i-1]+1(這是選上當前根結點) //f[u][i]的來源是u的子樹的f[v][i]的和,因為這是一個樹形lis //之後的所有方法本質都是去優化這個DP //只有理解了這些,才能真正理解為什麼權值線段樹中去維護方案個數而不是結點權值存在個數 // 剩下的明天再說 ,乾飯去了 // 然後,那個sum操作,我們是用了合併來進行累加,先遞迴深搜到這個葉子節點,把 //把它與父親節點進行層層合併,合併就是max累加,因為我們要的就是max,所以要記錄max去從下正好更新了上面 //再有就是,先合併,合併完了之後,出來迴圈在對這個點進行建立(或者就是建立)線段樹 //外面這個操作意義就在於,如果是個葉子,正好建起來,後面去用來合併,如果不是葉子,那就已經被合併出樹了 //就處理一下i>=w的問題 如果先建樹再合併,一是後面還要等到合併完再去處理一遍i>=w; //二是照我的理解 ,感覺好像也沒有什麼問題,就是你建了個寂寞,我試試 , root[u]=merge(root[u],root[v],1,dr,0,0); } } int an=query(root[u],1,dr,va[u]-1)+1; Build(root[u],1,dr,va[u],dr,an); } void Solve(){ scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d%d",&va[i],&fa[i]); if(fa[i]==0)continue; Insert(fa[i],i); drt[i]=va[i]; } sort(drt+1,drt+n+1); dr=unique(drt+1,drt+n+1)-drt-1; for(int i=1;i<=n;++i){ va[i]=lower_bound(drt+1,drt+dr+1,va[i])-drt; //如果真的是這裡出了問題,我就從機房跳下去 //我現在的心情已經沒有辦法表述了 //我從上上週六調到現在,交了20多次 //最後告訴我,離散化細節出問題了。。。。。。。。。。。。。 } dfs(1); int ans=0; ans=query(root[1],1,dr,dr); printf("%d\n",ans); } int main(){ Solve(); return 0; }View Code
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define ls(x) t[x].lson #define rs(x) t[x].rson using namespace std; const int mx=200000+1000; int n,len,dr,tot; int va[mx],drt[mx],head[mx],fa[mx]; int f[1010][1010]; bool vis[mx]; int root[mx]; struct Node{ int from; int to; int next; }e[mx]; struct Nodee{ int lson; int rson; int cn; int lazy; }t[mx*10]; void Insert(int u,int v){ e[++len].from=u; e[len].to=v; e[len].next=head[u]; head[u]=len; } void Pushup(int rt){ t[rt].cn=(t[ls(rt)].cn+t[rs(rt)].cn); } void Pushdown(int rt){ int la=t[rt].lazy; if(ls(rt)){ t[ls(rt)].cn+=la; t[ls(rt)].lazy+=la; } if(rs(rt)){ t[rs(rt)].cn+=la; t[rs(rt)].lazy+=la; } t[rt].lazy=0; } int merge(int r1,int r2,int l,int r,int r2max,int r1max){ r2max=max(r2max,t[r2].cn); r1max=max(r1max,t[r1].cn); // printf("r1=%d r2=%d l=%d r=%d r1max=%d r2max=%d\n",r1,r2,l,r,r1max,r2max); //權值線段樹它就是我們的DP定義,1節點代表1到9的允許j值,所以它的cn是零,2是1的兒子,它代表6到9的允許j值 //所以它可以是1,所以我們不能在Build時Pushup,所以我們要在merge時(就是DP的累加時)有一個就加一個 //注意r1max,r2max可都時現在的cn,這裡仍然沒有任何pushup操作 //對 ,這就是,dp轉權值線段樹的普遍思考 if(!r1 || !r2){ if(r1){ //如果不用lazy,這裡再下去跑,把r1的子樹都加上r2的值也確實挺麻煩的 t[r1].cn+=r2max; t[r1].lazy+=r2max; return r1; } if(r2){ t[r2].cn+=r1max; t[r2].lazy+=r1max; return r2; } return r1; } t[r1].cn=r1max+r2max; int mid=(l+r)>>1; Pushdown(r1); Pushdown(r2); ls(r1)=merge(ls(r1),ls(r2),l,mid,r2max,r1max); rs(r1)=merge(rs(r1),rs(r2),mid+1,r,r2max,r1max); // printf("t[r1=%d]=%d\n",r1,t[r1].cn); return r1; } int query(int rt,int l,int r,int x){ if(!rt)return 0; if(l==r){ return t[rt].cn; } int res=t[rt].cn; Pushdown(rt); int mid=(l+r)>>1; if(x<=mid)res=max(res,query(ls(rt),l,mid,x)); else res=max(res,query(rs(rt),mid+1,r,x)); return res; } void Build(int &rt,int l,int r,int L,int R,int w){ //printf("rt=%d l=%d r=%d L=%d R=%d w=%d\n",rt,l,r,L,R,w); if(!rt){ rt=++tot; // printf("rt=%d l=%d r=%d L=%d R=%d w=%d\n",rt,l,r,L,R,w); } if(L<=l && R>=r){ //這裡這麼做因為這裡不僅僅是建,更是合併後的更新 //在建立葉子節點時,R>=這一步會使>=va[a] 的區間都被進行這裡的操作符合定義 t[rt].cn=max(t[rt].cn,w); return ; } Pushdown(rt); int mid=(l+r)>>1; if(L<=mid)Build(ls(rt),l,mid,L,R,w); if(R>mid) Build(rs(rt),mid+1,r,L,R,w); } void dfs(int u){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){ // printf("u=%d v=%d\n",u,v); vis[v]=1; dfs(v); root[u]=merge(root[u],root[v],1,dr,0,0); // printf("root[%d]=%d\n",u,root[u]); } } int an=query(root[u],1,dr,va[u]-1)+1; Build(root[u],1,dr,va[u],dr,an); // printf("an=%d\n",an); // printf("root[%d]=%d va[%d]=%d dr=%d an=%d\n",u,root[u],u,va[u],dr,an); } void Solve(){ scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d%d",&va[i],&fa[i]); if(fa[i]==0)continue; Insert(fa[i],i); drt[i]=va[i]; } sort(drt+1,drt+n+1); dr=unique(drt+1,drt+n+1)-drt-1; for(int i=1;i<=n;++i){ va[i]=lower_bound(drt+1,drt+dr+1,va[i])-drt; } dfs(1); int ans=0; ans=query(root[1],1,dr,dr); printf("%d\n",ans); } int main(){ Solve(); return 0; }View Code
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define ls(x) t[x].lson #define rs(x) t[x].rson using namespace std; const int mx=200000+1000; int n,len,dr,tot; int va[mx],drt[mx],head[mx],fa[mx]; int f[1010][1010]; bool vis[mx]; int root[mx]; struct Node{ int from; int to; int next; }e[mx]; struct Nodee{ int lson; int rson; int cn; int lazy; }t[mx*10]; void Insert(int u,int v){ e[++len].from=u; e[len].to=v; e[len].next=head[u]; head[u]=len; } void Pushup(int rt){ t[rt].cn=(t[ls(rt)].cn+t[rs(rt)].cn); } void Pushdown(int rt){ int la=t[rt].lazy; if(ls(rt)){ t[ls(rt)].cn+=la; t[ls(rt)].lazy+=la; } if(rs(rt)){ t[rs(rt)].cn+=la; t[rs(rt)].lazy+=la; } t[rt].lazy=0; } int merge(int r1,int r2,int l,int r,int r2max,int r1max){ r2max=max(r2max,t[r2].cn); r1max=max(r1max,t[r1].cn); // printf("r1=%d r2=%d l=%d r=%d r1max=%d r2max=%d\n",r1,r2,l,r,r1max,r2max); //權值線段樹它就是我們的DP定義,1節點代表1到9的允許j值,所以它的cn是零,2是1的兒子,它代表6到9的允許j值 //所以它可以是1,所以我們不能在Build時Pushup,所以我們要在merge時(就是DP的累加時)有一個就加一個 //注意r1max,r2max可都時現在的cn,這裡仍然沒有任何pushup操作 //反而會將max往下傳,進行pushdwon,回顧定義以及儲存你會發現這樣合情合理 //最後肯定是要全部更新出一輪max,1-9的限制絕對是被限制在1,所以越往下越大,max往下傳就行 //對 ,這就是,dp轉權值線段樹的普遍思考 if(!r1 || !r2){ if(r1){ //如果不用lazy,這裡再下去跑,把r1的子樹都加上r2的值也確實挺麻煩的 t[r1].cn+=r2max; t[r1].lazy+=r2max; return r1; } if(r2){ t[r2].cn+=r1max; t[r2].lazy+=r1max; return r2; } return r1; } t[r1].cn=r1max+r2max; int mid=(l+r)>>1; Pushdown(r1); Pushdown(r2); ls(r1)=merge(ls(r1),ls(r2),l,mid,r2max,r1max); rs(r1)=merge(rs(r1),rs(r2),mid+1,r,r2max,r1max); return r1; } int query(int rt,int l,int r,int x){ if(!rt)return 0; if(l==r){ return t[rt].cn; } int res=t[rt].cn; //我大概懂這個標記什麼意思了,手摸了兩個小時,建了兩張A4紙的樹 //就是說普通線段樹,左右兒子是乘二,如果去Pushdown,不管此刻這個兒子在之前有沒有被訪問過,都會用lazy更新 //但是如果是權值線段樹且動態開點,對於這個題而言,還是拿我目前模的這組樣例說,在17這個點,代表區間是4-5,值為2 //但是它並沒有左右兒子,我去查5,答案顯然是2,但是因為17沒有左右兒子,所以L<= R>=這個判定一直不會完成,4-5下不去了 //所以會返回0,我目前用的lazy是在維護開的點的lazy,它並不能想普通那樣更新沒有別需要過的點 //然後又是因為這個題我們的DP定義,4-5的答案,如果下面有兒子可能沒有兒子大,但如果沒兒子肯定它大,所以只需要一句 // int res=t[rt].cn;就解決問題了 // int res=0; Pushdown(rt); int mid=(l+r)>>1; if(x<=mid)res=max(res,query(ls(rt),l,mid,x)); else res=max(res,query(rs(rt),mid+1,r,x)); return res; } void Build(int &rt,int l,int r,int L,int R,int w){ if(!rt){ rt=++tot; } if(L<=l && R>=r){ //這裡這麼做因為這裡不僅僅是建,更是合併後的更新 //在建立葉子節點時,R>=這一步會使>=va[a] 的區間都被進行這裡的操作符合定義 t[rt].cn=max(t[rt].cn,w); return ; } Pushdown(rt); int mid=(l+r)>>1; if(L<=mid)Build(ls(rt),l,mid,L,R,w); if(R>mid) Build(rs(rt),mid+1,r,L,R,w); } void dfs(int u){ for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]==0){ // printf("u=%d v=%d\n",u,v); vis[v]=1; dfs(v); root[u]=merge(root[u],root[v],1,dr,0,0); // printf("root[%d]=%d\n",u,root[u]); } } int an=query(root[u],1,dr,va[u]-1)+1; Build(root[u],1,dr,va[u],dr,an); // printf("an=%d\n",an); printf("root[%d]=%d va[%d]=%d dr=%d an=%d\n",u,root[u],u,va[u],dr,an); } void Solve(){ scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d%d",&va[i],&fa[i]); if(fa[i]==0)continue; Insert(fa[i],i); drt[i]=va[i]; } sort(drt+1,drt+n+1); dr=unique(drt+1,drt+n+1)-drt-1; for(int i=1;i<=n;++i){ va[i]=lower_bound(drt+1,drt+dr+1,va[i])-drt; } dfs(1); int ans=0; ans=query(root[1],1,dr,dr); printf("%d\n",ans); } int main(){ Solve(); return 0; }View Code