传送门 to Luogu.
真的降智了,那么明显的性质都没有被发现......
一眼树 \(\rm DP\) ?考虑设计状态 \(f(u,j)\) 表示在 \(j\) 时刻割掉 \(u\) 与其父亲的边的最大收益。那么我们就有转移式子:
\[f(u, j)=w_u[d_u=j]+\sum_{v\in son_u} \max_{k=0}^j f(v, k) \]单次转移是 \(\mathcal O(k)\) 的,那么总复杂度就是 \(\mathcal O(nk^2)\) 了。
但是,我们发现该转移有一个东西 —— 前缀取最大值,并且我们显然可以通过更改状态定义简化转移,因而,我们更改状态定义为 \(f(u, j)\) 表示在小于等于 \(j\) 时刻去掉 \(u\) 与其父亲的连边,能获得的最大收益,此时状转就变成了
\[f(u, j)=\max\left\{f(u, j-1), w_u[d_u=j]+\sum_{v\in son_u} f(v, j)\right\} \]再做复杂度就是 \(\mathcal O(nk)\) 的了。
对题目性质进行一些观察,首先,我们单考虑一条链,肯定是找 \(\rm LIS\),这不必说。
如果在树上,假设我们在 \(t\) 时刻切掉 \(u\) 与其父亲的连边,那么,我们能在 \(u\) 子树中选择的,就是那些 \(d\le t\) 的点了,即,时间越晚,能够选择的点越多,又即,切边时间 \(t\) 与收益存在正比的关系,也即,\(f(u, j)\) 与 \(j\) 存在正比关系。
显然,该算法瓶颈在于转移,我们试图简化转移,根据上面的 \(f(u, j)\) 与 \(j\) 成正比增长,对于一个固定点 \(u\),我们存在推论:
\[\sum_{v\in son_u} f(v, j_1)\le \sum_{v\in son_u}f(v, j_2)\quad (j_1<j_2) \]既然时间越大,儿子的函数值求和越大,那么我们是否可以继续将我们的转移去掉与前缀比最大?即将转移改写为:
\[f(u, j)=w_u[d_u=j]+\sum_{v\in son_u} f(v, j) \]这显然不对,问题在于当 \(j=d_u\) 时,改时刻对应函数值不只是儿子求和,另多一项 \(w_u\),不过,我们可以在 \(j=d_u\) 时刻做一个分界线:
并且,这个 \(\max\) 的真正含义似乎不必再单纯理解为是前缀最大了,由于儿子求和具有单调性,那么当 \(d_u+1<j_1<j_2\) 时,显然存在 \(j_1\) 时刻儿子求和大于 \(j_2\) 时刻儿子求和,而我们 \(\max\) 想要的作用,事实上是将 \(w_u+\sum f(v, d_u)\) 传递到后面,即,将 \(w_u+\sum f(v, d_u)\) 与 \(f(u, j)(j\in [d_u, k])\) 的每个元素取最大值。
那么我们现在再来看看转移:
不难发现每一项都有 \(\sum f(v,j)\),我们可以先将每一项都赋值为 \(\sum f(v,j)\),然后再将 \([d_u, k]\) 中每一项与 \(w_u+\sum f(v, d_u)\) 取最大值。而这可以使用线段树合并在 \(\mathcal O(\log n)\) 的时间内解决。
所以,总复杂度 \(\mathcal O(n\log n)\). 需要实现线段树合并。
来自神 _LPF_ 的代码细节提示:
不考虑使用普通线段树的区间赋值标记(比较难合并),而是记录下区间 \(\min,\max\),如果一个区间 \(\min=\max\),则说明该区间所有元素都相同,
合并时直接被合并,更新时直接删掉左右儿子,修改时添加左右儿子,就相当于有了区间赋值的作用了。
就没有了。
#include<cstdio> #include<algorithm> #include<vector> #include<cstring> using namespace std; #define NDEBUG #include<cassert> namespace Elaina{ #define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i) #define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i) #define fi first #define se second #define mp(a, b) make_pair(a, b) #define Endl putchar('\n') #define mmset(a, b) memset(a, b, sizeof a) // #define int long long typedef long long ll; typedef unsigned long long ull; typedef pair<int, int> pii; typedef pair<ll, ll> pll; template<class T>inline T fab(T x){ return x<0? -x: x; } template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); } template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); } template<class T>inline T readin(T x){ x=0; int f=0; char c; while((c=getchar())<'0' || '9'<c) if(c=='-') f=1; for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48)); return f? -x: x; } template<class T>inline void writc(T x, char s='\n'){ static int fwri_sta[1005], fwri_ed=0; if(x<0) putchar('-'), x=-x; do fwri_sta[++fwri_ed]=x%10, x/=10; while(x); while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed); putchar(s); } } using namespace Elaina; const int maxn=1e5; const int logn=30; const ll inf=1ll<<50; vector<int>Delshack; int rt[maxn+5], ncnt; struct node{ int ls, rs; ll mx, mn, tag; }tre[maxn*logn+5]; inline int apply(){ int ret; if(!Delshack.empty()){ ret=Delshack.back(); Delshack.pop_back(); } else ret=++ncnt; return ret; } inline void collapse(int& u){ if(!u) return; tre[u].ls=tre[u].rs=0; tre[u].mx=tre[u].mn=tre[u].tag=0; Delshack.push_back(u); u=0; } inline void add(int u, ll val){ if(!u) return; tre[u].mx+=val, tre[u].mn+=val; tre[u].tag+=val; } inline void pushdown(int u){ if(!tre[u].tag) return; add(tre[u].ls, tre[u].tag); add(tre[u].rs, tre[u].tag); tre[u].tag=0; } inline void pushup(int u){ tre[u].mx=max(tre[tre[u].ls].mx, tre[tre[u].rs].mx); tre[u].mn=min(tre[tre[u].ls].mn, tre[tre[u].rs].mn); if(tre[u].mx==tre[u].mn) collapse(tre[u].ls), collapse(tre[u].rs); } inline bool same(int u){ return tre[u].mx==tre[u].mn; } int merge(int u, int v){ if(!u || !v) return u|v; if(same(u)) return add(v, tre[u].mx), v; if(same(v)) return add(u, tre[v].mx), u; pushdown(u), pushdown(v); tre[u].ls=merge(tre[u].ls, tre[v].ls); tre[u].rs=merge(tre[u].rs, tre[v].rs); return pushup(u), u; } ll query(int u, int l, int r, int pos){ // printf("query:> u == %d, l == %d, r == %d, pos == %d\n", u, l, r, pos); if(same(u)) return tre[u].mx; pushdown(u); int mid=(l+r)>>1; if(pos<=mid) return query(tre[u].ls, l, mid, pos); else return query(tre[u].rs, mid+1, r, pos); } void modify(int u, int l, int r, int L, int R, ll x){ if(tre[u].mn>=x) return; if(L<=l && r<=R && tre[u].mx<=x){ tre[u].mx=tre[u].mn=x, tre[u].tag=0; collapse(tre[u].ls), collapse(tre[u].rs); return; } pushdown(u); if(same(u)){ tre[u].ls=apply(), tre[u].rs=apply(); tre[tre[u].ls].mn=tre[tre[u].rs].mn=tre[u].mn; tre[tre[u].ls].mx=tre[tre[u].rs].mx=tre[u].mx; } int mid=(l+r)>>1; if(L<=mid) modify(tre[u].ls, l, mid, L, R, x); if(mid<R) modify(tre[u].rs, mid+1, r, L, R, x); pushup(u); } void print(int u, int l, int r){ printf("tre[%d] [%d, %d] :> ls == %d, rs == %d, mx == %lld, mn == %lld\n", u, l, r, tre[u].ls, tre[u].rs, tre[u].mx, tre[u].mn); if(same(u)) return; print(tre[u].ls, l, (l+r)>>1), print(tre[u].rs, ((l+r)>>1)+1, r); } vector<int>g[maxn+5]; int d[maxn+5], w[maxn+5]; int n, m, k; inline void add_edge(int u, int v){ g[u].push_back(v); } inline void input(){ n=readin(1), m=readin(1), k=readin(1); int par; for(int i=2; i<=n; ++i){ par=readin(1); add_edge(par, i); } for(int i=1; i<=m; ++i){ par=readin(1); d[par]=readin(1), w[par]=readin(1); } } void dfs(int u){ // printf("Now u == %d\n", u); rt[u]=apply(); for(int v: g[u]) dfs(v), rt[u]=merge(rt[u], rt[v]); if(d[u]){ ll val=query(rt[u], 1, k, d[u]); // printf("val == %lld\n", val); modify(rt[u], 1, k, d[u], k, val+w[u]); } // printf("<---------------- the tre of node %d ---------------->\n", u); // print(rt[u], 1, k); // Endl; Endl; } signed main(){ input(); dfs(1); writc(tre[rt[1]].mx); return 0; }
挖掘出 \(f(u, j)\) 具有单调性是最重要的。