長鏈剖分優化樹形dp

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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章