长链剖分优化树形dp

89 篇文章 0 订阅
72 篇文章 0 订阅

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)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值