[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;
}
posted @ 2022-06-28 10:33  flywatre  阅读(87)  评论(0编辑  收藏  举报