题解:P17851 「RiOI-(-2)」houran 和芒果 / [ROI 2026 Day1] 泰莫利亚的调查 加强版
假装这是验题人题解。
反悔贪心做法。
如果要求是边不相交就是直接长剖选叶子,但本题要求点不相交。在一个子树允许的叶子数上升的时候,这个子树的根节点往下会改连更大的边。
考虑
大概思路是,
算法流程:
- 对于
dp_{v,1} ,把所有子树按照dp_{v,1}+w_v 从大到小排序,取值最大的v' 更新dp_{u,1}=dp_{v',1}+w_{v1} 。 - 令
lst=v' ,然后按上面顺序遍历v \neq v' 。如果w_v<w_{lst} ,则把dp_{v,1}+w_{lst}-w_v 加入额外数组y ,且更新lst=v ;否则把dp_{v,1} 原样加入数组y 。 - 把所有
dp_{v,j} (j>1) 加入数组y 。 - 把
y 从大到小排序,作为dp_{u,2\sim叶子个数} 。
正确性:
- 对于每个
v 子树,只有dp_{v,1} 的贡献上升,所以在dp_u 中的相对位置不变,能以子树内原本顺序合法选择。 - 对于每个
dp_{v,1} ,根据排序方法显然答案达到上界。 -
直接用 set 启发式合并,特殊处理
代码较丑,就不放了。
发现这太唐了,
:::info[代码]
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int N=210000;
int t,n,d1[N],fa[N],w1[N],w[N],X[4],k,tot;
long long x[N],y[N];
vector<int>e[N];
bool cmp1(int a,int b){
return w[a]+x[a]>w[b]+x[b];
}
inline void dfs0(int u){
d1[++tot]=u;
for(int v:e[u])dfs0(v);
}
bool cmp(long long x,long long y){
return x>y;
}
inline void read(int&x){
char c=getchar();
while(!('0'<=c&&c<='9'))c=getchar();
x=0;
while('0'<=c&&c<='9')x=x*10+c-'0',c=getchar();
}
int main(){
//freopen("mango.in","r",stdin);
//freopen("mango.out","w",stdout);
read(n);
for(int i=2;i<=n;i++)read(fa[i]),e[fa[i]].push_back(i);
for(int i=2;i<=n;i++)read(w1[i]);
dfs0(1);
read(t);
while(t--){
for(int i=0;i<4;i++)read(X[i]);
read(k);
for(int i=2;i<=n;i++)w[i]=w1[i]^X[i%4];
tot=0;
for(int i=n;i>=1;i--){
int u=d1[i];
x[u]=0;
if(e[u].empty())continue;
sort(e[u].begin(),e[u].end(),cmp1);
int v1=e[u][0];
x[u]=x[v1]+w[v1];
for(int v:e[u]){
if(v==v1)continue;
y[++tot]=x[v]+max(w[v]-w[v1],0);
if(w[v]>w[v1])v1=v;
}
}
y[++tot]=x[1];
sort(y+1,y+tot+1,cmp);
//for(int i=1;i<=tot;i++)cout<<y[i]<<' ';
//cout<<endl;
for(int i=1;i<=tot;i++)y[i]+=y[i-1];
printf("%lld\n",y[min(k,tot)]);
}
return 0;
}
:::
考虑继续优化到线性。这时瓶颈是两个 sort。
对于 dfs 中对所有儿子的排序,值域大不好优化。但注意到我们在做的事形如一维
对于最外面的排序,注意到我们是先把 nth_element() 后前
为卡掉
我的实现没有这么优秀,荣获最劣解(
:::info[代码]
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=210000;
int t,n,d1[N],fa[N],w1[N],w[N],X[4],k,tot;
long long x[N],y[N],ans;
int m,cnt[N],rk[N],at[N],e1[N],le[N],re[N];
basic_string<int>e[N];
bool cmp1(int a,int b){
return w[a]+x[a]>w[b]+x[b];
}
inline void dfs0(int u){
d1[++tot]=u;
for(int v:e[u])dfs0(v);
}
bool cmp(long long x,long long y){
return x>y;
}
inline void read(int&x){
char c=getchar();
while(!('0'<=c&&c<='9'))c=getchar();
x=0;
while('0'<=c&&c<='9')x=x*10+c-'0',c=getchar();
}
int main(){
//freopen("mango8.in","r",stdin);
//freopen("mango.out","w",stdout);
read(n);
for(int i=2;i<=n;i++)read(fa[i]),e[fa[i]].push_back(i);
for(int i=1;i<=n;i++)le[i]=re[i-1]+1,re[i]=le[i]+e[i].size()-1;
for(int i=2;i<=n;i++)read(w1[i]);
dfs0(1);
read(t);
while(t--){
for(int i=0;i<4;i++)read(X[i]);
read(k);
for(int i=2;i<=n;i++)w[i]=w1[i]^X[i%4];
m=0;
for(int i=2;i<=n;i++)m=max(m,w[i]);
for(int i=0;i<=m;i++)cnt[i]=0;
for(int i=2;i<=n;i++)cnt[w[i]]++;
for(int i=m-1;i>=0;i--)cnt[i]+=cnt[i+1];
for(int i=2;i<=n;i++)rk[cnt[w[i]]--]=i;
for(int i=1;i<=n;i++)re[i]=le[i]-1;
for(int i=1;i<n;i++)e1[++re[fa[rk[i]]]]=rk[i];
tot=0;
for(int i=n;i>=1;i--){
int u=d1[i];
x[u]=0;
if(le[u]>re[u])continue;
int v1=0;
for(int i=le[u];i<=re[u];i++){
int v=e1[i];
if(x[v]+w[v]>x[v1]+w[v1])v1=v;
}
x[u]=x[v1]+w[v1];
int lst=0;
bool bol=0;
for(int i=le[u];i<=re[u];i++){
int v=e1[i];
if(v==v1){bol=1;continue;}
if(bol||w[v]+x[v]<=w[lst]+x[lst])y[++tot]=x[v],at[v]=tot;
else{
y[at[lst]]-=w[v];
y[++tot]=x[v]+w[v],at[v]=tot;
lst=v;
}
}
y[at[lst]]-=w[v1];
}
y[++tot]=x[1];
//for(int i=1;i<=tot;i++)cout<<y[i]<<' ';
//cout<<endl;
nth_element(y+1,y+min(k,tot),y+tot+1,cmp);
ans=0;
for(int i=1;i<=tot&&i<=k;i++)ans+=y[i];
printf("%lld\n",ans);
}
return 0;
}
:::
另外,场下这题还有一个类似的更本质的理解。把一个节点的每个儿子按照
所以怎么真的有一万种做法写出来是一个东西