apio鐵牌告辭(開場想打暴力然後gedit碼代碼5個小時沒寫完三題最低檔暴力真是快樂),聽課也就學到了一丟丟這個東西。
模板題:
https://www.luogu.org/problemnew/show/P5384
首先k級兄弟可以一遍dfs的時候丟到k級父親上變成求k級孩子的詢問。
求k級孩子有個很簡單的做法,直接dfs的時候維護掃到某個點時記錄下的每種dep出現次數,對於所有求這個點k級孩子的詢問減去當前出現次數,dfs完這棵樹後在加上當前出現次數即可。複雜度O(n)。
代碼:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+100;
template<class T>
void rd(T &x)
{
char c=getchar();x=0;bool f=0;
while(!isdigit(c))f|=(c=='-'),c=getchar();
while(isdigit(c))x=x*10+c-48,c=getchar();
if(f)x=-x;
}
template<class T>
void print(T x)
{
if(x<0)x=-x,putchar('-');
if(x==0){putchar('0');return;}
static int buf[30],bnum;
bnum=0;
while(x)buf[++bnum]=x%10,x/=10;
for(int i=bnum;i>=1;i--)putchar(buf[i]+'0');
}
struct pii{
int fi,sc;
pii(){};
pii(int fi,int sc):fi(fi),sc(sc){};
};
int n,Q,fa[N],ans[N],stk[N],snum=0,cnt[N],dep[N];
vector<int>son[N];
vector<pii>q[N];
void dfs(int x)
{
stk[++snum]=x;
for(int i=0,v;i<q[x].size();i++)
{
v=snum-q[x][i].fi;
if(v<=0||q[x][i].fi==0)ans[q[x][i].sc]=1;
else q[stk[v]].push_back(pii(q[x][i].fi,q[x][i].sc));
}
q[x].clear();
for(int i=0;i<son[x].size();i++)
dfs(son[x][i]);
--snum;
}
void dfs1(int x)
{
for(int i=0;i<q[x].size();i++)
ans[q[x][i].sc]-=cnt[dep[x]+q[x][i].fi];
cnt[dep[x]]++;
for(int i=0;i<son[x].size();i++)
dep[son[x][i]]=dep[x]+1,dfs1(son[x][i]);
for(int i=0;i<q[x].size();i++)
ans[q[x][i].sc]+=cnt[dep[x]+q[x][i].fi];
}
int main()
{
rd(n),rd(Q);
for(int i=2;i<=n;i++)
{
rd(fa[i]),son[fa[i]].push_back(i);
}
for(int i=1,u,k;i<=Q;i++)
{
rd(u),rd(k);
q[u].push_back(pii(k,i));
}
dfs(1),dep[1]=1,dfs1(1);
for(int i=1;i<=Q;i++)
print(ans[i]-1),putchar(' ');
}
好吧這種做法不是重點。。。
考慮naive的做法,記錄f(i,j)表示i子樹下距離i爲j的點有多少個,然後逐層合併。優化:首先一個點記的子樹下深度的點一定是大於當前深度的,然後考慮每個點初始時可以繼承一個孩子的信息,而如果這個點取子樹深度最深的點,就能證明總複雜度也是O(n)的了。因爲這樣的繼承相當於在繼承該點子樹內長鏈剖分的長鏈信息,而發現對於每一條剖分的鏈,它作爲短鏈只會在鏈頂端被用於拷貝一次,而拷貝的複雜度就是鏈長,所以總複雜度是O(n)的,空間基本證明也一樣。
(實現時繼承那步要用指針以實現偏移1的操作)
代碼:(被毒瘤卡空間被迫換方式存邊)
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+100;
template<class T>
void rd(T &x)
{
char c=getchar();x=0;bool f=0;
while(!isdigit(c))f|=(c=='-'),c=getchar();
while(isdigit(c))x=x*10+c-48,c=getchar();
if(f)x=-x;
}
template<class T>
void print(T x)
{
if(x<0)x=-x,putchar('-');
if(x==0){putchar('0');return;}
static int buf[30],bnum;
bnum=0;
while(x)buf[++bnum]=x%10,x/=10;
for(int i=bnum;i>=1;i--)putchar(buf[i]+'0');
}
struct pii{
int fi,sc;
pii(){};
pii(int fi,int sc):fi(fi),sc(sc){};
};
int n,Q,ans[N],stk[N],snum=0,mxd[N],lson[N],*f[N],*mem;
vector<pii>q[N];
int hd[N],nxt[N],to[N],tot=0;
void add(int x,int y)
{nxt[++tot]=hd[x],to[tot]=y,hd[x]=tot;}
void dfs(int x)
{
stk[++snum]=x,lson[x]=0,mxd[x]=1;
for(int i=0,v;i<q[x].size();i++)
{
v=snum-q[x][i].fi;
if(v<=0||q[x][i].fi==0)ans[q[x][i].sc]=1;
else q[stk[v]].push_back(pii(q[x][i].fi,q[x][i].sc));
}
q[x].clear();
for(int i=hd[x],v;i;i=nxt[i])
{
v=to[i],dfs(v);
if(mxd[lson[x]]<mxd[v])lson[x]=v;
}
mxd[x]=mxd[lson[x]]+1;
--snum;
}
void dfs1(int x,int *fa_f)
{
f[x]=fa_f,f[x][0]=1;
if(lson[x])dfs1(lson[x],f[x]+1);
for(int i=hd[x],v;i;i=nxt[i])
{
v=to[i];
if(v==lson[x])continue;
int *tmp=mem;
mem+=mxd[v];
dfs1(v,tmp);
for(int j=0;j<mxd[v];j++)
f[x][j+1]+=tmp[j];
}
for(int i=0;i<q[x].size();i++)
ans[q[x][i].sc]=f[x][q[x][i].fi];
}
int main()
{
rd(n),rd(Q);
for(int i=2,fa;i<=n;i++)
rd(fa),add(fa,i);
for(int i=1,u,k;i<=Q;i++)
{
rd(u),rd(k);
q[u].push_back(pii(k,i));
}
dfs(1);
mem=(int *)malloc(sizeof(int)*N);
dfs1(1,(int *)malloc(sizeof(int)*N));
for(int i=1;i<=Q;i++)
print(ans[i]-1),putchar(' ');
}
這種依靠長鏈剖分優化樹形dp的做法還可以改一改在一些自頂向上傳遞信息的樹上問題使用,具體做法就是對於當前點子樹內長鏈的那個長兒子,直接把字樹內其它短鏈信息丟給它,根據之前證明是O(n)的。對於短鏈,則用當前子樹信息減去這條短鏈裏的信息,發現與之前幾乎一樣也是O(n),總複雜度就是O(n)的了。(具體題目還沒寫過,瞎bb)