P17851

· · 题解

沙子卡常题,差评。

做法

考虑本题弱化版,允许 O(n\log n) 复杂度如何处理。笔者题解。

定义 dp_{u,i} 表示子树 u 内选择出 i 条不相交的路径的最大边权和,钦定 i>0 时 u 必选。

观察到这个路径选择收益是递减的,即从选 i 条提升到选 i+1 条带来的额外收益是不小于从选 i+1 条到选 i+2 条的。即 dp_{u}(i+1)-dp_u(i)\ge dp_{u}(i+2)-dp_{u}(i+1),令 d_i=dp_u(i+1)-dp_u(i)。d_i 单调不增,故 dp_u 是上凸的。

合并子树 v 的时候有两种情况,一种是不考虑 v 然后直接做 (\max,+) 卷积合并。一种是 dp_u(i)=\max(dp_u(i),dp_{v}(i)+w),注意到这个只等价于 dp_u(0) 和 dp_v(i) 合并的时候多了 w 的价值,于是在卷积前令 dp_u(0)=w,卷完之后 dp_u(0) 变回 0 即可。这样就完成了归一。

但让 dp_u(0)=w,相当于 d_0-w。这可能会导致 d_i 不在单调,也就是会影响 dp_u 的凸性。但如果我们令 v 的合并顺序是按照 w 从大到小的。那么 dp_u(0) 在赋值为 w 后依旧是上凸的。

::::info[证明] 要证 dp_u(0) 从 0\to w 后 dp_u(i) 依旧上凸。即证 d_0-w\ge d_1,d_0-d_1\ge w。

考虑使用数学归纳法。

考虑在合并第一个 v 的时候。dp_u(1) 只可能等于 dp_v(1)+w,同理 dp_u(2)=dp_v(2)+w,以此类推。此时 d_i=dp_v(i+1)-dp_v(i),u 的 d_i 此时与 v 的 d_i 完全相同。

然后因为是 u 考虑的第一个儿子,所以没有之前的状态。所有非 0 状态要加上价值 w,dp_u(i)=dp_v(i)+w,i> 0。差分数组中等价于只有 d_0\gets d_0-w。此前 d_i 完全等于 v 的 d_i,满足 d_i\ge d_{i+1}。所以第一次操作后 d_0-d_1\ge w,之后的操作都满足 w 不大于第一次操作的。临时 d_0-w 不会让 d_0<d_1,单调性仍存在。

证明毕。
--- 发现每次除了 $d_0$ 以外,其余的 $d_i$ 都只是参与合并不会修改值。且最后我们也只关注 $1$ 的前 $m$ 个的 $d_i$ 和。于是每个点只维护 $d_0$,每次合并之后较小的一方将 $d_0$ 存到一个东西 $T$ 里面,因为它最后一定会在 $1$ 的 $d_i$ 里面。具体第几个目前不重要。 最后再将 $d_0$ 加入全局 $T$ 里面,$T$ 就是 $1$ 未排序的 $d_i$ 集。用`nth_element`可以做到 $O(n)$ 的时间将前 $k$ 小的放在前 $k$ 个位置。但不保证前 $k$ 个之间的大小关系。合并和统计就做到了线性。 然后考虑 $v$ 要按照 $w$ 从大到小处理。本题 $w\le 2^{17}$,直接用桶计数排序即可。 ::::info[详细处理] ```cpp for(int i=2;i<=n;i++)tot[W[i]^X[i&3]]++;//统计每个值出现次数 for(int i=(1<<17);i>0;i--)cnt[i-1]=cnt[i]+tot[i];//cnt[i] 表示最终排序玩之后前 [1,cnt[i]] 的数都大于 i for(int i=2;i<=n;i++)ord[++cnt[W[i]^X[i&3]]]=i;//大小为 i 的值从 cnt[i] 开始放在排序后的数组里面 for(int i=1;i<n;i++)//按照 w 排序后的顺序加入图即可 { int v=ord[i];int u=fa[v],w=W[v]^X[v&3]; G[u].push_back(v); } ``` :::: 每次复杂度就是 $O(n)$ 的。总体就是 $O(Tn)$。 ## 代码 ::::warning[未卡常代码] 沙子卡常题,差评差评差评差评差评差评差评差评。 ```cpp #include<bits/stdc++.h> using namespace std; //#define int long long const int N=2e5+10; const int inf=1e9; int n,m,fa[N],X[4],tp,W[N]; vector<int>G[N]; long long g[N],d[N]; int tot[(1<<17)+1],cnt[(1<<17)+1]; int ord[N]; void solve() { tp=0; cin>>X[0]>>X[1]>>X[2]>>X[3]>>m; for(int i=1;i<=n;i++)G[i].clear(); memset(tot,0,sizeof tot); memset(cnt,0,sizeof cnt); for(int i=2;i<=n;i++)tot[W[i]^X[i&3]]++; for(int i=(1<<17);i>0;i--)cnt[i-1]=cnt[i]+tot[i]; for(int i=2;i<=n;i++)ord[++cnt[W[i]^X[i&3]]]=i; for(int i=1;i<n;i++) { int v=ord[i];int u=fa[v],w=W[v]^X[v&3]; G[u].push_back(v); } for(int u=n;u>=1;u--) { d[u]=0; for(auto v:G[u]) { int w=W[v]^X[v&3]; if(!d[u]) { d[u]=d[v]+w; continue; } d[u]-=w; if(d[u]<d[v])swap(d[u],d[v]); g[++tp]=d[v]; d[u]+=w; } } g[++tp]=d[1]; if(m==1) { cout<<d[1]<<"\n"; return ; } long long dp=0; if(m<tp)nth_element(g+1,g+m+1,g+tp+1,greater<long long>()); for(int i=1;i<=min(m,tp);i++)dp+=g[i]; cout<<dp<<"\n"; } signed main() { // freopen("mango.in","r",stdin); // freopen("mango.out","w",stdout); ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin>>n; for(int i=2;i<=n;i++)cin>>fa[i]; for(int i=2;i<=n;i++)cin>>W[i]; int T;cin>>T; while(T--)solve(); return 0; } ``` ::::