「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;
}