FJOI2018 领导集团问题 题解

发布时间 2023-08-16 23:18:32作者: HaHeHyt

先考虑暴力 dp。设 \(f_{u,x}\) 表示在子树 \(u\) 中选出的节点集合的
\(w\) 最小值为 \(x\) 的情况下,最大的节点集合的大小。有两种转移(选不选 \(u\)):

\(f_{u,x}\gets \sum\limits_{v\in \text{substree}_u} f_{v,\ge x}\)

\(f_{u,w_u}\gets \sum\limits_{v\in \text{substree}_u} f_{v,\ge w_u}+1\)


考虑优化一下状态:令 \(F_{u,x}=\max\limits_{y\ge x} f_{u,y}\)

\(F_{u,x}\gets \sum\limits_{v\in \text{substree}_u} F_{v,x}(1\le x\le n)\)

\(F_{u,x}\gets \sum\limits_{v\in \text{substree}_u} F_{v,w_u}+1(1\le x\le w_u)\)

这样可以直接线段树合并做的,但是我理解不了这种做法。


考虑第二种转移,被被更新的 \(x\) 一定是一个后缀,因为 \(f_u\) 单调不增。

考虑差分,令 \(g_{u,x}=F_{u,x}-F_{u,x+1}\)\(L:x_{max}\ s.t\ F_{u,x}\ge F_{u,w_u}+1\)。于是转移变为:

\(g_{u,x}\gets \sum\limits_{v\in \text{substree}_u} g_{v,x}(1\le x\le n)\)

\(F_{u,x}\gets F_{u,x}+1(L\le x\le w_u)\Leftrightarrow g_{u,w_u}\gets g_{u,w_u}+1,g_{u,L-1}\gets g_{u,L-1}-1\)

考虑 \(L-1\)\(g\) 中的意义,发现是 \(g_{u,[1,w_u]}\) 中最后一个不为 \(0\) 的,于是线段树上二分查找一下即可。我们分析一下操作,发现只需线段树合并,区间和,单点加,这就是板子啦!具体看代码。

#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=2e5+5;
int n,len,a[N],b[N],rt[N],tot,c[N*50],ls[N*50],rs[N*50];//50是因为2logn,肯定开大多了,我习惯 
vector<int>g[N];
void updata(int l,int r,int &wz,int p,int x)
{
	if(!wz) wz=++tot;if(l==r) return c[wz]+=x,void();
	int mid=(l+r)>>1;(p<=mid)?updata(l,mid,ls[wz],p,x):updata(mid+1,r,rs[wz],p,x);
	c[wz]=c[ls[wz]]+c[rs[wz]];
}//单点加 
int query(int l,int r,int wz,int L,int R)
{
	if(L<=l&&r<=R) return c[wz];int mid=(l+r)>>1,ans=0;
	if(L<=mid) ans+=query(l,mid,ls[wz],L,R);
	if(mid<R) ans+=query(mid+1,r,rs[wz],L,R);return ans;
}//查询区间和 
int Find(int l,int r,int wz,int k)
{
	if(l==r) return c[wz]?l:0;int mid=(l+r)>>1;
	if(c[ls[wz]]>=k) return Find(l,mid,ls[wz],k);
	return Find(mid+1,r,rs[wz],k-c[ls[wz]]);
}//查询最后一个不为0的数,如果[mid+1,w_u]这段都为0,则 c[ls[wz]]>=k,走左边(由于是最后一个优先左),否则走右边。 注意有可能全为0。 
int hb(int l,int r,int wz,int wz1)
{
	if(!wz||!wz1) return wz+wz1;
	if(l==r) return c[wz]+=c[wz1],wz;int mid=(l+r)>>1;
	ls[wz]=hb(l,mid,ls[wz],ls[wz1]),rs[wz]=hb(mid+1,r,rs[wz],rs[wz1]);
	return c[wz]=c[ls[wz]]+c[rs[wz]],wz;
}//线段树合并,基础操作 
void dfs(int x)
{
	for(int i:g[x]) dfs(i),rt[x]=hb(1,len,rt[x],rt[i]);
	int t=query(1,len,rt[x],1,a[x]),w=Find(1,len,rt[x],t);
	updata(1,len,rt[x],a[x],1);(w)&&(updata(1,len,rt[x],w,-1),1);
}//没啥好说的,注意 w 可能为 0,就是不需要 -1 啦! 
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i];sort(b+1,b+1+n);len=unique(b+1,b+1+n)-b-1;
	for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+1+len,a[i])-b;
	for(int i=1,x;i<n;i++) cin>>x,g[x].push_back(i+1);dfs(1);
	return cout<<c[rt[1]],0;
}