题解——P4577 [FJOI2018]领导集团问题


题意

树上 \(\text{LIS}\) 问题,要求树上最长不上升子序列。

思路

我们先回忆一下正常线性的 \(\text{LIS}\) 的方法,朴素做法是枚举前一位,复杂度是 \(O(n^2)\) 的。一个优化做法是(拿最长不上升子序列为例),对于一个新的值 \(k\),我们找到它的前驱(小于它且最大的树),将其与前驱替换。这样在保证原有子序列有不上升性质的同时,还能保证当前的选择是最优的(可以理解成“紧凑”)。

举例说明一下,比如子序列 \(5,4,1\),此时我们枚举到了 \(3\),若将 \(1\) 替换成 \(3\),子序列就成了 \(5,4,3\),这样一来后续枚举到 \(2\) 时,还是可以并入这个子序列的,这就是我们查询前驱的目的所在了。

那么这些操作依然明了,且显然一个节点的子树们是互不影响的,于是我们考虑合并,最后剩余要插入的就是子树根节点的权值,之后的操作就是查询前驱和替换了。

实现

(1)\(\text{STL}\)

用一个 \(\text{multiset}\) 维护,并执行上述操作即可。

multiset s[maxn];
inline void merge(int x,int y){
	if(s[x].size()::iterator i=s[y].begin();i!=s[y].end();i++){
		s[x].insert(*i);
	}
}
inline void dfs(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		merge(u,v);
	}
	multiset::iterator i=s[u].lower_bound(w[u]);
	if(i!=s[u].end()) s[u].erase(i);
	s[u].insert(w[u]);
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
    	//这里为了方便查询前驱,赋值成负贡献
		w[i]=1e9-read();
	}
	for(int i=2;i<=n;i++){
		int v=read();
		add_edge(i,v);
		add_edge(v,i);
	}
	dfs(1,0);
	printf("%ld\n",s[1].size());
	return 0;
}

(2)线段树合并

离散化后用线段树去查前驱以及修改(注意特判本身为最小值的情况)。

int tot,rt[maxn];
struct SegmentTree{
	#define mid ((l+r)>>1)
	int ch[maxm][2],siz[maxm];
	bool pd=0;
	inline void merge(int &x,int &y){
		if(!x||!y){
			x+=y;
			return;
		}
		siz[x]+=siz[y];
		merge(ch[x][0],ch[y][0]),merge(ch[x][1],ch[y][1]);
	}
	inline void update(int x){
		if(!x) return;
		siz[x]--;
		if(siz[ch[x][1]]) update(ch[x][1]);
		else update(ch[x][0]);
	}
	inline void insert(int &x,int l,int r,int k){
		if(!x) x=++tot;
		siz[x]++;
		if(l==r) return;
		if(k<=mid){
			insert(ch[x][0],l,mid,k);
		}
		else{
			insert(ch[x][1],mid+1,r,k);
			if(!pd&&siz[ch[x][0]]){
				update(ch[x][0]);
				pd=1;
			}
		}
		if(pd) siz[x]--;
	}
}tree;
vector G;
inline int get(int x){
	return lower_bound(G.begin(),G.end(),x)-G.begin()+1;
}
inline void dfs(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		tree.merge(rt[u],rt[v]);
	}
	tree.pd=0;
	tree.insert(rt[u],1,G.size(),get(w[u]));
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		w[i]=read();
		G.push_back(w[i]);
	}
	sort(G.begin(),G.end());
	G.erase(unique(G.begin(),G.end()),G.end());
	for(int i=2;i<=n;i++){
		int v=read();
		add_edge(i,v);
		add_edge(v,i);
	}
	dfs(1,0);
	printf("%d\n",tree.siz[rt[1]]);
	return 0;
}