樹上啓發式合併。我並不知道爲何要叫作這個名字。。。node
能夠在\(O(n\log n)\)的時間內完成對子樹信息的詢問,可橫向對比把樹按\(dfs\)序轉成序列問題的\(O(n\sqrt n)\)莫隊算法。算法
當\(dfs\)到一個點\(u\),執行如下操做:spa
一、遞歸處理全部輕兒子;
二、遞歸處理重兒子;
三、計算整棵子樹的貢獻(在第2步中重兒子的貢獻得以保留,因此不須要重複計算);
四、若點\(u\)不是其父親的重兒子,刪除整棵子樹的貢獻。code
看上去像是暴力?遞歸
只有當存在一條輕邊時,這條輕邊所鏈接的子樹才須要被從新計算一次貢獻。那麼一個點被重複計算的次數就等於這個點到根路徑上通過的輕邊條數。而根據輕重鏈剖分那套理論,一個點到根路徑上的輕邊不會超過\(\log n\)條,因此這個算法的複雜度爲\(O(n\log n*t)\),其中\(t\)爲一次計算貢獻的複雜度。get
懶得分開寫博客了qaq博客
luogu
一棵樹有\(n\)個結點,每一個結點都有一種顏色,求每一個子樹中出現次數最多的顏色(們)的編號之和。requests
sol:維護\(num_i\)表示有多少種顏色出現了\(i\)次,\(sum_i\)表示這些顏色的編號和。顯然計算貢獻是\(O(1)\)的。string
#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; }
luogu
給你一棵\(n\)個節點的樹,每一個節點上有一個小寫英文字母。每次詢問\(v_i,h_i\),表示在\(v_i\)子樹中全部深度爲\(h_i\)的點上的字母拿出來從新組合可否造成一個迴文串。it
sol:一些字母從新組合後可以造成迴文串當且僅當存在至多一個字母的出現次數爲奇數。因此能夠把每一個小寫英文字母看成是一個二進制位而後維護子樹中每一個深度的異或和便可。
#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; }
luogu
給你一片森林,每次詢問和一個點有相同的\(k\)次祖先的點有多少個。
sol:倍增跳到那個祖先上而後就是隻須要知道該深度有多少個點就好了。
#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; }