HDU6866 Linuber File System
題目大意
給定一棵\(n\)個節點的有根樹,\(1\)號節點是根。每個點有一個引數,初始時為\(0\)。
你可以進行若干次操作。每次操作你指定一個節點\(u\)和一個整數\(x\)(可以為負),令\(u\)的子樹內(包含\(u\))所有節點的引數加上\(x\)。
給你\(n\)個區間\([l_i,r_i]\),問最少進行多少次操作,可以使每個節點\(i\)的引數都在自己的\([l_i,r_i]\)範圍內。
資料範圍:\(1\leq n\leq 2000,-10^9\leq l_i\leq r_i\leq 10^9\)。\(T\)組測試資料,\(T\leq 10\)。
本題題解
首先,容易發現,在同一個節點上,我們最多隻會進行一次
考慮樹形DP。設\(dp[u][x]\)表示,如果節點\(u\)的所有祖先(不包含它自己。如無特殊說明,以下所說的祖先都不包含自己,子樹都包含自己)操作值之和為\(x\),在\(u\)的子樹內,最少還要再進行多少次操作。顯然,子樹內的操作,不會影響到子樹外的其他點,且這個子樹內的答案只受它的祖先影響(這個影響就是\(x\),我們把我們無法確定的東西都寫到狀態裡),所以這個狀態很合理。
考慮轉移。顯然,對於\(u\)
對於\(u\)的兒子來說,這個\(y\)是它的祖先對它的貢獻之和。對\(u\)來說,這個\(y\)是它自己的“操作值”加上它祖先的操作值之和(也就是說,\(y\)比前面的\(x\),就是多了一個\(u\)自己的“操作值”)。並且,\(y\)就是\(u\)最終的引數,所以合法的\(y\)必須在\([l_u,r_u]\)之間。於是我們能寫出轉移式:
\[dp[u][x]=\min_{l_u\leq y\leq r_u}\{g[u][y] + [y\neq x]\} \]
可以對第二維,在\(l,r\)區間裡的部分求字首、字尾最小值。則轉移就是\(O(1)\)的了(具體可以見我程式碼)。時間複雜度\(O(n\cdot \text{值域})\)。最終答案就是\(dp[u][0]\)。
但是由於值域高達\(10^9\),這個做法目前還不可行。為了繼續優化,需要用到一個觀察:
引理:一定存在一種最優解,使得每個節點最終的引數(它和它所有祖先的“操作值”之和)屬於集合\(\{l_i,r_i | 1\leq i\leq n\}\)。
證明可以考慮,設把所有\(l_i,r_i\)放在一起排序後,設這些值為\(w_1\dots w_{2n}\)。對於任意一個\(x\),假設\(w_j<x<w_{j+1}\),那麼\(dp[u][x]\)的值一定等於\(dp[u][w_j]\)。這個好像可以歸納。
由這個引理,有一個直接的推論就是,存在一種最優解,使得所有的\(x,y\),都在\(\{l_i,r_i | 1\leq i\leq 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;
}