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());
}
posted @ 2021-06-20 18:03  mizu164  阅读(311)  评论(0编辑  收藏  举报