Contest Hunter 模拟赛09 C [树形dp+差分]

题面

传送门

思路

又双叒叕是一道差分题我没想出来......记录一下

首先这个“所有祖先都比自己小”等价于“父亲比自己小”

这题的基础dp方程很显然,$dp[u][i]$表示当前在点$u$,且点$u$的值是$i$的时候最小修改几个点

然后我们发现每个$dp[u]$可以取到的有意义的值只有几个,所以我们考虑开一个$set$来维护这些取值和它们对应的$dp$值

这样并不方便转移:我们发现可以把$dp$值差分一下,每个$set$元素维护取值,以及这个取值的$dp$值和上一个取值的$dp$值之间的差——显然这个是单调递增的,否则会不优

然后发现我们可以启发式合并子树的$set$,然后针对当前点$dp$,返回的$dp$值需要保证所有儿子的$set$的最大值都大于这个$dp$值

然后就发现做完了,因为用了启发式合并,总复杂度为$O(nlog ^2 n)$

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<set>
#define ll long long
using namespace std;
inline int read(){
	int re=0,flag=1;char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') flag=-1;
		ch=getchar();
	}
	while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
	return re*flag;
}
int n,first[200010],cnte=-1;
struct edge{
	int to,next;
}a[400010];int w[200010];
bool fl[200010];
multiset<int>s[200010];
inline void add(int u,int v){
	a[++cnte]=(edge){v,first[u]};first[u]=cnte;
	a[++cnte]=(edge){u,first[v]};first[v]=cnte;
}
void dfs(int u,int f){
	int i,v;fl[u]=1;
	multiset<int>::iterator it;
	for(i=first[u];~i;i=a[i].next){
		v=a[i].to;if(v==f) continue;
		dfs(v,u);fl[u]=fl[u]&&fl[v];
		if(w[u]>w[v]) fl[u]=0;
		if(s[u].size()<s[v].size()) s[u].swap(s[v]);
		for(auto x:s[v]) s[u].insert(x);
		s[v].clear();
	}
//	cout<<"finish dfs "<<u<<' '<<fl[u]<<'
';
	s[u].insert(1);
	it=s[u].upper_bound(w[u]);it--;
	s[u].erase(it);
	s[u].insert(w[u]+1);
}
int main(){
	memset(first,-1,sizeof(first));
	n=read();int i,t1,t2;
	for(i=1;i<=n;i++) w[i]=read();
	for(i=1;i<n;i++){
		t1=read();t2=read();
		add(t1,t2);
	}
	dfs(1,0);
	int minn=*s[1].begin(),ans=0;
	if(fl[1]==1){puts("0");return 0;}
	for(auto x:s[1]) if(x==minn) ans++; else break;
	cout<<ans<<'
';
}
原文地址:https://www.cnblogs.com/dedicatus545/p/10575024.html