题目大意
给定一棵(n)个节点的有根树,(1)号节点是根。每个点有一个参数,初始时为(0)。
你可以进行若干次操作。每次操作你指定一个节点(u)和一个整数(x)(可以为负),令(u)的子树内(包含(u))所有节点的参数加上(x)。
给你(n)个区间([l_i,r_i]),问最少进行多少次操作,可以使每个节点(i)的参数都在自己的([l_i,r_i])范围内。
数据范围:(1leq nleq 2000,-10^9leq l_ileq r_ileq 10^9)。(T)组测试数据,(Tleq 10)。
本题题解
首先,容易发现,在同一个节点上,我们最多只会进行一次操作。于是问题转化为,给每个节点确定一个“操作值”,使得【(i)及(i)的所有祖先的操作值之和】在([l_i,r_i])范围内,同时最小化“操作值”不为(0)的节点数量。
考虑树形DP。设(dp[u][x])表示,如果节点(u)的所有祖先(不包含它自己。如无特殊说明,以下所说的祖先都不包含自己,子树都包含自己)操作值之和为(x),在(u)的子树内,最少还要再进行多少次操作。显然,子树内的操作,不会影响到子树外的其他点,且这个子树内的答案只受它的祖先影响(这个影响就是(x),我们把我们无法确定的东西都写到状态里),所以这个状态很合理。
考虑转移。显然,对于(u)的所有儿子来说,它们的祖先对它们的贡献是相同的。所以可以预处理一个(g[u][y]=sum_{vin ext{son}(u)}dp[v][y])。
对于(u)的儿子来说,这个(y)是它的祖先对它的贡献之和。对(u)来说,这个(y)是它自己的“操作值”加上它祖先的操作值之和(也就是说,(y)比前面的(x),就是多了一个(u)自己的“操作值”)。并且,(y)就是(u)最终的参数,所以合法的(y)必须在([l_u,r_u])之间。于是我们能写出转移式:
可以对第二维,在(l,r)区间里的部分求前缀、后缀最小值。则转移就是(O(1))的了(具体可以见我代码)。时间复杂度(O(ncdot ext{值域}))。最终答案就是(dp[u][0])。
但是由于值域高达(10^9),这个做法目前还不可行。为了继续优化,需要用到一个观察:
引理:一定存在一种最优解,使得每个节点最终的参数(它和它所有祖先的“操作值”之和)属于集合({l_i,r_i | 1leq ileq n})。
证明可以考虑,设把所有(l_i,r_i)放在一起排序后,设这些值为(w_1dots w_{2n})。对于任意一个(x),假设(w_j<x<w_{j+1}),那么(dp[u][x])的值一定等于(dp[u][w_j])。这个好像可以归纳。
由这个引理,有一个直接的推论就是,存在一种最优解,使得所有的(x,y),都在({l_i,r_i | 1leq ileq n}cup{0})这个集合里((x)可能是(0),因为你可以认为(1)的祖先的参数是(0))。所以直接把这些点离散化,就可以(O(n^2)) DP了。
参考代码:
//problem:1012
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
const int MAXN=2000;
const int INF=1e9;
int n;
struct EDGE{int nxt,to;}edge[MAXN*2+5];
int head[MAXN+5],tot;
inline void add_edge(int u,int v){edge[++tot].nxt=head[u],edge[tot].to=v,head[u]=tot;}
struct Range_t{
int l,r;
}rg[MAXN+5];
int vals[MAXN*2+5],cnt_val;
int f[MAXN+5][MAXN*2+5],pre[MAXN*2+5],suf[MAXN*2+5];
void dfs(int u,int fa){
for(int i=1;i<=cnt_val;++i){
f[u][i]=0;
}
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==fa) continue;
dfs(v,u);
for(int j=1;j<=cnt_val;++j){
f[u][j]+=f[v][j];
}
}
int l=lob(vals+1,vals+cnt_val+1,rg[u].l)-vals;
int r=lob(vals+1,vals+cnt_val+1,rg[u].r)-vals;
pre[0]=suf[cnt_val+1]=INF;
for(int i=1;i<=cnt_val;++i){
pre[i]=min(pre[i-1],((i>=l && i<=r) ? f[u][i] : INF));
}
for(int i=cnt_val;i>=1;--i){
suf[i]=min(suf[i+1],((i>=l && i<=r) ? f[u][i] : INF));
}
for(int i=1;i<=cnt_val;++i){
f[u][i]=min(((i>=l && i<=r) ? f[u][i] : INF), min(pre[i-1],suf[i+1])+1);
}
}
void solve_case(){
cin>>n;
for(int i=1;i<=n;++i){
head[i]=0;
}
tot=0;
for(int i=1;i<n;++i){
int u,v;cin>>u>>v;
add_edge(u,v);
add_edge(v,u);
}
cnt_val=0;
for(int i=1;i<=n;++i){
cin>>rg[i].l>>rg[i].r;
vals[++cnt_val]=rg[i].l;
vals[++cnt_val]=rg[i].r;
}
vals[++cnt_val]=0;
sort(vals+1,vals+cnt_val+1);
cnt_val=unique(vals+1,vals+cnt_val+1)-(vals+1);
dfs(1,0);
// for(int i=1;i<=cnt_val;++i)cout<<vals[i]<<" ";cout<<endl;
// for(int i=1;i<=n;++i){
// for(int j=1;j<=cnt_val;++j)cout<<f[i][j]<<" ";cout<<endl;
// }
int pos=lob(vals+1,vals+cnt_val+1,0)-vals;
cout<<f[1][pos]<<endl;
}
int main() {
int T;cin>>T;while(T--)solve_case();
return 0;
}