[Luogu5384][Cnoi2019] 雪松果樹

傳送門c++

雖然這題是一道二合一,也不算難,但仍是學到了不少東西啊,\(k\) 級兒子個數的五種求法!!我仍是以爲四種比較好(數組

\(k\) 級兒子個數有五種求法,你知道麼? ——魯迅spa

首先 \(k\) 級祖先很好求,離線的話dfs的時候開個棧就行了。長鏈剖分也能夠但我不會,倍增什麼的就不用說了。code

樹上啓發式合併

就是求一個子樹裏爲某一個深度的點的個數嘛,這個明顯能夠dsu on tree啊,開個桶記錄下各類深度的有幾個就行了。get

複雜度:\(O(nlogn)\),應該不能過0_0it

樹狀數組

轉化爲dfs序,就是一個區間裏等於某一個數的個數,二維數點弱化版,離線+樹狀數組。模板

複雜度:\(O(nlogn)\)class

二分

給每一個深度開一個vector,按照dfs序把點塞進去,詢問時只要在對應深度的vector裏二分出區間左右端點就行了。統計

這個作法雖然也是 \(O(nlogn)\) ,但它是在線的,很妙啊!!co

長鏈剖分

這個是模板了吧,用一個簡單的DP統計一下就行了

複雜度 \(O(n)\)

由於我以前其實不會長鏈剖分因此就寫了下代碼……

#include<bits/stdc++.h>
#define ll long long
#define fr(i,x,y) for(int i=(x);i<=(y);i++)
#define rf(i,x,y) for(int i=(x);i>=(y);i--)
#define frl(i,x,y) for(int i=(x);i<(y);i++)
using namespace std;
const int N=1000003;
const int M=N<<1;
int n,q;
int cnt,head[N],Next[M],v[M];
vector<int> id[N];
int qk[N];

void read(int &x){
    char ch=getchar();x=0;
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
}

void add(int x,int y){
    Next[++cnt]=head[x];
    head[x]=cnt;
    v[cnt]=y;
}

int st[N],L;
int d[N],bc[N],ls[N],*f[N],*now=ls;
//vector<int> qry[N];
void predfs(int x,int fa,int dep){
    st[dep]=x;
    //int bc=0;
    for(auto tmp:id[x])
     if (dep>qk[tmp])
      id[st[dep-qk[tmp]]].push_back(tmp);
    id[x].resize(0);
    for(int i=head[x];i;i=Next[i]){
        predfs(v[i],x,dep+1);
        if (d[v[i]]>d[bc[x]]) bc[x]=v[i];
    }
    d[x]=d[bc[x]]+1;
}

int ans[N];
void dfs(int x,int fa){
    f[x][0]=1;
    if (bc[x]) f[bc[x]]=f[x]+1,dfs(bc[x],x);
    for(int i=head[x];i;i=Next[i]){
        int tmp=v[i];
        if (tmp==bc[x]) continue;
        f[tmp]=now;now+=d[tmp];
        dfs(tmp,x);
        fr(j,1,d[tmp]) f[x][j]+=f[tmp][j-1];
    }
    for(auto tmp:id[x])
     ans[tmp]=f[x][qk[tmp]]-1;
}

int main(){
    read(n);read(q);
    int x;
    fr(i,2,n){
        read(x);
        add(x,i);
    }
    fr(i,1,q){
        read(x);read(qk[i]);
        id[x].push_back(i);
    }
    predfs(1,0,1);
    f[1]=now;now+=d[1];
    fr(i,1,n) for(auto j:id[i]) printf("%d ",j);puts("---");
    dfs(1,0);
    fr(i,1,q) printf("%d ",qk[i]==0?0:ans[i]);
    return 0;
}

dfs+差分

這是標算,,想不到……

前面那個樹狀數組未免太大材小用了,由於咱們只是求區間裏等於一個數的個數,並不真的須要樹狀數組所維護的前綴和。

咱們dfs的時候記錄一個 \(cnt_i\) 表示dfs過的裏面深度爲 \(i\) 的有多少個,而後求一個子樹裏深度爲 \(d\) 的個數只要把dfs這個子樹先後的 \(cnt_d\) 減一減就行了。

相關文章
相關標籤/搜索