题解 P5024 【保卫王国】
作为NOIP2018的Day2T3,这题也是非常恶心了,思维难度大,程序复杂。很多大佬说这题是动态DP模板,但我这个蒟蒻完全不了解动态DP,经过思考,我终于了解到如何用LCA倍增思想解决这道题,现在就让我们一起来进行分析。
1.对于前11组数据:
先思考简单的暴力做法,题目说明每个点可以选或不选,所以我们用:
之后对于每个询问,都进行一次DP,最后输出
对于处理一个节点是否必须要选/不选,只需将其权值改为0/maxint即可。
时间复杂度
program project1;
var
dp:array[0..100005,0..1]of int64;
a,r:array[0..100005]of longint;
l,v:array[0..200005]of longint;
n,m:longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
procedure re;
var i,t,x,y:longint;
s:string;
begin
readln(s);
for i:=1 to n do read(a[i]);
for i:=1 to n-1 do begin
t:=i*2;
read(x,y);
l[t]:=r[x];
r[x]:=t;
v[t]:=y;
l[t-1]:=r[y];
r[y]:=t-1;
v[t-1]:=x;
end;
end;
procedure sc(f,fa:longint);
var i:longint;
begin
i:=r[f];
while i<>0 do begin
if v[i]<>fa then begin
sc(v[i],f);
dp[f,1]:=dp[f,1]+min(dp[v[i],0],dp[v[i],1]);
dp[f,0]:=dp[f,0]+dp[v[i],1];
end;
i:=l[i];
end;
dp[f,1]:=dp[f,1]+a[f];
end;
procedure main;
var i,j,x,y,tx,ty,ans,p,q:longint;
begin
for i:=1 to m do begin
read(x,tx,y,ty);
ans:=0;
p:=a[x];
q:=a[y];
fillchar(dp,sizeof(dp),0);
if tx=1 then begin
ans:=ans+a[x];
a[x]:=0;
end else a[x]:=1000000000;
if ty=1 then begin
ans:=ans+a[y];
a[y]:=0;
end else a[y]:=1000000000;
sc(1,0);
ans:=min(ans+dp[1,1],ans+dp[1,0]);
if ans>=1000000000 then writeln('-1')
else writeln(ans);
a[x]:=p;
a[y]:=q;
end;
end;
begin
read(n,m);
fillchar(a,sizeof(a),0);
re;
main;
end.
2.对于B类型数据:
在之间的基础上进行分析,我们发现对于每一次询问,除了被询问的两个点之间的链上,各节点的答案会发生改变,其余节点的答案是不变的。
比如下图(询问 O 和 M ):
则只有 O K D M 四个节点的答案需要更新。
由此,我们可以有一个玄学的优化,即再用一个数组
之后对于每次询问,只需对两个节点之间的链进行维护,一直维护到两个节点的最近公共祖先处。
设它们的最近公共祖先为
假设链的平均长度为
这棵树退化的越接近一条链,这个玄学优化的性能越差,最坏时间复杂度仍接近
program project1;
var
dp,ndp,dp2:array[0..100005,0..1]of int64;
a,r,father,deep:array[0..100005]of longint;
l,v:array[0..200005]of longint;
n,m:longint;
function min(a,b:int64):int64;
begin
if a<b then exit(a) else exit(b);
end;
procedure re;
var i,t,x,y:longint;
s:string;
begin
readln(s);
for i:=1 to n do read(a[i]);
for i:=1 to n-1 do begin
t:=i*2;
read(x,y);
l[t]:=r[x];
r[x]:=t;
v[t]:=y;
l[t-1]:=r[y];
r[y]:=t-1;
v[t-1]:=x;
end;
end;
procedure sc(f,fa:longint);
var i:longint;
begin
father[f]:=fa;
deep[f]:=deep[fa]+1;
i:=r[f];
while i<>0 do begin
if v[i]<>fa then begin
sc(v[i],f);
dp[f,1]:=dp[f,1]+min(dp[v[i],0],dp[v[i],1]);
dp[f,0]:=dp[f,0]+dp[v[i],1];
end;
i:=l[i];
end;
dp[f,1]:=dp[f,1]+a[f];
end;
procedure sc2(f,fa:longint);
var i:longint;
begin
i:=r[f];
while i<>0 do begin
if v[i]<>fa then begin
ndp[v[i],1]:=min(ndp[f,0]+dp[f,0]-dp[v[i],1],ndp[f,1]+dp[f,1]-min(dp[v[i],0],dp[v[i],1]));
ndp[v[i],0]:=ndp[f,1]+dp[f,1]-min(dp[v[i],0],dp[v[i],1]);
sc2(v[i],f);
end;
i:=l[i];
end;
end;
function ans(x,y:longint):int64;
var i,t:longint;
begin
if deep[x]<deep[y] then begin
t:=x;
x:=y;
y:=t;
end;
while (deep[x]>deep[y]) and (father[x]<>y) do begin
t:=father[x];
dp2[t,1]:=dp[t,1]-min(dp[x,0],dp[x,1])+min(dp2[x,0],dp2[x,1]);
dp2[t,0]:=dp[t,0]-dp[x,1]+dp2[x,1];
x:=t;
end;
if father[x]=y then begin
t:=y;
dp2[t,1]:=dp2[t,1]-min(dp[x,0],dp[x,1])+min(dp2[x,0],dp2[x,1]);
dp2[t,0]:=dp2[t,0]-dp[x,1]+dp2[x,1];
x:=t;
end;
while father[x]<>father[y] do begin
t:=father[x];
dp2[t,1]:=dp[t,1]-min(dp[x,0],dp[x,1])+min(dp2[x,0],dp2[x,1]);
dp2[t,0]:=dp[t,0]-dp[x,1]+dp2[x,1];
x:=t;
t:=father[y];
dp2[t,1]:=dp[t,1]-min(dp[y,0],dp[y,1])+min(dp2[y,0],dp2[y,1]);
dp2[t,0]:=dp[t,0]-dp[y,1]+dp2[y,1];
y:=t;
end;
if x<>y then begin
t:=father[x];
dp2[t,1]:=dp[t,1]-min(dp[x,0],dp[x,1])+min(dp2[x,0],dp2[x,1])-min(dp[y,0],dp[y,1])+min(dp2[y,0],dp2[y,1]);
dp2[t,0]:=dp[t,0]-dp[x,1]-dp[y,1]+dp2[x,1]+dp2[y,1];
x:=t;
end;
exit(min(dp2[t,0]+ndp[t,0],dp2[t,1]+ndp[t,1]));
end;
procedure main;
var i,x,y,tx,ty:longint;
sum:int64;
begin
for i:=1 to m do begin
read(x,tx,y,ty);
if tx=1 then begin
dp2[x,1]:=dp[x,1];
dp2[x,0]:=1000000000000;
end else begin
dp2[x,1]:=1000000000000;
dp2[x,0]:=dp[x,0];
end;
if ty=1 then begin
dp2[y,1]:=dp[y,1];
dp2[y,0]:=1000000000000;
end else begin
dp2[y,1]:=1000000000000;
dp2[y,0]:=dp[y,0];
end;
sum:=ans(x,y);
if sum<1000000000000 then writeln(sum)
else writeln('-1');
end;
end;
begin
read(n,m);
fillchar(a,sizeof(a),0);
fillchar(father,sizeof(father),0);
fillchar(dp,sizeof(dp),0);
fillchar(deep,sizeof(deep),0);
fillchar(ndp,sizeof(ndp),0);
re;
sc(1,0);
sc2(1,0);
main;
end.
3.对于全部的数据:
以上的玄学优化虽然不能AC此题,但却提供了一个可供发展的思路。可以看出,我们在对这条链进行处理时,相当于一直向上遍历寻找这两个点的最近公共祖先,并将DP值更新。所以我们可以对更新的方式进行优化,不再是一个点一个点的向上遍历,而是采用类似LCA倍增的方法,加快处理速度。
我们用
状态转移为:
依靠此状态转移方程对lca进行预处理,之后对于每一次询问,即可凭借lca数组通过倍增方式进行处理:
设当前节点为
处理完之后向上倍增:
倍增过程就和求LCA的方式基本一样了。
设链的平均长度为
program project1;
var
dp,ndp:array[0..100005,0..1]of int64;
a,r,deep:array[0..100005]of longint;
father:array[0..100005,0..25]of longint;
lca:array[0..100005,0..25,0..1,0..1]of int64;
l,v:array[0..200005]of longint;
n,m:longint;
function min(a,b:int64):int64;
begin
if a<b then exit(a) else exit(b);
end;
procedure re;
var i,t,x,y:longint;
s:string;
begin
readln(s);
for i:=1 to n do read(a[i]);
for i:=1 to n-1 do begin
t:=i*2;
read(x,y);
l[t]:=r[x];
r[x]:=t;
v[t]:=y;
l[t-1]:=r[y];
r[y]:=t-1;
v[t-1]:=x;
end;
end;
procedure sc(f,fa:longint);
var i:longint;
begin
father[f,0]:=fa;
deep[f]:=deep[fa]+1;
i:=r[f];
while i<>0 do begin
if v[i]<>fa then begin
sc(v[i],f);
dp[f,1]:=dp[f,1]+min(dp[v[i],0],dp[v[i],1]);
dp[f,0]:=dp[f,0]+dp[v[i],1];
end;
i:=l[i];
end;
dp[f,1]:=dp[f,1]+a[f];
end;
procedure sc2(f,fa:longint);
var i:longint;
begin
i:=r[f];
while i<>0 do begin
if v[i]<>fa then begin
ndp[v[i],1]:=min(ndp[f,0]+dp[f,0]-dp[v[i],1],ndp[f,1]+dp[f,1]-min(dp[v[i],0],dp[v[i],1]));
ndp[v[i],0]:=ndp[f,1]+dp[f,1]-min(dp[v[i],0],dp[v[i],1]);
sc2(v[i],f);
end;
i:=l[i];
end;
end;
procedure lca_up; //预处理lca数组
var i,t,p,q:longint;
begin
for i:=1 to n do begin
lca[i,0,0,0]:=1000000000000;
lca[i,0,0,1]:=dp[father[i,0],1]-min(dp[i,0],dp[i,1]);
lca[i,0,1,0]:=dp[father[i,0],0]-dp[i,1];
lca[i,0,1,1]:=dp[father[i,0],1]-min(dp[i,0],dp[i,1]);
end;
for t:=1 to 20 do
for i:=1 to n do begin
father[i,t]:=father[father[i,t-1],t-1];
for p:=0 to 1 do
for q:=0 to 1 do begin
lca[i,t,p,q]:=1000000000000;
lca[i,t,p,q]:=min(lca[i,t,p,q],lca[i,t-1,p,0]+lca[father[i,t-1],t-1,0,q]);
lca[i,t,p,q]:=min(lca[i,t,p,q],lca[i,t-1,p,1]+lca[father[i,t-1],t-1,1,q]);
end;
end;
end;
function ans(x,tx,y,ty:longint):int64;
var i,t,p,q:longint;
sumx,sumy,ansx,ansy:array[0..1]of int64;
begin
if deep[x]<deep[y] then begin
t:=x;
x:=y;
y:=t;
t:=tx;
tx:=ty;
ty:=t;
end;
ansx[1]:=1000000000000;
ansx[0]:=1000000000000;
ansy[1]:=1000000000000;
ansy[0]:=1000000000000;
ansx[tx]:=dp[x,tx];
ansy[ty]:=dp[y,ty];
for t:=20 downto 0 do begin
if deep[x]-(1 shl t)>=deep[y] then begin
sumx[0]:=1000000000000;
sumx[1]:=1000000000000;
for p:=0 to 1 do
for q:=0 to 1 do
sumx[q]:=min(sumx[q],lca[x,t,p,q]+ansx[p]);
ansx[1]:=sumx[1];
ansx[0]:=sumx[0];
x:=father[x,t];
end;
end;
if x=y then exit(ansx[ty]+ndp[x,ty]);
for t:=20 downto 0 do
if father[x,t]<>father[y,t] then begin
sumx[0]:=1000000000000;
sumx[1]:=1000000000000;
sumy[0]:=1000000000000;
sumy[1]:=1000000000000;
for p:=0 to 1 do
for q:=0 to 1 do begin
sumx[q]:=min(sumx[q],lca[x,t,p,q]+ansx[p]);
sumy[q]:=min(sumy[q],lca[y,t,p,q]+ansy[p]);
end;
ansx[0]:=sumx[0];
ansx[1]:=sumx[1];
ansy[0]:=sumy[0];
ansy[1]:=sumy[1];
x:=father[x,t];
y:=father[y,t];
end;
p:=father[x,0];
exit(min(dp[p,0]-dp[x,1]-dp[y,1]+ansx[1]+ansy[1]+ndp[p,0],dp[p,1]-min(dp[x,0],dp[x,1])-min(dp[y,0],dp[y,1])+min(ansx[0],ansx[1])+min(ansy[0],ansy[1])+ndp[p,1]));
end;
procedure main;
var i,x,y,tx,ty:longint;
sum:int64;
begin
for i:=1 to m do begin
read(x,tx,y,ty);
sum:=ans(x,tx,y,ty);
if sum<1000000000000 then writeln(sum)
else writeln('-1');
end;
end;
begin
read(n,m);
fillchar(a,sizeof(a),0);
fillchar(father,sizeof(father),0);
fillchar(dp,sizeof(dp),0);
fillchar(deep,sizeof(deep),0);
fillchar(ndp,sizeof(ndp),0);
fillchar(lca,sizeof(lca),0);
re;
sc(1,0);
sc2(1,0);
lca_up;
main;
end.