「CF1485E」Move and Swap DP+CDQ分治解法

「CF1485E」Move and Swap

https://codeforces.ml/problemset/problem/1485/E

显然是树形 dp 。

注意到我们顺着题目进行 dp,那么交换操作将会非常的烦人。所以我们考虑倒着进行 dp。

正着做的流程应该是:移动,计算贡献,交换。

那么倒着做的流程就应该是:交换,计算贡献,移动。

注意到倒着做的时候蓝色点在哪里并不重要,因为蓝色点可以出现在任意想要的位置,所以我们设 \(f_i\) 表示红色\(i\) 这个节点时最大的贡献。由于我们是倒着做的,所以答案就是 \(f_i\)

此外,我们是从下往上爬,所以移动一定是往父亲节点移动。

那么暴力转移可以这么转移:设 \(fa(x)\) 表示 \(x\) 的父亲,假设我们要将 \(f_u\) 转移到 \(f_{fa(u)}\),枚举 \(v\) 为同一层的两个点(可以相同,表示蓝红走到一个点了)。设 \(V=\max(f_u,f_v)+| a_u-a_v|\) 。那么我们就可以用 \(V\) 来更新 \(f_{fa(u)}\),即 \(f_{fa(u)}=\max(V,f_{fa(u)})\)

\(\max(f_u,f_v)\) 是因为我们倒着做的流程是先交换再计算贡献最后移动的,所以 \(u,v\) 都可能是红色的点。而 \(|a_u-a_v|\) 就是贡献。

注意到我们这么转移最坏是 \(O(n^2)\) 的。考虑优化。

要想优化,就得快速处理出最大的 \(V\)。也就是针对每一个 \(u\),我们需要快速找到一个同层节点 \(v\) 使得 \(\max(f_u,f_v)+|a_u-a_v|\) 最大。这条式子中的 \(\max(f_u,f_v)\)\(|a_u-a_v |\) 显得很棘手。

如果我们一开始按照 \(f\) 值大小对一层的节点排序,那么 \(\max\) 就可以去掉了,线性分左右两部分转移即可。但是 \(|a_u-a_v|\) 依然无法很好解决。如果一开始按照 \(a\) 值大小排序,那么 \(|a_u,a_v|\) 就可以去掉了,同样线性枚举分左右两部分转移,同样的 \(\max(f_u,f_v)\) 无法很好解决。

而实际上这个式子有点像二维偏序,如果我们能同时保证 \(f,a\) 的有序,那么这个式子就很好维护了。所以我们可以使用 cdq 分治来维护这个式子。

我们将同层编号存储在 \(A\) 中,并且在外层以按 \(f\) 值进行排序, 在内层按 \(a\) 值排序。也就是分治前按 \(f\) 排序。在分治时,将 \([l,mid],[mid+1,r]\) 这两个区间按 \(a\) 值排序。

由于 \([mid+1,r]\) 中任意的一个点的 \(f\) 值都是大于 \([l,mid]\) 中任意一个点的 \(f\) 值, 我们设 \(u\in[mid+1,r]\) 为当前要用以更新其父亲的点, \(V=\max(|a_u-a_{A(l)}|,|a_u-a_{A(mid)}|)\)。那么 \(f_{fa(u)}=\max(f_{fa(u)},f_u+V)\)

我们设 \(u\in[l,mid]\) 为当前要用以更新其父亲的点,设 \(pos\in[mid+1,r]\) 表示 $pos $ 为 $[mid+1,r] $ 中最后一个满足 \(a_{i}\le a_u\) 的点的下标。这个可以用二分在 \(O(\log n)\) 求出。然后我们求出前缀最大值 \(L_{pos}=\max_{i=mid+1}^{pos}(f_i-a_i)\),以及后缀最大值 \(R_{pos+1}=\max_{i=pos+1}^{r}(f_i+a_i)\)。这两个可以\(O(n)\) 预处理且 \(O(1)\) 查询。那么 \(f_{fa(u)}=\max(L_{pos}+a_u,R_{pos+1}-a_u)\)

总时间复杂度就是 \(O(n\log^2 n)\)

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 2e5+5;
ll dp[MAXN],W[MAXN],L[MAXN],R[MAXN];
int n,f[MAXN],dep[MAXN],mxdep,A[MAXN];
vector <int> e[MAXN],node[MAXN];
void dfs(int p,int fa)
{
	dep[p]=dep[fa]+1;f[p]=fa;
	mxdep=max(mxdep,dep[p]);
	for(int i=0;i<e[p].size();++i)
	{
		int to=e[p][i];
		if(to==fa) continue;
		dfs(to,p);
	}
	node[dep[p]].push_back(p);
}
bool cmp1(int x,int y)
{
	return dp[x]<dp[y];
}
bool cmp2(int x,int y)
{
	return W[x]<W[y];
}
void cdq_solve(int l,int r)
{
	if(l==r)
	{
		dp[f[A[l]]]=max(dp[f[A[l]]],dp[A[l]]);
		return ;
	}
	int mid=l+r>>1;
	cdq_solve(l,mid);cdq_solve(mid+1,r);
	sort(A+l,A+mid+1,cmp2);sort(A+mid+1,A+r+1,cmp2);
	for(int i=mid+1;i<=r;++i)
	{
		L[i]=dp[A[i]]-W[A[i]];
		R[i]=dp[A[i]]+W[A[i]];
		if(i>mid+1) L[i]=max(L[i],L[i-1]);
		ll V=dp[A[i]]+max(abs(W[A[i]]-W[A[mid]]),abs(W[A[i]]-W[A[l]]));
		dp[f[A[i]]]=max(dp[f[A[i]]],V);
	}
	for(int i=r-1;i>=mid+1;--i)
		R[i]=max(R[i],R[i+1]);
	for(int i=l;i<=mid;++i)
	{
		int pos=upper_bound(A+mid+1,A+r+1,A[i],cmp2)-A-1;
		if(pos>r) pos=r;
		ll V=0;
		if(pos>=mid+1) V=max(V,L[pos]+W[A[i]]);
		if(pos+1<=r) V=max(V,R[pos+1]-W[A[i]]);
		dp[f[A[i]]]=max(dp[f[A[i]]],V);
	}
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		memset(dp,-0x3f,sizeof dp);
		for(int i=1;i<=n;++i) e[i].clear();
		for(int i=1;i<=n;++i) node[i].clear();
		scanf("%d",&n);
		for(int i=2;i<=n;++i)
		{
			int v;scanf("%d",&v);
			e[i].push_back(v);
			e[v].push_back(i);
		}
		for(int i=2;i<=n;++i) scanf("%lld",&W[i]);
		mxdep=0;
		dfs(1,0);
		for(int i=0;i<node[mxdep].size();++i)
			dp[node[mxdep][i]]=0;
		for(int d=mxdep;d>1;--d)
		{
			int tot=0;
			for(int i=0;i<node[d].size();++i)
				A[++tot]=node[d][i];
			sort(A+1,A+1+tot,cmp1);
			cdq_solve(1,tot);
		}
		printf("%lld\n",dp[1]);
	}

	return 0;
}
posted @ 2022-01-27 20:04  夜空之星  阅读(49)  评论(0编辑  收藏  举报