CF1023G Pisces【数据结构优化 dp】
这仍然是最小链覆盖问题,转化为求最大反链。
然后考虑用上树的性质进行优化:条件集合 \(S\) 构成反链 \(\Leftrightarrow\) 选定一个没有条件的节点 \(rt\) 做根,对 \(rt\) 的所有儿子 \(x\) 的子树 \(\text{sub}(x)\) 与 \(S\) 的交构成反链,且存在时刻 \(T\) 满足 \(\forall(v,d)\in S\),\(d+\text{dep}_v>T\) 且 \(d-\text{dep}_v<T\)。
注意 \(T\) 不一定是整数,但是若将所有边权以及 \(d\) 变为两倍,\(T\) 就是整数了。
于是就可以 dp 了,设 \(f_{u,t}\) 表示考虑 \(u\) 的子树(除了 \(u\)),\(T=t\) 的时候的最大反链。
考虑将 \(f_u\) 合并进父亲:设贡献为 \(f'_u\),\(u\) 与父亲的连边长度为 \(l\),若 \(u\) 没有元素,那么就是 \(f_{u,i}'=\max_{j=i-l}^{i+l}f_{u,j}\),称这个操作为向上走 \(l\) 步。
那如果有元素怎么办,设条件 \((u,d)\) 上有 \(x\) 条,考虑到贡献时刻为 \([d-l+1,d+l-1]\),应当是向上走 \(1\) 步,然后 \(f_{u,d}':=\max(f_{u,d}',f_{u,d}+x)\),然后向上走 \(l-1\) 步。
dp 数组很大存不下,但都是一大段相同的,所以考虑维护差分数组。
向上走 \(l\) 步这个操作就是正数向左 \(l\) 步,负数向右 \(l\) 步,相遇的正负抵消。
所以可以用 map 存差分数组,用懒标记 \(add\) 表示向上走了多少步,用 priority_queue 维护相邻的负数和正数,按距离排序,走 \(l\) 步的时候就把距离 \(\leq 2(add+l)\) 的合并,然后 \(add:=add+l\)。
合并的时候就把差分数组加起来,所以启发式合并就可以了。
时间复杂度 \(O(n+k\log^2k)\)。
#include<bits/stdc++.h>
#define fi first
#define se second
#define PB emplace_back
using namespace std;
typedef pair<int, int> pii;
const int N = 100003;
int rd(){
int ch = getchar(), x = 0;
for(;ch < '0' || ch > '9';ch = getchar());
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
return x;
}
template<typename T>
bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
int n;
vector<pii> G[N], V[N];
struct DS {
map<int, int> ps, ng; int tag;
priority_queue<pii, vector<pii>, greater<pii> > pq;
void reb(int x, bool op){
if(op){ // positive
auto it = ng.upper_bound(x-(tag<<1));
if(it == ng.begin()) return; --it;
pq.emplace(x-it->fi, x);
// printf("pq <- %d, %d\n", it->fi, x);
} else { // negative
auto it = ps.lower_bound(x+(tag<<1));
if(it == ps.end()) return;
pq.emplace(it->fi-x, it->fi);
// printf("pq <- %d, %d\n", x, it->fi);
}
}
void ins(int x, int val){
if(val > 0){
ps[x+tag] += val;
reb(x+tag, true);
} else {
ng[x-tag] += val;
reb(x-tag, false);
}
}
int get() const {
int res = 0, now = 0;
auto i = ps.begin(), j = ng.begin();
while(i != ps.end() && j != ng.end()){
if(i->fi-j->fi>=(tag<<1)){
now += j->se; ++j;
} else {
now += i->se; ++i;
} chmax(res, now);
} return res;
}
void work(int l){
while(!pq.empty()){
pii _ = pq.top();
if(_.fi > (tag+l<<1)) break;
pq.pop();
int x = _.se, y = x-_.fi;
auto i = ps.find(x), j = ng.find(y);
if(i == ps.end() || j == ng.end()) continue;
if(i->se + j->se < 0){
j->se += i->se; ps.erase(i); reb(j->fi, false);
} else {
// printf("x = %d, y = %d\n", x, y);
i->se += j->se; ng.erase(j); reb(i->fi, true);
// printf("ng.erase(j)\n");
}
} tag += l;
}
size_t size() const {return ps.size()+ng.size();}
int qry(int p) const {
auto i = ps.find(p+tag), j = ng.find(p-tag);
return (i==ps.end()?0:i->se)+(j==ng.end()?0:j->se);
}
void operator += (const DS &o){
for(pii i : o.ps) ins(i.fi-o.tag, i.se);
for(pii i : o.ng) ins(i.fi+o.tag, i.se);
}
} S[N];
void dfs(int x, int fa){
for(pii _ : G[x]) if(_.fi != fa){
int v = _.fi, len = _.se;
dfs(v, x);
for(pii &i : V[v]) i.se -= max(0, max(-S[v].qry(i.fi), S[v].qry(i.fi+1)));
S[v].work(1);
for(pii i : V[v]) if(i.se > 0){
S[v].ins(i.fi, i.se);
S[v].ins(i.fi+1, -i.se);
}
S[v].work(len-1);
if(S[x].size() < S[v].size()) swap(S[x], S[v]);
S[x] += S[v];
}
}
int main(){
n = rd();
for(int i = 1;i < n;++ i){
int u = rd(), v = rd(), l = rd()<<1;
G[u].PB(v, l); G[v].PB(u, l);
}
int m = rd(); G[0].PB(1, 2);
while(m --){
int d = rd()<<1, f = rd(), p = rd();
V[p].PB(d, f);
} dfs(0, 0);
printf("%d\n", S[0].get());
}