1. 程式人生 > >dsu on tree總結

dsu on tree總結

int 什麽 小寫 啟發式 上啟 char pro esp 存在

dsu on tree

樹上啟發式合並。我並不知道為什麽要叫做這個名字。。。

幹什麽的

可以在\(O(n\log n)\)的時間內完成對子樹信息的詢問,可橫向對比把樹按\(dfs\)序轉成序列問題的\(O(n\sqrt n)\)莫隊算法。

怎麽實現

\(dfs\)到一個點\(u\),執行以下操作:

1、遞歸處理所有輕兒子;
2、遞歸處理重兒子;
3、計算整棵子樹的貢獻(在第2步中重兒子的貢獻得以保留,所以不需要重復計算);
4、若點\(u\)不是其父親的重兒子,刪除整棵子樹的貢獻。

看上去像是暴力?

復雜度分析

只有當存在一條輕邊時,這條輕邊所連接的子樹才需要被重新計算一次貢獻。那麽一個點被重復計算的次數就等於這個點到根路徑上經過的輕邊條數。而根據輕重鏈剖分那套理論,一個點到根路徑上的輕邊不會超過\(\log n\)

條,所以這個算法的復雜度為\(O(n\log n*t)\),其中\(t\)為一次計算貢獻的復雜度。

題目

懶得分開寫博客了qaq

CF600E Lomsat gelral

luogu
一棵樹有\(n\)個結點,每個結點都有一種顏色,求每個子樹中出現次數最多的顏色(們)的編號之和。

sol:維護\(num_i\)表示有多少種顏色出現了\(i\)次,\(sum_i\)表示這些顏色的編號和。顯然計算貢獻是\(O(1)\)的。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace
std; int gi(){ int x=0,w=1;char ch=getchar(); while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar(); if (ch=='-') w=0,ch=getchar(); while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return
w?x:-x; } #define ll long long const int N = 1e5+5; int n,a[N],to[N<<1],nxt[N<<1],head[N],cnt; int sz[N],son[N],vis[N],tot[N],num[N],Max;ll sum[N],ans[N]; void link(int u,int v){ to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt; } void dfs1(int u,int f){ sz[u]=1; for (int e=head[u];e;e=nxt[e]) if (to[e]!=f){ dfs1(to[e],u);sz[u]+=sz[to[e]]; if (sz[to[e]]>sz[son[u]]) son[u]=to[e]; } } void update(int u,int f,int val){ int &t=tot[a[u]]; --num[t];sum[t]-=a[u]; t+=val; ++num[t];sum[t]+=a[u]; if (val>0) Max=max(Max,t); else if (!num[Max]) --Max; for (int e=head[u];e;e=nxt[e]) if (to[e]!=f&&!vis[to[e]]) update(to[e],u,val); } void dfs(int u,int f,int keep){ for (int e=head[u];e;e=nxt[e]) if (to[e]!=f&&to[e]!=son[u]) dfs(to[e],u,0); if (son[u]) dfs(son[u],u,1),vis[son[u]]=1; update(u,f,1); ans[u]=sum[Max]; vis[son[u]]=0; if (!keep) update(u,f,-1); } int main(){ n=gi(); for (int i=1;i<=n;++i) a[i]=gi(); for (int i=1;i<n;++i){ int u=gi(),v=gi(); link(u,v);link(v,u); } dfs1(1,0);dfs(1,0,1); for (int i=1;i<=n;++i) printf("%I64d ",ans[i]); puts("");return 0; }

CF570D Tree Requests

luogu
給你一棵\(n\)個節點的樹,每個節點上有一個小寫英文字母。每次詢問\(v_i,h_i\),表示在\(v_i\)子樹中所有深度為\(h_i\)的點上的字母拿出來重新組合能否形成一個回文串。

sol:一些字母重新組合後能夠形成回文串當且僅當存在至多一個字母的出現次數為奇數。所以可以把每個小寫英文字母當作是一個二進制位然後維護子樹中每個深度的異或和即可。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 5e5+5;
struct node{int h,nxt;}a[N];
int n,m,nxt[N],head[N],val[N],ft[N];char s[N];
int dep[N],sz[N],son[N],vis[N],sum[N],ans[N];
void add(int v,int h,int id){
    a[id]=(node){h,ft[v]};ft[v]=id;
}
void dfs1(int u,int f){
    dep[u]=dep[f]+1;sz[u]=1;
    for (int v=head[u];v;v=nxt[v]){
        dfs1(v,u),sz[u]+=sz[v];
        if (sz[v]>sz[son[u]]) son[u]=v;
    }
}
void update(int u){
    sum[dep[u]]^=val[u];
    for (int v=head[u];v;v=nxt[v])
        if (!vis[v]) update(v);
}
bool cal(int x){
    int res=0;
    while (x) ++res,x^=x&-x;
    return res<=1;
}
void dfs(int u,int f,int keep){
    for (int v=head[u];v;v=nxt[v])
        if (v!=son[u]) dfs(v,u,0);
    if (son[u]) dfs(son[u],u,1),vis[son[u]]=1;
    update(u);
    for (int i=ft[u];i;i=a[i].nxt) ans[i]=cal(sum[a[i].h]);
    vis[son[u]]=0;
    if (!keep) update(u);
}
int main(){
    n=gi();m=gi();
    for (int i=2,f;i<=n;++i)
        f=gi(),nxt[i]=head[f],head[f]=i;
    scanf("%s",s+1);
    for (int i=1;i<=n;++i) val[i]=1<<s[i]-'a';
    for (int i=1,v,h;i<=m;++i) v=gi(),h=gi(),add(v,h,i);
    dfs1(1,0);dfs(1,0,1);
    for (int i=1;i<=m;++i) puts(ans[i]?"Yes":"No");
    return 0;
}

CF208E Blood Cousins

luogu
給你一片森林,每次詢問和一個點有相同的\(k\)次祖先的點有多少個。

sol:倍增跳到那個祖先上然後就是只需要知道該深度有多少個點就行了。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 1e5+5;
int n,m,nxt[N],head[N],root[N],fa[18][N],ft[N];
int dep[N],sz[N],son[N],vis[N],tot[N],ans[N];
struct node{int d,nxt;}a[N];
void add(int x,int d,int id){
    a[id]=(node){d,ft[x]};ft[x]=id;
}
void dfs1(int u,int f){
    fa[0][u]=f;dep[u]=dep[f]+1;sz[u]=1;
    for (int i=1;i<=17;++i) fa[i][u]=fa[i-1][fa[i-1][u]];
    for (int v=head[u];v;v=nxt[v]){
        dfs1(v,u);sz[u]+=sz[v];
        if (sz[v]>sz[son[u]]) son[u]=v;
    }
}
void update(int u,int val){
    tot[dep[u]]+=val;
    for (int v=head[u];v;v=nxt[v])
        if (!vis[v]) update(v,val);
}
void dfs(int u,int keep){
    for (int v=head[u];v;v=nxt[v])
        if (v!=son[u]) dfs(v,0);
    if (son[u]) dfs(son[u],1),vis[son[u]]=1;
    update(u,1);
    for (int i=ft[u];i;i=a[i].nxt) ans[i]=tot[a[i].d]-1;
    vis[son[u]]=0;
    if (!keep) update(u,-1);
}
int main(){
    n=gi();
    for (int i=1;i<=n;++i){
        int f=gi();
        if (!f) root[++root[0]]=i;
        else nxt[i]=head[f],head[f]=i;
    }
    for (int i=1;i<=root[0];++i) dfs1(root[i],0);
    m=gi();
    for (int i=1;i<=m;++i){
        int x=gi(),k=gi(),d=dep[x];
        for (int j=0;j<=17;++j) if (k&(1<<j)) x=fa[j][x];
        add(x,d,i);
    }
    for (int i=1;i<=root[0];++i) dfs(root[i],0);
    for (int i=1;i<=m;++i) printf("%d ",ans[i]);
    puts("");return 0;
}

dsu on tree總結