【NOIP 2018】Day2 T3 保卫王国
作者:互联网
【NOIP 2018】Day2 T3 保卫王国
题目描述
Z 国有\(n\)座城市,\(n - 1\)条双向道路,每条双向道路连接两座城市,且任意两座城市 都能通过若干条道路相互到达。
Z 国的国防部长小 Z 要在城市中驻扎军队。驻扎军队需要满足如下几个条件:
- 一座城市可以驻扎一支军队,也可以不驻扎军队。
- 由道路直接连接的两座城市中至少要有一座城市驻扎军队。
- 在城市里驻扎军队会产生花费,在编号为\(i\)的城市中驻扎军队的花费是\(p_i\)。
小 Z 很快就规划出了一种驻扎军队的方案,使总花费最小。但是国王又给小 Z 提出 了\(m\)个要求,每个要求规定了其中两座城市是否驻扎军队。小 Z 需要针对每个要求逐一 给出回答。具体而言,如果国王提出的第\(j\)个要求能够满足上述驻扎条件(不需要考虑 第 \(j\) 个要求之外的其它要求),则需要给出在此要求前提下驻扎军队的最小开销。如果 国王提出的第j个要求无法满足,则需要输出\(-1~~(1 ≤ j ≤ m)\)。现在请你来帮助小 Z。
输入输出格式
输入格式:
第 1 行包含两个正整数\(n,m\)和一个字符串\(type\),分别表示城市数、要求数和数据类型。\(type\)是一个由大写字母 \(A\),\(B\) 或 \(C\) 和一个数字 1,2,3 组成的字符串。它可以帮助你获得部分分。你可能不需要用到这个参数。这个参数的含义在【数据规模与约定】中 有具体的描述。
第 2 行\(n\)个整数\(p_i\),表示编号\(i\)的城市中驻扎军队的花费。
接下来 \(n - 1\) 行,每行两个正整数\(u,v\),表示有一条\(u\)到\(v\)的双向道路。
接下来 \(m\) 行,第\(j\)行四个整数\(a,x,b,y(a ≠ b)\),表示第\(j\)个要求是在城市\(a\)驻扎\(x\)支军队, 在城市\(b\)驻扎\(y\)支军队。其中,\(x\) , \(y\)的取值只有 0 或 1:若 \(x\) 为 0,表示城市 \(a\) 不得驻 扎军队,若 \(x\) 为 1,表示城市 \(a\) 必须驻扎军队;若 \(y\)为 0,表示城市 \(b\)不得驻扎军队, 若 \(y\)为 1,表示城市 \(b\) 必须驻扎军队。
输入文件中每一行相邻的两个数据之间均用一个空格分隔。
输出格式:
输出共 \(m\) 行,每行包含 1 个整数,第\(j\)行表示在满足国王第\(j\)个要求时的最小开销, 如果无法满足国王的第\(j\)个要求,则该行输出 -1。
输入输出样例
输入样例#1:
5 3 C3
2 4 1 3 9
1 5
5 2
5 3
3 4
1 0 3 0
2 1 3 1
1 0 5 0
输出样例#1:
12
7
-1
说明
【样例解释】
对于第一个要求,在 4 号和 5 号城市驻扎军队时开销最小。
对于第二个要求,在 1 号、2 号、3 号城市驻扎军队时开销最小。
第三个要求是无法满足的,因为在 1 号、5 号城市都不驻扎军队就意味着由道路直接连 接的两座城市中都没有驻扎军队。
【数据规模与约定】
对于 \(100\%\)的数据,\(n,m ≤ 100000,1 ≤ p_i ≤ 100000\)。
数据类型的含义:
A:城市\(i\)与城市\(i + 1\)直接相连。
B:任意城市与城市 1 的距离不超过 100(距离定义为最短路径上边的数量),即如果这 棵树以 1 号城市为根,深度不超过 100。
C:在树的形态上无特殊约束。
1:询问时保证\(a = 1,x = 1\),即要求在城市 1 驻军。对b,y没有限制。
2:询问时保证\(a,b\)是相邻的(由一条道路直接连通)
3:在询问上无特殊约束。
考点
倍增,树形DP
思路
首先看到数据范围为 \(1e5\),那么发现 \(n^2\) 算法是不可能过的,不难想到算法是 \(nlogn\) 的。那么接着看到题面,我们会发现一件事,那就是每个询问只影响两个点,而通过树形\(dp\)的过程我们可以知道一件事情:除了一个询问影响的两个点以外的点的\(dp\)过程其实都是没有变化的,或者说,除去这两个点,当做整棵树里没有它们以及它们的子树,只考虑其他点的最优 \(dp\) 值还是不变!
那么不难想到,我们处理出除了这两个点以外的 \(dp\) 值,再单独处理这两个点不就行了吗?可是不能直接处理,时间空间都会爆炸。那么你想到了什么吗?倍增!联赛每年必考的倍增不见了!那么题目不就清晰明朗起来了吗?
\(f[i][0/1]\)表示选或不选\(i\)结点,选择以\(i\)为根的子树所有结点的最优值。\(g[i][0/1]\)表示的是选或不选\(i\)结点,整棵树除了以\(i\)为根的子树以外全部选择的最优值。然后呢?当然就是倍增!设\(fa\)为\(i\)的\(2^j\)祖先,\(dp[i][j][0/1][0/1]\)表示的是\(i\)结点选或不选,\(i\)的\(2^j\)祖先\(fa\)选或不选,以\(fa\)为根的子树内独不选以\(i\)为根的子树的最优值!换句话来说,也可以说是 \(i\) 结点相对于它的 \(2^j\) 祖先结点为根的树的另一个 \(g\) 数组。
那么处理出了倍增数组之后呢?我们这样做:对于两个询问的点不断向上跳并统计\(dp\)值,如果其中一个是另一个的祖先结点,那么这一个的答案就可以直接输出。否则继续向上统计答案,直到到达\(lca\)处。为什么跳到这种位置就行了呢?因为修改两点的影响范围是有限的,对于范围之外的点它们的\(dp\)值并不会受到影响!
具体怎么跳?对于当前点\(x\)来说,记录它的子树最优值\(tx[1/0]\),代表当前点选或不选的子树最优值。那么对于初始修改点,\(tx\)数组的两个数里只会有一个是有值的,另一个设为\(inf\)即可。另一个点\(y\)自然同理。
至于判断 \(-1\)?\(emm...\)如果修改的两点是相连的并且修改值都为 \(0\) 呗......
代码
这个地方我分开写伐。
f 数组:
void DP(int x)
{
f[x][1]=w[x];
for(int i=head[x];i;i=nx[i])
if(to[i]!=fa[x])
{
int p=to[i];
DP(p);
f[x][1]+=min(f[p][0],f[p][1]);
f[x][0]+=f[p][1];
}
}
g 数组:
void D_P(int x)
{
for(int i=head[x];i;i=nx[i])
if(to[i]!=fa[x])
{
int p=to[i];
g[p][0]=g[x][1]+f[x][1]-min(f[p][0],f[p][1]);
g[p][1]=min(g[x][0]+f[x][0]-f[p][1],g[p][0]);
D_P(p);
}
}
重点- dp 数组:
for(int i=1;i<=n;i++)
{
jump[i][0]=fa[i];
dp[i][0][0][0]=2e15;
dp[i][0][0][1]=f[fa[i]][1]-min(f[i][0],f[i][1]);//这里你可能会疑惑:为什么是减去min?因为这并不是别的,而是减去f数组DP上来的时候贡献的DP值
dp[i][0][1][0]=f[fa[i]][0]-f[i][1];
dp[i][0][1][1]=f[fa[i]][1]-min(f[i][0],f[i][1]);
}
for(int j=1;j<=17;j++)
for(int i=1;i<=n;i++)
{
jump[i][j]=jump[jump[i][j-1]][j-1];
for(int k=0;k<=1;k++)
for(int l=0;l<=1;l++)
{
dp[i][j][k][l]=2e15;
for(int q=0;q<=1;q++)
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][q]+dp[jump[i][j-1]][j-1][q][l]);
}
}
关键-向上跳:
long long work()
{
先跳到同一深度
if(deep[a]<deep[b])
{
swap(a,b);
swap(c,d);
}
tx,ty分别代表a,b跳到当前点的最优值,nx,ny代表下一步转移的值
long long tx[2]={inf,inf},ty[2]={inf,inf},nx[2],ny[2];
tx[c]=f[a][c],ty[d]=f[b][d];
for(int i=17;i>=0;i--)
if(deep[jump[a][i]]>=deep[b])
{
nx[0]=nx[1]=inf;
for(int j=0;j<=1;j++)
for(int k=0;k<=1;k++)
nx[j]=min(nx[j],tx[k]+dp[a][i][k][j]);
tx[0]=nx[0],tx[1]=nx[1];
a=jump[a][i];
}
if(a==b)
return tx[d]+g[b][d];//如果b为a的祖先,那么直接返回DP最优值+子树外最优值
a,b一起跳
for(int i=17;i>=0;i--)
if(jump[a][i]!=jump[b][i])
{
nx[0]=nx[1]=inf;
ny[0]=ny[1]=inf;
for(int j=0;j<=1;j++)
for(int k=0;k<=1;k++)
{
nx[j]=min(nx[j],tx[k]+dp[a][i][k][j]);
ny[j]=min(ny[j],ty[k]+dp[b][i][k][j]);
}
tx[0]=nx[0],tx[1]=nx[1];
ty[0]=ny[0],ty[1]=ny[1];
a=jump[a][i];
b=jump[b][i];
}
int lca=fa[a];
判断返回值哪个更优
return min(f[lca][0]-f[a][1]-f[b][1]+tx[1]+ty[1]+g[lca][0],f[lca][1]-min(f[a][0],f[a][1])-min(f[b][0],f[b][1])+min(tx[0],tx[1])+min(ty[0],ty[1])+g[lca][1]);
展开写法:
long long sum1=min(f[lca][0]-f[a][1]-f[b][1]+tx[1]+ty[1]+g[lca][0]);//减去原本DP值并加上跳过来的最优值再加上子树外最优值。
long long sum2=min(f[lca][1]-min(f[a][0],f[a][1])-min(f[b][0],f[b][1])+min(tx[0],tx[1])+min(ty[0],ty[1]))+g[lca][1];//减去原本DP值加上当前最优加上子树外最优
}
总代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,nx[200001],to[200001],head[100001],a,b,c,d;
int fa[200001],deep[200001],w[100001],jump[100001][18];
long long f[100001][2],g[100001][2],dp[100001][18][2][2],inf=2e15;
char type[2];
void add(int u,int v,int d)
{
nx[d]=head[u];
to[d]=v;
head[u]=d;
}
void build(int x)
{
for(int i=head[x];i;i=nx[i])
if(to[i]!=fa[x])
{
fa[to[i]]=x;
deep[to[i]]=deep[x]+1;
build(to[i]);
}
}
void DP(int x)
{
f[x][1]=w[x];
if(x==a)
f[x][(c+1)%2]=inf;
if(x==b)
f[x][(d+1)%2]=inf;
for(int i=head[x];i;i=nx[i])
if(to[i]!=fa[x])
{
int p=to[i];
DP(p);
f[x][1]+=min(f[p][0],f[p][1]);
f[x][0]+=f[p][1];
}
}
void D_P(int x)
{
for(int i=head[x];i;i=nx[i])
if(to[i]!=fa[x])
{
int p=to[i];
g[p][0]=g[x][1]+f[x][1]-min(f[p][0],f[p][1]);
g[p][1]=min(g[x][0]+f[x][0]-f[p][1],g[p][0]);
D_P(p);
}
}
long long work()
{
if(deep[a]<deep[b])
{
swap(a,b);
swap(c,d);
}
long long tx[2]={inf,inf},ty[2]={inf,inf},nx[2],ny[2];
tx[c]=f[a][c],ty[d]=f[b][d];
for(int i=17;i>=0;i--)
if(deep[jump[a][i]]>=deep[b])
{
nx[0]=nx[1]=inf;
for(int j=0;j<=1;j++)
for(int k=0;k<=1;k++)
nx[j]=min(nx[j],tx[k]+dp[a][i][k][j]);
tx[0]=nx[0],tx[1]=nx[1];
a=jump[a][i];
}
if(a==b)
return tx[d]+g[b][d];
for(int i=17;i>=0;i--)
if(jump[a][i]!=jump[b][i])
{
nx[0]=nx[1]=inf;
ny[0]=ny[1]=inf;
for(int j=0;j<=1;j++)
for(int k=0;k<=1;k++)
{
nx[j]=min(nx[j],tx[k]+dp[a][i][k][j]);
ny[j]=min(ny[j],ty[k]+dp[b][i][k][j]);
}
tx[0]=nx[0],tx[1]=nx[1];
ty[0]=ny[0],ty[1]=ny[1];
a=jump[a][i];
b=jump[b][i];
}
int lca=fa[a];
return min(f[lca][0]-f[a][1]-f[b][1]+tx[1]+ty[1]+g[lca][0],f[lca][1]-min(f[a][0],f[a][1])-min(f[b][0],f[b][1])+min(tx[0],tx[1])+min(ty[0],ty[1])+g[lca][1]);
}
int main()
{
cin>>n>>m>>type;
int u,v;
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
add(u,v,i);
add(v,u,i+n);
}
deep[1]=1;
build(1);
DP(1);
D_P(1);
for(int i=1;i<=n;i++)
{
jump[i][0]=fa[i];
dp[i][0][0][0]=2e15;
dp[i][0][0][1]=f[fa[i]][1]-min(f[i][0],f[i][1]);
dp[i][0][1][0]=f[fa[i]][0]-f[i][1];
dp[i][0][1][1]=f[fa[i]][1]-min(f[i][0],f[i][1]);
}
for(int j=1;j<=17;j++)
for(int i=1;i<=n;i++)
{
jump[i][j]=jump[jump[i][j-1]][j-1];
for(int k=0;k<=1;k++)
for(int l=0;l<=1;l++)
{
dp[i][j][k][l]=2e15;
for(int q=0;q<=1;q++)
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][q]+dp[jump[i][j-1]][j-1][q][l]);
}
}
for(int i=1;i<=m;i++)
{
scanf("%d%d%d%d",&a,&c,&b,&d);
if((fa[a]==b&&c==0&&d==0)||(fa[b]==a&&c==0&&d==0))
{
cout<<"-1\n";
continue;
}
cout<<work()<<endl;
}
}
标签:NOIP,int,军队,城市,Day2,T3,nx,inf,驻扎 来源: https://www.cnblogs.com/luoshuitianyi/p/10336613.html