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\)已经咕了两个月了(可能下一次就是一百年后了),觉得我退化了啊,比去年还菜了啊

posted @ 2019-08-12 20:31  flyfeather  阅读(140)  评论(0编辑  收藏  举报