[NOIP2018 提高组] 保卫王国(DP)
洛谷题面
不得不说这道题非常地好啊非常地好,有多种做法并且还体现了DP优化的思想。
从这道题中总结出两点:1.DP优化的一种思想:找每次DP重复的部分,这部分是可以重复用的。 2.倍增方式加速树上(也许不仅仅是树上?)DP的方式非常地好啊。(令人联想到矩阵加速)
题面见上...
此题好像有5种以上做法,各路大神一眼动态动态规划(然而我不会QAQ),于是乎只会倍增求。
对于每次询问,我们发现除了两点路径上的贡献需要重新算,其余路径及子树的贡献都是不变的,考虑使用倍增优化算路径上贡献的过程,倍增时DP过程和一般DP无二,每次倍增求到两点LCA再到根节点即可得到答案。
貌似也有用矩阵加速这个过程的。
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int rd(){
int f=1,j=0;
char w=getchar();
while(w>'9'||w<'0'){
if(w=='-')f=-1;
w=getchar();
}
while(w>='0'&&w<='9'){
j=(j<<3)+(j<<1)+w-'0';
w=getchar();
}
return f*j;
}
const int N=100001,MAX=10000000001;
int n,m,dep[N];
int val[N],tree[N][2],lca[N][18][2][2],fa[N][18];
vector<int>line[N];
void dfs1(int u){
dep[u]=dep[fa[u][0]]+1;
tree[u][1]=val[u];
for(int to:line[u]){
if(to==fa[u][0])continue;
fa[to][0]=u;
dfs1(to);
tree[u][0]+=tree[to][1];
tree[u][1]+=min(tree[to][1],tree[to][0]);
}
for(int to:line[u]){
if(to==fa[u][0])continue;
lca[to][0][0][0]=MAX;
lca[to][0][1][0]=tree[u][0]-tree[to][1];
lca[to][0][0][1]=lca[to][0][1][1]=tree[u][1]-min(tree[to][0],tree[to][1]);
}
return ;
}
void dfs2(int u){
for(int i=1;(1<<i)<dep[u];i++)fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=1;(1<<i)<dep[u];i++){
lca[u][i][0][0]=min(lca[u][i-1][0][0]+lca[fa[u][i-1]][i-1][0][0],lca[u][i-1][0][1]+lca[fa[u][i-1]][i-1][1][0]);
lca[u][i][1][0]=min(lca[u][i-1][1][0]+lca[fa[u][i-1]][i-1][0][0],lca[u][i-1][1][1]+lca[fa[u][i-1]][i-1][1][0]);
lca[u][i][0][1]=min(lca[u][i-1][0][0]+lca[fa[u][i-1]][i-1][0][1],lca[u][i-1][0][1]+lca[fa[u][i-1]][i-1][1][1]);
lca[u][i][1][1]=min(lca[u][i-1][1][0]+lca[fa[u][i-1]][i-1][0][1],lca[u][i-1][1][1]+lca[fa[u][i-1]][i-1][1][1]);
}
for(int to:line[u]){
if(to!=fa[u][0])dfs2(to);
}
return ;
}
void work(){
int a=rd(),x=rd(),b=rd(),y=rd(),ans=MAX;
if(dep[a]<dep[b])swap(a,b),swap(x,y);
int xx=x,yy=y;
if(x==0&&y==0&&(fa[a][0]==b||fa[b][0]==a)){
printf("-1\n");
return ;
}
int suma[2],sumb[2];
suma[x]=tree[a][x];suma[x^1]=MAX;
sumb[y]=tree[b][y];sumb[y^1]=MAX;
for(int i=17;i>=0;i--){
if(dep[fa[a][i]]>dep[b]){
x=min(suma[0]+lca[a][i][0][0],suma[1]+lca[a][i][1][0]);
y=min(suma[0]+lca[a][i][0][1],suma[1]+lca[a][i][1][1]);
suma[0]=x;suma[1]=y;
a=fa[a][i];
}
}
if(fa[a][0]==b){
x=min(suma[0]+lca[a][0][0][0],suma[1]+lca[a][0][1][0]);
y=min(suma[0]+lca[a][0][0][1],suma[1]+lca[a][0][1][1]);
a=fa[a][0];
suma[0]=(yy==0)?x:MAX;
suma[1]=(yy==1)?y:MAX;
}
else if(dep[a]>dep[b]){
x=min(suma[0]+lca[a][0][0][0],suma[1]+lca[a][0][1][0]);
y=min(suma[0]+lca[a][0][0][1],suma[1]+lca[a][0][1][1]);
suma[0]=x;suma[1]=y;
a=fa[a][0];
}
for(int i=17;i>=0;i--){
if(fa[a][i]==fa[b][i])continue;
x=min(suma[0]+lca[a][i][0][0],suma[1]+lca[a][i][1][0]);
y=min(suma[0]+lca[a][i][0][1],suma[1]+lca[a][i][1][1]);
suma[0]=x;suma[1]=y;
a=fa[a][i];
x=min(sumb[0]+lca[b][i][0][0],sumb[1]+lca[b][i][1][0]);
y=min(sumb[0]+lca[b][i][0][1],sumb[1]+lca[b][i][1][1]);
sumb[0]=x;sumb[1]=y;
b=fa[b][i];
}
if(a!=b){
int FA=fa[a][0];
x=tree[FA][0]-tree[a][1]-tree[b][1]+suma[1]+sumb[1];
y=tree[FA][1]-min(tree[a][0],tree[a][1])-min(tree[b][0],tree[b][1])+min(suma[0],suma[1])+min(sumb[0],sumb[1]);
a=FA;
suma[0]=x;suma[1]=y;
}
for(int i=17;i>=0;i--){
if(dep[fa[a][i]]>=1){
x=min(suma[0]+lca[a][i][0][0],suma[1]+lca[a][i][1][0]);
y=min(suma[0]+lca[a][i][0][1],suma[1]+lca[a][i][1][1]);
suma[0]=x;suma[1]=y;
a=fa[a][i];
}
}
printf("%lld\n",min(suma[0],suma[1]));
return ;
}
signed main(){
n=rd();m=rd();
char s[10];scanf("%s",s+1);
for(int i=1;i<=n;i++)val[i]=rd();
for(int i=1;i<n;i++){
int x=rd(),y=rd();
line[x].push_back(y);line[y].push_back(x);
}
dfs1(1);dfs2(1);
for(int i=1;i<=m;i++)work();
return 0;
}