*题解:P5024 [NOIP 2018 提高组] 保卫王国
解析
这里介绍倍增的做法。
考虑如果没有要求时怎么做。设 \(f_{i,j}\) 表示以 \(i\) 为根的子树中,点 \(i\) 颜色为 \(j\) 时的最小花费。于是有:
考虑钦定一个点时怎么做。由于修改一个点会影响其到根的这条链,所以考虑从上往下 dp。设 \(g_{i,j}\) 表示当点 \(i\) 的颜色为 \(j\) 时,总体最小花费,讨论该点与父结点颜色,则有:
接下来考虑钦定两个点,当这两个点相邻时(即性质 2)怎么做。设这两个点为 \(u\) 和 \(fa_u\),这两个点的颜色不会影响 \(u\) 的子树,所以不妨把 \(fa_u\) 的子树直接挖掉,设 \(h_{i,j}\) 表示点 \(fa_i\) 的颜色为 \(j\) 且点 \(i\) 的子树被挖掉时的最小花费,那么有:
处理完 \(fa_u\) 的子树后再进行合并。对于 \(fa_u\) 的子树内部,讨论 \(u\) 与 \(fa_u\) 的染色情况即可。
再来就是钦定两个点但这两个点在其中一个点到根的链上的情况。此时两点之间会有其余点,我们需要通过这些点将限制传递。设 \(v_{i,x,k,l}\) 表示在从 \(i\) 点往上走 \(x\) 步到达的点 \(j\) 的子树中,点 \(i\) 的颜色为 \(k\),点 \(j\) 的颜色为 \(l\) 的最小花费。然而状态数太多,所以改为倍增跳,点 \(j\) 变为从 \(i\) 点往上走 \(2 ^ x - 1\) 步到达的节点。如果设 \(fa_{i,j}\) 表示点 \(i\) 的 \(2^j\) 级祖先,则有:
然后就变成性质 2 了。
接下来轮到正解。两点 \(a,b\) 可能不在一条到根的链上,聪明的你肯定想到了挖掉以 LCA 为根的子树。对于 \(a\to b\) 的链,将 \(v\) 的信息在 LCA 处合并即可。
时间复杂度 \(O((n + m) \log n )\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100000 + 5,M = 17;
typedef long long ll;
typedef pair<int,int> pii;
ll f[N][2],g[N][2],h[N][2],v[N][M][2][2],h2[N][2];
int fa[N][M],dep[N],ffa[N][M];
int p[N];
int n,m;
vector<int> t[N];
void dfs(int x,int _f){
dep[x] = dep[_f] + 1;
fa[x][0] = _f;
ffa[x][0] = x;
ffa[x][1] = _f;
for(int i=1;i<M;i++){
fa[x][i] = fa[fa[x][i - 1]][i - 1];
ffa[x][i] = ffa[fa[ffa[x][i - 1]][0]][i - 1];//x 的 (2^i) - 1 级祖先
}
f[x][1] = p[x];
for(int nx : t[x])if(nx != _f){
dfs(nx,x);
f[x][0] += f[nx][1];
f[x][1] += min(f[nx][0],f[nx][1]);
}
}
void dfs2(int x){
int ff = fa[x][0];
g[x][0] = g[ff][1] - min(f[x][0],f[x][1]) + f[x][0];
g[x][1] = min(g[ff][1] - min(f[x][0],f[x][1]) + f[x][1],g[ff][0] - f[x][1] + f[x][1]);
h[x][0] = g[ff][0] - f[x][1];
h[x][1] = g[ff][1] - min(f[x][0],f[x][1]);
h2[x][0] = f[x][1];
h2[x][1] = min(f[x][0],f[x][1]);
if(x == 1){
g[x][0] = f[x][0];
g[x][1] = f[x][1];
h[x][0] = 0;
h[x][1] = 0;
h2[x][0] = f[x][0];
}
for(int nx : t[x])if(nx != ff){
dfs2(nx);
}
}
void init(){
for(int i=1;i<=n;i++){
v[i][0][0][0] = f[i][0];
v[i][0][1][1] = f[i][1];
v[i][0][1][0] = v[i][0][0][1] = 2e10;
}
for(int j=1;j<M;j++){
for(int i=1;i<=n;i++)if(ffa[i][j]){
for(int x=0;x<=1;x++){
for(int y=0;y<=1;y++){
int f0 = fa[i][j - 1],f1 = ffa[i][j - 1];
ll l0 = v[i][j - 1][x][0],l1 = v[i][j - 1][x][1],r0 = v[f0][j - 1][0][y],r1 = v[f0][j - 1][1][y];
v[i][j][x][y] = min({(ll)2e10,l0 + r1 - h2[f1][1],
l1 + r1 - h2[f1][1],
l1 + r0 - h2[f1][0]});
}
}
}
}
}
struct S{
ll r0,r1,lca;
};
void upd(ll res[],int u,int i){
ll r1 = res[1],r0 = res[0];
int fu = fa[u][0];
res[1] = min(min(r0,r1) + v[fu][i][1][1] - h2[u][1],r1 + v[fu][i][0][1] - h2[u][0]);
res[0] = min(min(r0,r1) + v[fu][i][1][0] - h2[u][1],r1 + v[fu][i][0][0] - h2[u][0]);
}
S get(int a,int x,int b,int y){
if(dep[a] > dep[b]){
swap(a,b);
swap(x,y);
}
ll resa[2] = {v[a][0][0][0],v[a][0][1][1]},resb[2] = {v[b][0][0][0],v[b][0][1][1]};
resa[x ^ 1] = resb[y ^ 1] = 2e10;
for(int i=M - 1;i>=0;i--){
if(dep[fa[b][i]] >= dep[a]){
upd(resb,b,i);
b = fa[b][i];
}
}
if(a == b){
return {resb[x],resb[x],a};
}
for(int i=M - 1;i>=0;i--){
if(fa[a][i] != fa[b][i]){
upd(resb,b,i);
upd(resa,a,i);
a = fa[a][i],b = fa[b][i];
}
}
upd(resa,a,0);
ll res[2] = {resa[0] + resb[1] - h2[b][0],min(resa[1] + resb[0] - h2[b][1],resa[1] + resb[1] - h2[b][1])};
return {res[0],res[1],fa[a][0]};
}
ll sol(int a,int x,int b,int y){
if(dep[a] > dep[b]){
swap(a,b);
swap(x,y);
}
S res = get(a,x,b,y);
ll r = 2e10;
if(res.lca == a){
if(x == 1){
return min(h[res.lca][0] + res.r1,h[res.lca][1] + res.r1);
}else{
return h[res.lca][1] + res.r0;
}
}
return min({h[res.lca][1] + res.r0,h[res.lca][0] + res.r1,h[res.lca][1] + res.r1});
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
string type;
cin>>n>>m>>type;
for(int i=1;i<=n;i++){
cin>>p[i];
}
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
t[u].push_back(v);
t[v].push_back(u);
}
dfs(1,0);
dfs2(1);
init();
while(m--){
int a,x,b,y;
cin>>a>>x>>b>>y;
ll res = sol(a,x,b,y);
cout<<(res > 1e10 ? -1 : res)<<'\n';
}
return 0;
}

浙公网安备 33010602011771号