LG5024 保卫王国
题意
给定一棵\(n\)个点的树,每个点的代价为\(a_i\),每条边至少有一个端点要被选。
\(m\)次询问,规定\(x\)和\(y\)选或不选,求覆盖整棵树的最小权值。
思路
考场上拿掉\(44pts\)就放弃了
下文中的\(u\)为子节点。
这是不是非常显然:
\(dp[x][0/1]\)表示第\(x\)个点不选/选时的最小代价
- \(dp[x][0]=dp[x][0]+dp[u][1];\)
- \(dp[x][1]=dp[x][1]+min(dp[u][0],dp[u][1]);\)
每次询问时把\(dp[x][1-tx]\)、\(dp[y][1-ty]\)变为\(INF\),\(O(n)\)再做一遍就好
那么我们发现,这样子做有许多是无用功,其实改变了的只是\(x\)到\(y\)这条路径。
进阶来了:发现有一些\(B\)类的,那么暴力跳链,再维护整棵树除了某棵子树外的答案,加起来就好了。
定义\(ndp[x][0/1]\)表示第\(x\)个点不选/选时除了\(x\)这棵子树外的最小代价(不包括\(x\)点)
- \(ndp[u][0]=ndp[x][1]+dp[x][1]-min(dp[u][0],dp[u][1]);\)
- \(ndp[u][1]=min(ndp[u][0],ndp[x][0]+dp[x][0]-dp[u][1]);\)
接下来考虑瓶颈:跳链的部分如何来优化
其实是可以倍增的
\(Dp[x][i][p][q]\)表示从第\(x\)个点(状态为\(p\))到\(x\)的\(2^i\)个父亲(状态为\(q\))的答案
注意这个是父亲的子树-\(x\)的子树的答案,因为这样转移时方便,且最后对\(sum_链+dp[x][tx]+dp[y][ty]+ndp[lca][0/1]\)讨论就好
转移枚举中间点的情况:
\(Dp[j][i][p][q]=min(Dp[j][i-1][p][0]+Dp[f][i-1][0][q],Dp[j][i-1][p][1]+Dp[f][i-1][1][q]);\)
最后求答案,基本跟倍增求\(lca\)一样,看代码吧(暴露懒的本性)
#include <bits/stdc++.h>
using std::min;
using std::swap;
const long long INF=10000000000;
const int N=100005;
int to[N<<1],edge,last[N],Next[N<<1],a[N],deep[N],n,x,y,tx,ty,m;
char s[3];
long long dp[N][2],ndp[N][2],fa[N][21],Dp[N][21][2][2],fx[2][2],fy[2][2];
void add(int x,int y){
to[++edge]=y;
Next[edge]=last[x];
last[x]=edge;
}
void dfs(int x){
dp[x][1]=a[x];
for (int i=last[x];i;i=Next[i])
if (to[i]!=fa[x][0]){
int u=to[i];
fa[u][0]=x,deep[u]=deep[x]+1;
dfs(to[i]);
dp[x][0]=dp[x][0]+dp[u][1];
dp[x][1]=dp[x][1]+min(dp[u][0],dp[u][1]);
}
}
void dfs2(int x){
for (int i=last[x];i;i=Next[i])
if (to[i]!=fa[x][0]){
int u=to[i];
ndp[u][0]=ndp[x][1]+dp[x][1]-min(dp[u][0],dp[u][1]);
ndp[u][1]=min(ndp[u][0],ndp[x][0]+dp[x][0]-dp[u][1]);
dfs2(u);
}
}
void pre(){
memset(Dp,0x3f,sizeof(Dp));
for (int i=1;i<=n;i++){
int f=fa[i][0];
Dp[i][0][0][1]=dp[f][1]-min(dp[i][0],dp[i][1]);
Dp[i][0][1][0]=dp[f][0]-dp[i][1];
Dp[i][0][1][1]=dp[f][1]-min(dp[i][0],dp[i][1]);
}
for (int i=1;i<=20;i++)
for (int j=1;j<=n;j++){
int f=fa[j][i-1];
fa[j][i]=fa[fa[j][i-1]][i-1];
for (int x=0;x<=1;x++)
for (int y=0;y<=1;y++)
Dp[j][i][x][y]=min(Dp[j][i-1][x][0]+Dp[f][i-1][0][y],Dp[j][i-1][x][1]+Dp[f][i-1][1][y]);
}
}
long long solve(int x,int tx,int y,int ty){
if (deep[x]<deep[y])
swap(x,y),swap(tx,ty);
int flag=1;
fx[flag][1-tx]=INF,fx[flag][tx]=dp[x][tx];
long long sum;
for (int i=20;i>=0;i--)
if (deep[fa[x][i]]>=deep[y]){
int f=fa[x][i];
flag=1-flag;
fx[flag][0]=min(fx[1-flag][0]+Dp[x][i][0][0],fx[1-flag][1]+Dp[x][i][1][0]);
fx[flag][1]=min(fx[1-flag][0]+Dp[x][i][0][1],fx[1-flag][1]+Dp[x][i][1][1]);
x=fa[x][i];
}
if (x==y) return fx[flag][ty]+ndp[y][ty];
fy[flag][1-ty]=INF,fy[flag][ty]=dp[y][ty];
for (int i=20;i>=0;i--)
if (fa[x][i]!=fa[y][i]){
int f=fa[x][i];
flag=1-flag;
fx[flag][0]=min(fx[1-flag][0]+Dp[x][i][0][0],fx[1-flag][1]+Dp[x][i][1][0]);
fx[flag][1]=min(fx[1-flag][0]+Dp[x][i][0][1],fx[1-flag][1]+Dp[x][i][1][1]);
x=fa[x][i];
f=fa[y][i];
fy[flag][0]=min(fy[1-flag][0]+Dp[y][i][0][0],fy[1-flag][1]+Dp[y][i][1][0]);
fy[flag][1]=min(fy[1-flag][0]+Dp[y][i][0][1],fy[1-flag][1]+Dp[y][i][1][1]);
y=fa[y][i];
}
int f=fa[x][0];
long long sum0=fx[flag][1]+fy[flag][1]+dp[f][0]-dp[x][1]-dp[y][1];
long long sum1=min(fx[flag][1],fx[flag][0])+min(fy[flag][0],fy[flag][1])+dp[f][1]-min(dp[x][0],dp[x][1])-min(dp[y][0],dp[y][1]);
return min(sum0+ndp[f][0],sum1+ndp[f][1]);
}
int main(){
scanf("%d%d%",&n,&m);
gets(s);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
deep[1]=1;
dfs(1);
dfs2(1);
pre();
for (int i=1;i<=m;i++){
scanf("%d%d%d%d",&x,&tx,&y,&ty);
long long ans=solve(x,tx,y,ty);
if (ans>=INF) puts("-1");
else printf("%lld\n",ans);
}
}
后记
或许我的\(blog\)已经咕了两个月了(可能下一次就是一百年后了),觉得我退化了啊,比去年还菜了啊
* 生而自由 爱而无畏 *