NOIP2018 保卫王国 题解
\(\Huge\mathsf{Solution\text{ }For\text{ }NOIP2018\text{ }Day2\text{ }T3}\)
题意简述
给出一棵 \(N\) 个节点的树, 选取每一个节点都有一个固定花费,每一条边连接的两个点中必选 \(1\) 个,现在钦定 \(2\) 个节点的选/不选状态,问在这种状态下选取的最小花费是多少,若该状态下不能满足条件输出 \(-1\) .
朴素暴力
先不管钦定的点,现在就是要求在满足每一条边连接的两个节点中必选一个的条件下的最小花费.
树形动规直接做 复杂度 \(O(N)\) ,用 \(dp[u][1]\) 表示节点 \(u\) 被选择时子树 \(u\) 的最小花费,方程如下:
\(\Large\begin{cases}dp[u][0]=\sum\limits_{v\in son[u]} dp[v][1]\\dp[u][1]=\sum\limits_{v\in son[u]} min(dp[v][0],dp[v][1])\end{cases}\)
然后考虑钦定的两个点 \(a,b\)
因为 \(a,b\) 状态确定,假如 \(a\) 被规定必须选入,那么直接 \(dp[a][0]=+\infty\) (即不允许选入状态 \(dp[a][0]\) )
所以直接可以 \(O(nm)\) 暴力搞 \(44\) 分.
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int maxn=100007;
const int inf=0x7f7f7f7f;
struct E{
int u,v;
}e[maxn<<1];
int first[maxn],nt[maxn<<1],ES=1;
inline void addE(int u,int v)
{
e[++ES]=(E){u,v};
nt[ES]=first[u];
first[u]=ES;
return ;
}
inline int R()
{
char c;
int re;
while((c=getchar())>'9'||c<'0');
re=c-48;
while((c=getchar())>='0'&&c<='9')
re=re*10+c-48;
return re;
}
int N,M;
LL F[maxn][2],cost[maxn];
char qwq[7];
inline void dfs(int u,int fa)
{
if(F[u][1]!=inf) F[u][1]=cost[u];
int v;
for(register int i=first[u];i;i=nt[i])
{
v=e[i].v;
if(v!=fa)
{
dfs(v,u);
F[u][0]+=F[v][1];
F[u][1]+=min(F[v][0],F[v][1]);
}
}
return ;
}
int main()
{
N=R();M=R();
scanf("%s",qwq);
for(register int i=1;i<=N;i++)
cost[i]=R();
int u,v,x,y;
LL ans;
for(register int i=1;i<N;i++)
{
u=R();v=R();
addE(u,v);addE(v,u);
}
for(register int i=1;i<=M;i++)
{
u=R();x=R();v=R();y=R();
memset(F,0,sizeof(F));
F[u][x^1]=F[v][y^1]=inf;
dfs(1,0);
ans=min(F[1][0],F[1][1]);
if(ans>=inf) puts("-1");
else printf("%lld\n",ans);
}
return 0;
}
tip
暴力搞了之后你会发现一件事情,那就是这棵树并不是每次钦定节点后所有节点都会发生变化,观察给出的转移方程可以知道,节点 \(a,b\) 的改变影响的范围就是二者所有的祖先,也就是 \(a->LCA(a,b)\) 和 \(b->LCA(a,b)\) 以及 \(LCA(a,b)->root\) 三个部分
那我们是不是可以拿这个性质尝试去搞点不一样的?
首先你会想到去优化暴力, 原来的暴力是直接整棵树重新DP一次, 发现了这个性质就可以改为只重新计算 \(a->LCA(a,b)\) \(b->LCA(a,b)\) \(LCA(a,b)->root\) 三个部分的值. 可以优化常数,但是肯定是过不去的.
暴力重新计算比较劣的地方就在于重新计算的时候只能一级一级往上推算, 时间是 \(O(n)\) 的.
考虑是不是能用倍增来把他优化到 \(O(logn)\) ?
正解
设计状态
令 \(F[u][i][1/0][1/0]\) 来表示节点 \(u\) 选/不选 以及向上 \(2^i\) 级祖先 选/不选 时, 子树 \(fa[u][i]\) 中 除去子树 \(u\) 的最小代价
为什么要除去子树 \(u\) , 因为我们是要利用 \(F\) 数组进行计算, 所以当点 \(u\) 被钦定的时候如果其父亲的 \(F\) 值中包含了自己的成分, 转移将会相当麻烦 (因为这时候你也不知道 \(u\) 是怎么转移到父亲的, 而且还要考虑向上的多级祖先的转移)
令 \(fa[u][i]\) 表示节点 \(u\) 向上第 \(2^i\) 级祖先
初始化及状态转移
则 \(F\) 数组的初值设定:
这时候最开始暴力的树形DP可以捞出来用一用了, 还是设 \(dp[u][0/1]\) 为点 \(u\) 取/不取 时子树 \(u\) 的最小花费
那么
\(\large\begin{cases}dp[u][0]=\sum\limits_{v\in son[u]} dp[v][1]\\dp[u][1]=\sum\limits_{v\in son[u]} min(dp[v][0],dp[v][1])\end{cases}\)
\(\large\begin{cases}F[u][0][0][0]=+\infty(\text{不合法})\\F[u][0][1][0]=dp[fa[u][0]][0]-dp[u][1]\\F[u][0][1][1]=F[u][0][0][1]=dp[fa[u][0]][1]-min(dp[u][0],dp[u][1])\end{cases}\)
然后预先处理一次(相当于没有钦定的点时的状态), 方程:
令 \(t=fa[u][i-1]\)
\(\begin{cases}F[u][i][0][0]=min(F[u][i-1][0][0]+F[t][i-1][0][0],F[u][i-1][0][1]+F[t][i-1][1][0])\\F[u][i][0][1]=min(F[u][i-1][0][0]+F[t][i-1][0][1],F[u][i-1][0][1]+F[t][i-1][1][1])\\F[u][i][1][0]=min(F[u][i-1][1][0]+F[t][i-1][0][0],F[u][i-1][1][1]+F[t][i-1][1][0])\\F[u][i][1][1]=min(F[u][i-1][1][0]+F[t][i-1][0][1],F[u][i-1][1][1]+F[t][i-1][1][1])\end{cases}\)
(实际上就是考虑向上第 \(2^{i-1}\) 级祖先 取/不取 )
接下来实际上就是考虑对于每一组钦定的点如何计算新的贡献了.
计算答案
实际上按照上面处理完了答案是很好计算的,暴力的方程依然可以拿出来用,由于我们倍增数组都处理好了,直接往上跳计算就可以。
策略:
对于给定的点 \(u\)、 \(v\) ,我们设 \(depth[v]\le depth[u]\) 然后先把 \(u\) 跳到和 \(v\) 同深度,判断 \(u\) \(v\) 是否重合(存在 \(v\) 就是 \(u\) 祖先的可能),如果重合,那么令 \(lca=u\) ,否则将 \(u\)、\(v\) 一起跳到父亲重合,然后令 \(lca=fa[u][0]\) ,接下来就是把 \(lca\) 跳到根节点即可。
详细过程:
现在假使我们要把点 \(x\) 跳到根节点(并计算沿途数据),假设我们钦定 \(x\) 点必取
设 \(x_0\) 为当我们决定将 \(x\) 跳到 \(fa[x][i]\) 时,\(fa[x][i]\) 不取的最小花费
设 \(x_1\) 为当我们决定将 \(x\) 跳到 \(fa[x][i]\) 时,选取 \(fa[x][i]\) 的最小花费
初始化为 \(x_0=+\infty,x_1=dp[x][1]\) (要是钦定 \(x\) 点必不取则 \(x_0=dp[x][0],x_1=+\infty\))
那么转移如下:
令 \(t_0=x_0,t_1=x_1\) ,则
\(x_0=min(t_0+F[u][fa[u][i]][0][0],t_1+F[u][fa[u][i]][1][0])\)
\(x_1=min(t_0+F[u][fa[u][i]][0][1],t_1+F[u][fa[u][i]][1][1])\)
最后的结果就是 \(min(x_1,x_0)\)
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int maxn=100007;
const int lim=17;
const LL inf=1e10;
struct E{
int u,v;
}e[maxn<<1];
int first[maxn],nt[maxn<<1],ES;
inline void addE(int u,int v)
{
e[++ES]=(E){u,v};
nt[ES]=first[u];
first[u]=ES;
return ;
}
inline int R()
{
char c;
int re;
while((c=getchar())>'9'||c<'0');
re=c-48;
while((c=getchar())>='0'&&c<='9')
re=re*10+c-48;
return re;
}
int N,M;
LL F[maxn][lim+1][2][2],dp[maxn][2],cost[maxn];
int fa[maxn][lim+1],depth[maxn];
inline void dfs(int u)
{
int v;
dp[u][1]=cost[u];
for(int i=1;i<=lim;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=first[u];i;i=nt[i])
{
v=e[i].v;
if(v!=fa[u][0])
{
fa[v][0]=u;
depth[v]=depth[u]+1;
dfs(v);
dp[u][0]+=dp[v][1];
dp[u][1]+=min(dp[v][0],dp[v][1]);
}
}
return ;
}
inline LL cal(int u,int x,int v,int y)
{
LL u0,u1,v0,v1,l0,l1,t0,t1,l;
if(depth[u]<depth[v]) swap(u,v),swap(x,y);
if(x) {u0=inf;u1=dp[u][1];}
else {u1=inf;u0=dp[u][0];}
if(y) {v0=inf;v1=dp[v][1];}
else {v1=inf;v0=dp[v][0];}
for(int i=lim;i>=0;i--)
{
if(depth[fa[u][i]]>=depth[v])
{
t0=u0;t1=u1;
u0=min(t0+F[u][i][0][0],t1+F[u][i][1][0]);
u1=min(t0+F[u][i][0][1],t1+F[u][i][1][1]);
u=fa[u][i];
}
}
if(u==v)
{
l=v;
if(y) {l0=inf;l1=u1;}
else {l1=inf;l0=u0;}
}
else
{
for(int i=lim;i>=0;i--)
{
if(fa[u][i]!=fa[v][i])
{
t0=u0;t1=u1;
u0=min(t0+F[u][i][0][0],t1+F[u][i][1][0]);
u1=min(t0+F[u][i][0][1],t1+F[u][i][1][1]);
u=fa[u][i];
t0=v0;t1=v1;
v0=min(t0+F[v][i][0][0],t1+F[v][i][1][0]);
v1=min(t0+F[v][i][0][1],t1+F[v][i][1][1]);
v=fa[v][i];
}
}
l=fa[u][0];
l1=dp[l][1]-min(dp[u][0],dp[u][1])-min(dp[v][0],dp[v][1])+min(u1,u0)+min(v1,v0);
l0=dp[l][0]-dp[u][1]-dp[v][1]+u1+v1;
}
for(int i=lim;i>=0;i--)
{
if(depth[fa[l][i]])
{
t0=l0;t1=l1;
l0=min(t0+F[l][i][0][0],t1+F[l][i][1][0]);
l1=min(t0+F[l][i][0][1],t1+F[l][i][1][1]);
l=fa[l][i];
}
}
LL ans=min(l1,l0);
if(ans<inf) return ans;
else return -1;
}
char qwq[7];
int main()
{
N=R();M=R();scanf("%s",qwq);
int u,v;
for(register int i=1;i<=N;i++)
cost[i]=R();
for(register int i=1;i<N;i++)
{
u=R();v=R();
addE(u,v);addE(v,u);
}
depth[1]=1;
dfs(1);
for(int u=1;u<=N;u++)
{
F[u][0][0][0]=inf;
F[u][0][1][0]=dp[fa[u][0]][0]-dp[u][1];
F[u][0][0][1]=F[u][0][1][1]=dp[fa[u][0]][1]-min(dp[u][1],dp[u][0]);
}
int t;
for(int u=1;u<=N;u++)
for(int i=1;i<=lim;i++)
{
t=fa[u][i-1];
F[u][i][0][0]=min(F[u][i-1][0][0]+F[t][i-1][0][0],F[u][i-1][0][1]+F[t][i-1][1][0]);
F[u][i][1][0]=min(F[u][i-1][1][0]+F[t][i-1][0][0],F[u][i-1][1][1]+F[t][i-1][1][0]);
F[u][i][0][1]=min(F[u][i-1][0][0]+F[t][i-1][0][1],F[u][i-1][0][1]+F[t][i-1][1][1]);
F[u][i][1][1]=min(F[u][i-1][1][0]+F[t][i-1][0][1],F[u][i-1][1][1]+F[t][i-1][1][1]);
}
int x,y;
for(int i=1;i<=M;i++)
{
u=R();x=R();v=R();y=R();
printf("%lld\n",cal(u,x,v,y));
}
return 0;
}