NOIP2018 保卫王国

题目

Z 国有 \(n\) 座城市,\(n−1\)条双向道路,每条双向道路连接两座城市,且任意两座城市都能通过若干条道路相互到达。

Z 国的国防部长小 Z 要在城市中驻扎军队。驻扎军队需要满足如下几个条件:

  • 一座城市可以驻扎一支军队,也可以不驻扎军队。
  • 由道路直接连接的两座城市中至少要有一座城市驻扎军队。
  • 在城市里驻扎军队会产生花费,在编号为 \(i\) 的城市中驻扎军队的花费是 \(p_i\)

小 Z 很快就规划出了一种驻扎军队的方案,使总花费最小。但是国王又给小 Z 提出了 \(m\) 个要求,每个要求规定了其中两座城市是否驻扎军队。小 Z 需要针对每个要求逐一给出回答。具体而言,如果国王提出的第 \(j\) 个要求能够满足上述驻扎条件(不需要考虑第 \(j\) 个要求之外的其它要求),则需要给出在此要求前提下驻扎军队的最小开销。如果国王提出的第 \(j\) 个要求无法满足,则需要输出 \(-1\)。现在请你来帮助小 Z。

思路1 倍增

首先,这题给的切分非常多,所以考场上稳妥的策略应该是去打切分而不是正解。

正解有很多写法,这里写一种我认为最正常的写法:倍增。

首先得到两个数组:

\(f[x][0/1]\)表示在\(x\)这个子树,选或不选\(x\)点的最优方案。

\(g[x][0/1]\)表示对于全局而言,选或不选\(x\)的最优方案。

再处理一个倍增数组:\(dp[x][j][0/1][0/1]\)表示\(x\)的距离为\(2^j\)的祖先的子树排除掉\(x\)的子树,两者的取与不取的状态,最优的方案。

然后考虑对于一个询问\(x\),\(y\)。分成两种情况讨论(以下描述默认\(x\)\(y\)浅)。

如果两个点具有祖先关系,那么直接把\(y\)跳到\(x\)的子节点处,对两个点的情况进行讨论。

如果两个点不具备祖先关系,那么把两个点都跳到lca的子节点处,对lca的是否存在进行讨论,用我们上面预处理出的这些数组即可计算答案。

代码

#include<bits/stdc++.h>
#define M 100005
#define LL long long
using namespace std;
void tomin(LL &x,LL y){if(x>y)x=y;}
int n,m,h[M],tt,A[M];
char op[15];
struct edge{
	int nxt,to;
}G[M<<1];
void Add(int a,int b){
	G[++tt]=(edge){h[a],b};
	h[a]=tt;
}
struct node{
	LL a[2][2];
	node(){a[0][0]=a[1][1]=a[0][1]=a[1][0]=1e13;}
}dp[M][18];
node merge(node x,node y){
	node res;
	for(int a=0;a<2;a++)
		for(int b=0;b<2;b++)
			for(int k=0;k<2;k++)
				tomin(res.a[a][b],x.a[a][k]+y.a[k][b]);
	return res;
}
LL f[M][2],g[M][2],dep[M];
int fa[M][18];
void dfs(int x,int ff,int d){
	f[x][0]=0;f[x][1]=A[x];fa[x][0]=ff;
	dep[x]=d;
	for(int i=h[x];i;i=G[i].nxt){
		int u=G[i].to;
		if(u==ff)continue;
		dfs(u,x,d+1);
		f[x][0]+=f[u][1];
		f[x][1]+=min(f[u][0],f[u][1]);
	}
}
void redfs(int x,int ff,LL t0,LL t1){
	g[x][0]=f[x][0]+t1;
	g[x][1]=f[x][1]+min(t0,t1);
	for(int i=h[x];i;i=G[i].nxt){
		int u=G[i].to;
		if(u==ff)continue;
		redfs(u,x,g[x][0]-f[u][1],g[x][1]-min(f[u][0],f[u][1]));
	}
	if(ff){
		dp[x][0].a[0][0]=1e13;
		dp[x][0].a[1][0]=f[ff][0]-f[x][1];
		dp[x][0].a[0][1]=f[ff][1]-min(f[x][0],f[x][1]);
		dp[x][0].a[1][1]=f[ff][1]-min(f[x][0],f[x][1]);
	}
}
int up(int x,int step){
	for(int i=17;i>=0;i--)
		if(step&1<<i)x=fa[x][i];
	return x;
}
int LCA(int a,int b){
	if(dep[a]>dep[b])swap(a,b);
	int step=dep[b]-dep[a];
	b=up(b,step);
	if(a==b)return a;
	for(int i=17;i>=0;i--)
		if(fa[a][i]!=fa[b][i])
			a=fa[a][i],b=fa[b][i];
	return fa[a][0];
}
LL Print(LL x){
	if(x>=1e13)return -1;
	return x;
}
int main(){
//	freopen("defense.in","r",stdin);
//	freopen("defense.out","w",stdout);
	scanf("%d%d%s",&n,&m,op+1);
	for(int i=1;i<=n;i++)scanf("%d",&A[i]);
	for(int i=1,a,b;i<n;i++){
		scanf("%d%d",&a,&b);
		Add(a,b);Add(b,a);
	}
	dfs(1,0,0);redfs(1,0,0,0);
	for(int j=1;j<=17;j++)
		for(int i=1;i<=n;i++)
			fa[i][j]=fa[fa[i][j-1]][j-1];
	for(int j=1;j<=17;j++)
		for(int i=1;i<=n;i++)
			dp[i][j]=merge(dp[i][j-1],dp[fa[i][j-1]][j-1]);
	for(int i=1,x,a,y,b;i<=m;i++){
		scanf("%d%d%d%d",&x,&a,&y,&b);
		if(dep[x]>dep[y])swap(x,y),swap(a,b);
		int lca=LCA(x,y);
		int tx=x,ty=y;
		if(x==lca){
			int step=dep[y]-dep[x]-1;
			node res;res.a[0][0]=-1;
			for(int j=17;j>=0;j--){
				if(step&1<<j){
					if(res.a[0][0]==-1)res=dp[y][j];
					else res=merge(res,dp[y][j]);
					y=fa[y][j];
				}
			}
			if(res.a[0][0]==-1){
				res.a[0][0]=res.a[1][1]=0;
				res.a[1][0]=res.a[0][1]=1e13;
			}
			LL tmp[3];
			tmp[0]=g[x][0]-f[y][1];
			tmp[1]=g[x][1]-min(f[y][0],f[y][1]);
			if(a==0)printf("%lld\n",Print(f[ty][b]+res.a[b][1]+tmp[a]));
			else printf("%lld\n",Print(f[ty][b]+min(res.a[b][0],res.a[b][1])+tmp[a]));
		}
		else {
			node res1,res2;res1.a[0][0]=-1;res2.a[0][0]=-1;
			int step=dep[y]-dep[lca]-1;
			for(int j=17;j>=0;j--){
				if(step&1<<j){
					if(res2.a[0][0]==-1)res2=dp[y][j];
					else res2=merge(res2,dp[y][j]);
					y=fa[y][j];
				}
			}
			step=dep[x]-dep[lca]-1;
			for(int j=17;j>=0;j--){
				if(step&1<<j){
					if(res1.a[0][0]==-1)res1=dp[x][j];
					else res1=merge(res1,dp[x][j]);
					x=fa[x][j];
				}
			}
			if(res1.a[0][0]==-1){
				res1.a[0][0]=0;
				res1.a[1][1]=0;
				res1.a[1][0]=res1.a[0][1]=1e13;
			}
			if(res2.a[0][0]==-1){
				res2.a[0][0]=0;
				res2.a[1][1]=0;
				res2.a[1][0]=res2.a[0][1]=1e13;
			}
			LL tmp[3];
			tmp[0]=g[lca][0]-f[y][1]-f[x][1];
			tmp[1]=g[lca][1]-min(f[y][0],f[y][1])-min(f[x][0],f[x][1]);
			LL ans=1e18;
			ans=min(ans,res1.a[a][1]+res2.a[b][1]+tmp[0]);
			ans=min(ans,min(res1.a[a][0],res1.a[a][1])+min(res2.a[b][0],res2.a[b][1])+tmp[1]);
			ans+=f[tx][a]+f[ty][b];
			printf("%lld\n",Print(ans));
		}
	}
	return 0;
}

思路2 动态dp

正是因为NOIP中出了这道题,让动态dp一下子普及了开来。

前置知识:矩阵乘法,树链剖分(虽然倍增好像也可以)。

首先要知道,矩阵乘法有一些扩展的应用:

\[C_{i,j}=\min_{k=1}^n{(A_{i,k}+B_{k,j})} \]

把式子改成上面那个样子同样满足结合律。

可以把dp式子往这个方向上去凑。

我们来看看这题暴力代码的dp式子:

\[f[x][0]=\sum{f[u][1]}\\ f[x][1]=\sum{min(f[u][0],f[u][1])}+val[x] \]

\(g[x][0/1]\)表示剥离\(x\)的重儿子时,所计算出来的dp值,定义出新的dp式子:

\[f[x][0]=g[x][0]+f[son][1]\\ f[x][1]=g[x][1]+min(f[son][0],f[son][1]) \]

这样就可以构造出转移矩阵了:

\[\begin{bmatrix}\infty & g_{x,0} \\g_{x,1} & g_{x, 1} \end{bmatrix}\times\begin{pmatrix} f_{s_{x},0} \\f_{s_x,1}\end{pmatrix}=\begin{pmatrix}f_{x,0} \\f_{x,1}\end{pmatrix} \]

特别的,单位矩阵为

\[\begin{bmatrix}0 & \infty \\\infty & 0 \end{bmatrix} \]

现在考虑,线段树维护一段链上的矩阵乘法。

考虑一条重链,对于一条重链来说,由于\(g\)数组是将其排除在外的,所以重链的答案是可以直接得到的。

事实上,会变化的,就只有轻边上的矩阵,这只有\(logn\)个,可以直接暴力更新。

posted @ 2019-11-05 15:33  zeroy0410  阅读(185)  评论(0编辑  收藏  举报