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)