传送门
看上去不可做…
两个树相同,可以转换为括号序列相同
看看操作在括号序列上的体现
- 生长:在任意位置添加一个括号,费用为括号的权值 * c 1 c_1 c1
- 伸展:在一个子树的两端填加括号,费用为括号权值 * c 1 c_1 c1
- 收缩:删除一个括号
- 转换:改一个括号的权值
最后需要让两颗树的括号序列相同
首先 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′] 变成一样的最小代价
- 删括号:枚举删哪个的括号,删左右的括号
- 改权值:这个括号不能动了,递归处理括号内和括号外
复杂度 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}
∑i∑sonisizsoni,
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;
}