省选模拟 19/10/22 Gosling (括号序列) (区间DP) (重链剖分思想优化)

传送门
看上去不可做…
两个树相同,可以转换为括号序列相同
看看操作在括号序列上的体现

  1. 生长:在任意位置添加一个括号,费用为括号的权值 * c 1 c_1 c1
  2. 伸展:在一个子树的两端填加括号,费用为括号权值 * c 1 c_1 c1
  3. 收缩:删除一个括号
  4. 转换:改一个括号的权值

最后需要让两颗树的括号序列相同
首先 1,2 操作都可以看做添括号,添括号相当于在另一边删括号
考虑 d p dp dp,令 f l , r , l ′ , r ′ f_{l,r,l',r'} fl,r,l,r 表示把区间 [ l , r ] , [ l ′ , r ′ ] [l,r],[l',r'] [l,r],[l,r] 变成一样的最小代价

  1. 删括号:枚举删哪个的括号,删左右的括号
  2. 改权值:这个括号不能动了,递归处理括号内和括号外

复杂度 O ( n 1 2 n 2 2 ) O(n_1^2 n_2^2) O(n12n22)

优化:
发现求 [ l , r ] [l,r] [l,r] 的答案时, [ l , r ] [l,r] [l,r] 区间的子括号已经求出答案,要做的就是将它们合并
考虑小的合并到大的上面,对于每个点的区间来说,合并的代价是除最大的子区间外的区间和
像极了重链剖分,代价为 ∑ i ∑ s o n i s i z s o n i \sum_i\sum_{son_i}siz_{son_i} isonisizsoni s o n i son_i soni 为轻儿子
复杂度分析,一个点的子树只会在跳轻边的时候被统计,于是一个点被统计的次数就是到根的轻边个数
复杂度是 n l o g n nlogn nlogn
主要用的就是让最大的尽量少计算的思想
于是处理 [ l , r ] [l,r] [l,r] 的时候,如果左边的括号更小,就递归到左边,否则到右边,复杂度就正确了!
O ( n 1 2 n 2 l o g ( n 2 ) ) O(n_1^2n_2log(n_2)) O(n12n2log(n2))
另外把树用括号序列抽象的序列上的思想也值得学习
区间个数不多,用 h a s h hash hash 存一下即可


ps.感谢 Jun 学长把我个蒟蒻讲懂

#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1; }
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
cs int N = 4e3 + 5, P = 3e7 + 7;
struct Graph{
	int first[N], nxt[N], to[N], w[N], tot;
	void add(int x, int y, int z){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y, w[tot] = z;}
	int lp[N], rp[N], seq[N], len;
	void dfs(int u){
		for(int i = first[u]; i; i = nxt[i]){
			int t = to[i];
			seq[++len] = i; lp[i] = len;
			dfs(t);
			seq[++len] = i; rp[i] = len;
		}
	}
}A, B;
int c1, c2, n1, n2;
typedef long long ll;
ll f[P], g[P]; 
ll hash(int l, int r, int u, int v){
	return r + 103ll * l + 103ll * 103ll * u + 103ll * 103ll * 4003ll * v;
}
int find(int l, int r, int u, int v){
	ll k = hash(l, r, u, v), p = k % P;
	while(g[p] && (g[p]^k)) p = (p + 1) % P;
	return p;
}
ll dp(int l, int r, int u, int v){
	while(l <= r && (A.rp[A.seq[l]] == l || A.rp[A.seq[l]] > r)) ++l;
	while(l <= r && (A.lp[A.seq[r]] == r || A.lp[A.seq[r]] < l)) --r;
	while(u <= v && (B.rp[B.seq[u]] == u || B.rp[B.seq[u]] > v)) ++u;
	while(u <= v && (B.lp[B.seq[v]] == v || B.lp[B.seq[v]] < u)) --v;
	int now = find(l, r, u, v);
	if(g[now]) return f[now];
	g[now] = hash(l, r, u, v);
	if(l > r){
		ll sum = 0;
		for(int i = u; i <= v; i++) if(B.lp[B.seq[i]] == i && B.rp[B.seq[i]] <= v) sum += 1ll * c1 * B.w[B.seq[i]]; 
		return f[now] = sum;
	}
	if(u > v){
		ll sum = 0;
		for(int i = l; i <= r; i++) if(A.lp[A.seq[i]] == i && A.rp[A.seq[i]] <= r) sum += 1ll * c1 * A.w[A.seq[i]];
		return f[now] = sum;
	}
	ll ret = 1e18, val;
	if(B.rp[B.seq[u]] - u <= v - B.lp[B.seq[v]]){
		val = dp(l + 1, A.rp[A.seq[l]] - 1, u + 1, B.rp[B.seq[u]] - 1) + dp(A.rp[A.seq[l]] + 1, r, B.rp[B.seq[u]] + 1, v);
		ret = min(ret, val + 1ll * c2 * abs(A.w[A.seq[l]] - B.w[B.seq[u]]));
		ret = min(ret, dp(l + 1, r, u, v) + 1ll * c1 * A.w[A.seq[l]]);
		ret = min(ret, dp(l, r, u + 1, v) + 1ll * c1 * B.w[B.seq[u]]);
	}
	else{
		val = dp(l, A.lp[A.seq[r]] - 1, u, B.lp[B.seq[v]] - 1) + dp(A.lp[A.seq[r]] + 1, r - 1, B.lp[B.seq[v]] + 1, v - 1);
		ret = min(ret, val + 1ll * c2 * abs(A.w[A.seq[r]] - B.w[B.seq[v]]));
		ret = min(ret, dp(l, r - 1, u, v) + 1ll * c1 * A.w[A.seq[r]]);
		ret = min(ret, dp(l, r, u, v - 1) + 1ll * c1 * B.w[B.seq[v]]);
	} return f[now] = ret;
}
int main(){
	c1 = read(), c2 = read();
	n1 = read();
	for(int i = 1; i <= n1; i++){
		int k = read();
		while(k--){
			int x = read(), w = read();
			A.add(i, x, w);
		}
	}
	n2 = read();
	for(int i = 1; i <= n2; i++){
		int k = read();
		while(k--){
			int x = read(), w = read();
			B.add(i, x, w);
		}
	} A.dfs(1); B.dfs(1);
	cout << dp(1, A.len, 1, B.len);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值