长链剖分总结

【算法简介】

长链剖分的思想很简单,和树剖基本一致,就是把维护的子树节点最多改成了子树深度最大

这种数据结构一般用于优化动态规划(一般涉及了长度的dp)

【习题】

1.P3899 [湖南集训]谈笑风生

【题意】

求树上的三个点构成的点对(a,b,c),给定a求满足a和b是c的祖先,a和b的距离不超过给定的k

【分析】

分类讨论

1.当b比a高时,c点的可能取值有siz[a]-1个,b的取值有min(dep[b],dep[a],k)种,相乘即可

2.当b比a低时,c点的可能取值有siz[b]-1个,b的取值为min(dep[叶子]-dep[a],k),也是相乘

第一部分的贡献很好计算

计算第二部分贡献,发现每个节点的贡献可以赋给长儿子很多,所以用到了长链剖分来优化

维护一个后缀和,计算每个点的后缀和贡献

从链的最深点开始的点,第二部分贡献加上差分的和即可

注意实现过程中,因为要从父亲节点继承信息,所以开的数组要用指针版,方便赋值

【代码】

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+5;
typedef long long ll;
int head[maxn],n,m,tot;
ll siz[maxn],ans[maxn],tmp[maxn],*s[maxn],*temp=tmp;
struct edge
{
	int to,nxt;
}e[maxn<<1];
void add(int x,int y)
{
	e[++tot].to=y; e[tot].nxt=head[x]; head[x]=tot;
}
struct query
{
	int no,z;
};
vector <query> q[maxn];
int dep[maxn],son[maxn],md[maxn];
void dfs1(int x,int fa)
{
	dep[x]=md[x]=dep[fa]+1; siz[x]=1;
	for(int i=head[x];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(to==fa) continue;
		dfs1(to,x);
		siz[x]+=siz[to];
		if(md[to]>md[son[x]]) son[x]=to;
	}
	if(son[x]) md[x]=md[son[x]];
}
void dfs(int x,int fa)
{
	s[x][0]=siz[x]-1;
	if(son[x]) s[son[x]]=s[x]+1,dfs(son[x],x),s[x][0]+=s[son[x]][0];
	for(int i=head[x];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(to==fa || to==son[x]) continue;
		s[to]=temp; temp+=md[to]-dep[to]+1;
		dfs(to,x);
		for(int j=0;j<=md[to]-dep[to];j++) s[x][j+1]+=s[to][j];
		s[x][0]+=s[to][0];
	}
	for(int i=0;i<q[x].size();i++)
	{
		int no=q[x][i].no,y=q[x][i].z;
		ans[no]+=1LL*(siz[x]-1)*min(dep[x]-1,y);
		ans[no]+=s[x][0]-siz[x]+1;
		if(y<md[x]-dep[x])  ans[no]-=s[x][y+1];
	}
}
int main()
{
	freopen("longchain.in","r",stdin);
	freopen("longchain.out","w",stdout);
	scanf("%d%d",&n,&m);
	int x,y;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y); add(y,x);
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		q[x].push_back((query){i,y});
	}
	dfs1(1,0);
	s[1]=temp; temp+=md[1];
	dfs(1,0); 
	for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
	return 0;
}

2.CF1009F Dominant Indices

【题意】

设 d(u,x) 为 u 子树中到 u 距离为 x 的节点数。对于每个点,求一个最小的 k,使得 d(u,k)最大。

【分析】

设计dp:

f[i][j]表示i子树中到i 的距离为j 的点的个数

在dp转移的时候,先做长儿子,每次处理完之后,直接把长儿子的信息向后错一位放到当前的f数组中,然后再把其他子树与当前暴力计算

我们在做dp的时候只对链的顶点申请内存,这样节省了很多空间,只需要开能放下一条链的长度即可O(N)

对于u的长儿子v,给v 的内存分配到f[u]+1上 ,即f[v=son[u]]=f[u]+1

然后去计算其他儿子的答案,对于u的非长儿子,为他申请内存f[v]=now , now+=dep[u],大小为v为顶端的长链长度

在dfs(v)之后,暴力合并信息即可

void dfs2(int u,int fa)
{
    f[u][0]=1; // 先算上自己
    if (son[u])
    {
        f[son[u]]=f[u]+1; // 共享内存,这样一步之后,f[son[u]][dep]会被自动保存到f[u][dep-1]
        dfs2(son[u],u);
    }
    for (int e=head[u];e;e=nex[e])
    {
        int v=tail[e];
        if (v==son[u] || v==fa) continue;
        f[v]=now;now+=dep[v]; // 为 v 节点申请内存,大小等于以 v 为顶端的长链的长度
        dfs2(v,u);
        for (int i=1;i<=dep[v];i++)
        {
            f[u][i]+=f[v][i-1]; //暴力合并
        }
    }
}

【代码】

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+5;
typedef long long ll;
int head[maxn],n,m,tot;
ll siz[maxn],ans[maxn],tmp[maxn],*s[maxn],*temp=tmp;
struct edge
{
	int to,nxt;
}e[maxn<<1];
void add(int x,int y)
{
	e[++tot].to=y; e[tot].nxt=head[x]; head[x]=tot;
}
struct query
{
	int no,z;
};
vector <query> q[maxn];
int dep[maxn],son[maxn],md[maxn];
void dfs1(int x,int fa)
{
	dep[x]=md[x]=dep[fa]+1; siz[x]=1;
	for(int i=head[x];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(to==fa) continue;
		dfs1(to,x);
		siz[x]+=siz[to];
		if(md[to]>md[son[x]]) son[x]=to;
	}
	if(son[x]) md[x]=md[son[x]];
}
void dfs(int x,int fa)
{
	s[x][0]=siz[x]-1;
	if(son[x]) s[son[x]]=s[x]+1,dfs(son[x],x),s[x][0]+=s[son[x]][0];
	for(int i=head[x];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(to==fa || to==son[x]) continue;
		s[to]=temp; temp+=md[to]-dep[to]+1;
		dfs(to,x);
		for(int j=0;j<=md[to]-dep[to];j++) s[x][j+1]+=s[to][j];
		s[x][0]+=s[to][0];
	}
	for(int i=0;i<q[x].size();i++)
	{
		int no=q[x][i].no,y=q[x][i].z;
		ans[no]+=1LL*(siz[x]-1)*min(dep[x]-1,y);
		ans[no]+=s[x][0]-siz[x]+1;
		if(y<md[x]-dep[x])  ans[no]-=s[x][y+1];
	}
}
int main()
{
	freopen("longchain.in","r",stdin);
	freopen("longchain.out","w",stdout);
	scanf("%d%d",&n,&m);
	int x,y;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y); add(y,x);
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		q[x].push_back((query){i,y});
	}
	dfs1(1,0);
	s[1]=temp; temp+=md[1];
	dfs(1,0); 
	for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值