NOIP2018提高组D2T3


ddp虽然好想,但是码量有点大(其实是我不会),因此本文用倍增优化树形DP来解决本题。


题意分析

给一棵树染色,每个节点染色需要一定的花费,要求相邻两个节点至少要有一个被染色,给出一些限制条件,求满足每个限制条件的最小花费为多少。

思路分析

首先判断无解的情况。显然,只有当$a,b$互为父子关系(这里的父子关系指的是严格相邻),且$x,y$都为0时才无解,其它情况都可以通过多染色来解。

很容易想到树形DP,那么具体状态如何设置呢?对于每个限制条件给出的两个点$a,b$,答案可以由四个部分构成:以$a$为根的子树,以$b$为根的子树,以$lca(a,b)$为根的子树减去以$a$为根的子树和以$b$为根的子树,整棵树减去以$lca(a,b)$为根的子树。因为对$a,b$的限制会影响到从$a$到$b$的链上的染色状态,因此拆成这四部分,这四部分互相不影响。

显然,对$a,b$的限制只会影响到以$lca(a,b)$为根的子树减去以$a$为根的子树和以$b$为根的子树的答案,因此,我们可以将其它三个部分的答案预处理出来,然后DP处理剩下的这一部分。

因此状态可以设定为:$f1[i][1/0]$表示以$i$为根的子树当$i$染色/不染色时的最小花费,$f2[i][1/0]$表示整棵树减去以$i$为根的子树当$i$染色/不染色时的最小花费,$dp[i][1/0][1/0]$表示以$lca(a,b)$为根的子树减去以$i$为根的子树当$i$染色/不染色及$i$的父节点染色/不染色(按顺序对应第二维和第三维)时的最小花费。这样,我们就可以从$a,b$一直DP到$lca(a,b)$处计算答案了。特别地,当当前状态无解时,值设定为INF。

但是这样的时间复杂度显然太大。类似这样在树上多次向上的DP,可以想到利用树上倍增进行优化。更改一下$dp$数组的定义,$dp[i][j][0/1][0/1]$表示以$i$的$2^j$辈祖先为根的子树减去以$i$为根的子树当$i$染色/不染色及$i$的$2^j$辈祖先染色/不染色时的最小花费。

接下来的问题就是如何进行DP了。利用上面的三个数组,我们可以倍增地DP到$lca(a,b)$处。首先,我们先将$a,b$中深度较大的一个向上倍增DP,直到$a,b$处于同一深度;此时若$a=b$,说明它们原本具有祖先与后代的关系,直接输出答案;否则就将$a,b$同时向上倍增DP,直到$lca(a,b)$的子节点处,然后进行最后的处理,输出答案。DP的时候,枚举状态转移即可。

整理一下思路:

1. 预处理出$f1,f2,dp$三个数组
1. 将$a,b$中深度较大的一个向上倍增直到$a,b$处于同一深度
1. 判断此时$a,b$是否相等,如果是,直接输出答案;否则继续向上倍增
1. 将$a,b$倍增到$lca(a,b)$的子节点处,进行最后的处理,输出答案

接下来将对具体的实现步骤进行讲解。

具体实现

1. 预处理$f1$数组

很基础的一个树形DP,类似于没有上司的舞会,就不再赘述了。同时也处理出深度数组$d$和倍增祖先数组$f$。

  1. void dfs1(int fa,int x)
  2. {
  3. f[x][]=fa,d[x]=d[fa]+,f1[x][]=p[x];
  4. for(int i=head[x],y;i;i=Next[i])
  5. if(fa!=(y=ver[i]))
  6. {
  7. dfs1(x,y);
  8. f1[x][]+=f1[y][];
  9. f1[x][]+=min(f1[y][],f1[y][]);
  10. }
  11. }

2. 预处理$f2$数组

当$i$不染色时,$i$的父亲节点必须染色,因此答案就是当$i$的父亲染色时,整棵树减去以$i$的父亲为根的子树的答案,加上以$i$的兄弟(即父亲相同的节点)为根的子树为根的答案。回顾递推$f1$数组的过程,以$i$的兄弟为根的子树为根的答案相当于统计时少了$i$的统计,即$f1[fa][1]-min(f1[i][0],f1[i][1])$,其中$fa$表示$i$的父亲。

当$i$染色时,$i$的父亲染不染色都可以,$i$的父亲染色时和$i$不染色时的答案是一样的,不染色时同理统计,相当于统计$f1$时撤销了对$i$的统计。

  1. void dfs2(int x)
  2. {
  3. for(int i=head[x],y;i;i=Next[i])
  4. if(f[x][]!=(y=ver[i]))
  5. {
  6. f2[y][]=f2[x][]+f1[x][]-min(f1[y][],f1[y][]);
  7. f2[y][]=min(f2[y][],f2[x][]+f1[x][]-f1[y][]);
  8. dfs2(y);
  9. }
  10. }

3. 预处理$dp$数组

先将$dp$数组初始化为无限大。首先先处理dp初值,即$j=0$的情况。

显然,当$i$和$i$的父节点都不染色时,是无解的,不动它;当$i$的父亲染色时,$i$染不染色都可以,因此答案和上面$f2[i][0]$的转移类似,只是不用加上剩下的部分;当$i$的父亲不染色时,$i$必须染色,转移类似,就不再赘述了。

然后就是倍增地处理,对于第$2^j$辈祖先,枚举第$2^{j-1}$辈祖先及$i$和第$2^j$辈祖先的状态进行转移。转移思想也基本类似。

  1. void pre()
  2. {
  3. memset(dp,0x3f3f,sizeof(dp));
  4. dfs1(,),dfs2();//先处理f1,f2数组
  5. for(int i=;i<=n;i++)
  6. {
  7. dp[i][][][]=dp[i][][][]=f1[f[i][]][]-min(f1[i][],f1[i][]);//父节点染色
  8. dp[i][][][]=f1[f[i][]][]-f1[i][];//父节点不染色
  9. }
  10. for(int j=;j<=;j++)
  11. for(int i=;i<=n;i++)
  12. {
  13. int fa=f[i][j-];
  14. f[i][j]=f[fa][j-];//先计算祖先
  15. for(int x=;x<;x++)//枚举当前点的状态
  16. for(int y=;y<;y++)//枚举第2^j辈祖先的状态
  17. for(int z=;z<;z++)//枚举第2^(j-1)辈祖先的状态
  18. dp[i][j][x][y]=min(dp[i][j][x][y],dp[i][j-][x][z]+dp[fa][j-][z][y]);
  19. }
  20. }

接下来对询问的处理进行讲解。

4. 将$a,b$中深度较大的一个上移,直到$a,b$处于同一深度

我们可以先令深度较大的一个为$a$,若不是,则交换。

定义四个数组$ansa[0/1],ansb[0/1],nowa[0/1],nowb[0/1]$,分别代表$a,b$不染色/染色时最终的答案和当前的答案。具体地说,这个答案就是以当前倍增到的这个祖先为根的子树的答案。

处理之前,因为限制条件,将$a,b$点的另一个状态初始化为INF。

转移的时候,一样地,枚举中间点的状态进行转移。每次转移前,将$nowa$数组初始化为INF;每次转移后,将$nowa$数组的值赋给$ansa$数组,然后将$a$上移。

上移之后,若$a=b$,则直接返回答案。因为当前点是$b$,而限制条件对$b$起作用,因此答案就是$ansa[y]+f2[a][y]$,即以$b$为根的子树的答案加上剩下部分的答案。

  1. if(d[a]<d[b])
  2. swap(a,b),swap(x,y);//令x为深度较大的点
  3. ansa[-x]=ansb[-y]=INF;
  4. ansa[x]=f1[a][x],ansb[y]=f1[b][y];//初值
  5. for(int j=;j>=;j--)//倍增
  6. if(d[f[a][j]]>=d[b])//上移到同一深度
  7. {
  8. nowa[]=nowa[]=INF;//初始化
  9. for(int u=;u<;u++)//枚举2^j辈祖先的状态
  10. for(int v=;v<;v++)//枚举当前点的状态
  11. nowa[u]=min(nowa[u],ansa[v]+dp[a][j][v][u]);
  12. ansa[]=nowa[],ansa[]=nowa[],a=f[a][j];//数组值转移
  13. }
  14. if(a==b)
  15. return ansa[y]+f2[a][y];//相等直接返回

5. 将$a,b$同时上移到$lca(a,b)$的子节点处

与上一步类似,只是同时上移,就不赘述了。

  1. for(int j=;j>=;j--)
  2. if(f[a][j]!=f[b][j])
  3. {
  4. nowa[]=nowa[]=nowb[]=nowb[]=INF;
  5. for(int u=;u<;u++)
  6. for(int v=;v<;v++)
  7. {
  8. nowa[u]=min(nowa[u],ansa[v]+dp[a][j][v][u]);
  9. nowb[u]=min(nowb[u],ansb[v]+dp[b][j][v][u]);
  10. }
  11. ansa[]=nowa[],ansa[]=nowa[],ansb[]=nowb[],ansb[]=nowb[],a=f[a][j],b=f[b][j];
  12. }

6. 对$a,b,lca(a,b)$的状态进行枚举,进行最后的处理

还是一样的思想,若$lca(a,b)$染色,则$a,b$染不染色都行;否则$a,b$必须染色。式子看起来很长,实际上只是之前的式子多了一个节点罢了。

  1. int fa=f[a][];
  2. ans0=f1[fa][]-f1[a][]-f1[b][]+f2[fa][]+ansa[]+ansb[];//lca(a,b)不染色
  3. ans1=f1[fa][]-min(f1[a][],f1[a][])-min(f1[b][],f1[b][])+f2[fa][]+min(ansa[],ansa[])+min(ansb[],ansb[]);//lca(a,b)染色
  4. return min(ans0,ans1);

完结撒花~

(所以对于正解来说type并没有什么用,不过在考场上打不出正解的时候确实是拿部分分的一个好助手)

------------
最后奉上完整代码:

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<algorithm>
  5. #define ll long long
  6. using namespace std;
  7. const int N=2e5,M=3e5,L=;
  8. const ll INF=1e17;
  9. int n,m,tot;
  10. int p[N],d[N],f[N][L];
  11. int head[N],ver[*M],Next[*M];
  12. ll ans0,ans1;
  13. ll nowa[],nowb[],ansa[],ansb[],f1[N][],f2[N][],dp[N][L][][];
  14. string tp;
  15. void add(int x,int y)
  16. {
  17. ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
  18. ver[++tot]=x,Next[tot]=head[y],head[y]=tot;
  19. }
  20. void dfs1(int fa,int x)
  21. {
  22. f[x][]=fa,d[x]=d[fa]+,f1[x][]=p[x];
  23. for(int i=head[x],y;i;i=Next[i])
  24. if(fa!=(y=ver[i]))
  25. {
  26. dfs1(x,y);
  27. f1[x][]+=f1[y][];
  28. f1[x][]+=min(f1[y][],f1[y][]);
  29. }
  30. }//1. 预处理 $f1$ 数组
  31. void dfs2(int x)
  32. {
  33. for(int i=head[x],y;i;i=Next[i])
  34. if(f[x][]!=(y=ver[i]))
  35. {
  36. f2[y][]=f2[x][]+f1[x][]-min(f1[y][],f1[y][]);
  37. f2[y][]=min(f2[y][],f2[x][]+f1[x][]-f1[y][]);
  38. dfs2(y);
  39. }
  40. }//2. 预处理 $f2$ 数组
  41. void pre()
  42. {
  43. memset(dp,0x3f3f,sizeof(dp));
  44. dfs1(,),dfs2();
  45. for(int i=;i<=n;i++)
  46. {
  47. dp[i][][][]=dp[i][][][]=f1[f[i][]][]-min(f1[i][],f1[i][]);
  48. dp[i][][][]=f1[f[i][]][]-f1[i][];
  49. }
  50. for(int j=;j<=;j++)
  51. for(int i=;i<=n;i++)
  52. {
  53. int fa=f[i][j-];
  54. f[i][j]=f[fa][j-];
  55. for(int x=;x<;x++)
  56. for(int y=;y<;y++)
  57. for(int z=;z<;z++)
  58. dp[i][j][x][y]=min(dp[i][j][x][y],dp[i][j-][x][z]+dp[fa][j-][z][y]);
  59. }
  60. }//3. 预处理 $dp$ 数组
  61. bool check(int a,int x,int b,int y)
  62. {
  63. return !x && !y &&(f[a][]==b || f[b][]==a);
  64. }
  65. ll ask(int a,int x,int b,int y)
  66. {
  67. if(d[a]<d[b])
  68. swap(a,b),swap(x,y);
  69. ansa[-x]=ansb[-y]=INF;
  70. ansa[x]=f1[a][x],ansb[y]=f1[b][y];
  71. for(int j=;j>=;j--)
  72. if(d[f[a][j]]>=d[b])
  73. {
  74. nowa[]=nowa[]=INF;
  75. for(int u=;u<;u++)
  76. for(int v=;v<;v++)
  77. nowa[u]=min(nowa[u],ansa[v]+dp[a][j][v][u]);
  78. ansa[]=nowa[],ansa[]=nowa[],a=f[a][j];
  79. }
  80. if(a==b)
  81. return ansa[y]+f2[a][y];//4. 将 $a,b$ 中深度较大的一个上移,直到 $a,b$ 处于同一深度
  82. for(int j=;j>=;j--)
  83. if(f[a][j]!=f[b][j])
  84. {
  85. nowa[]=nowa[]=nowb[]=nowb[]=INF;
  86. for(int u=;u<;u++)
  87. for(int v=;v<;v++)
  88. {
  89. nowa[u]=min(nowa[u],ansa[v]+dp[a][j][v][u]);
  90. nowb[u]=min(nowb[u],ansb[v]+dp[b][j][v][u]);
  91. }
  92. ansa[]=nowa[],ansa[]=nowa[],ansb[]=nowb[],ansb[]=nowb[],a=f[a][j],b=f[b][j];
  93. }//5. 将 $a,b$ 同时上移到 $lca(a,b)$ 的子节点处
  94. int fa=f[a][];
  95. ans0=f1[fa][]-f1[a][]-f1[b][]+f2[fa][]+ansa[]+ansb[];
  96. ans1=f1[fa][]-min(f1[a][],f1[a][])-min(f1[b][],f1[b][])+f2[fa][]+min(ansa[],ansa[])+min(ansb[],ansb[]);
  97. return min(ans0,ans1);//6. 对 $a,b,lca(a,b)$ 的状态进行枚举,进行最后的处理
  98. }
  99. int main()
  100. {
  101. scanf("%d%d",&n,&m);cin>>tp;
  102. for(int i=;i<=n;i++)
  103. scanf("%d",&p[i]);
  104. for(int i=;i<n;i++)
  105. {
  106. int x,y;
  107. scanf("%d%d",&x,&y);
  108. add(x,y);
  109. }
  110. pre();
  111. for(int i=,a,x,b,y;i<=m;i++)
  112. {
  113. scanf("%d%d%d%d",&a,&x,&b,&y);
  114. if(check(a,x,b,y))
  115. {
  116. puts("-1");
  117. continue;
  118. }
  119. printf("%lld\n",ask(a,x,b,y));
  120. }
  121. return ;
  122. }

[NOIP2018]保卫王国 题解的更多相关文章

  1. 竞赛题解 - NOIP2018 保卫王国

    \(\mathcal{NOIP2018}\) 保卫王国 - 竞赛题解 按某一个炒鸡dalao名曰 taotao 的话说: \(\ \ \ \ \ \ \ \ \ "一道sb倍增题" ...

  2. NOIP2018保卫王国

    题目大意:给一颗有点权的树,每次规定两个点选还是不选,求这棵树的最小权点覆盖. 题解 ZZ码农题. 要用动态dp做,这题就是板子,然鹅并不会,留坑代填. 因为没有修改,所以可以静态倍增. 我们先做一遍 ...

  3. [NOIP2018]保卫王国

    嘟嘟嘟 由于一些知道的人所知道的,不知道的人所不知道的原因,我来发NOIP2018day2T3的题解了. (好像我只是个搬运工--) 这题真可以叫做NOIplus了,跟其他几道比较水的题果然不一样,无 ...

  4. NOIP2018 保卫王国(动态DP)

    题意 求最小权值点覆盖. mmm次询问,每次给出两个点,分别要求每个点必须选或必须不选,输出每次的最小权值覆盖或者无解输出−1-1−1 题解 强制选或者不选可以看做修改权值为±∞\pm\infin±∞ ...

  5. 【比赛】NOIP2018 保卫王国

    DDP模板题 #include<bits/stdc++.h> #define ui unsigned int #define ll long long #define db double ...

  6. luogu5024 [NOIp2018]保卫王国 (动态dp)

    可以直接套动态dp,但因为它询问之间相互独立,所以可以直接倍增记x转移到fa[x]的矩阵 #include<bits/stdc++.h> #define CLR(a,x) memset(a ...

  7. 2019.02.16 bzoj5466: [Noip2018]保卫王国(链分治+ddp)

    传送门 题意简述: mmm次询问,每次规定两个点必须选或者不选,求树上的带权最小覆盖. 思路: 考虑链分治+ddpddpddp 仍然是熟悉的套路,先考虑没有修改的状态和转移: 令fi,0/1f_{i, ...

  8. [NOIP2018]保卫王国(树形dp+倍增)

    我的倍增解法吊打动态 \(dp\) 全局平衡二叉树没学过 先讲 \(NOIP\) 范围内的倍增解法. 我们先考虑只有一个点取/不取怎么做. \(f[x][0/1]\) 表示取/不取 \(x\) 后,\ ...

  9. 「NOIP2018 保卫王国」

    题目 强制选点我们可以把那个点权搞成\(-inf\),强制不选我们搞成\(inf\),之后就真的成为动态\(dp\)的板子题了 由于不想像板子那样再写一个最大独立集的方程,之后利用最小点覆盖=总点权- ...

随机推荐

  1. PHP date_parse_from_format() 函数

    ------------恢复内容开始------------ 实例 根据指定的格式返回一个包含指定日期信息的关联数组: <?phpprint_r(date_parse_from_format(& ...

  2. 线性DP 学习笔记

    前言:线性DP是DP中最基础的.趁着这次复习认真学一下,打好基础. ------------------ 一·几点建议 1.明确状态的定义 比如:$f[i]$的意义是已经处理了前$i个元素,还是处理第 ...

  3. 012_go语言中的Functions 函数

    代码演示 package main import "fmt" func plus(a int, b int) int { return a + b } func plusPlus( ...

  4. 用 Python 可以实现侧脸转正脸?我也要试一下!

    作者 | 李秋键 责编 | Carol 封图 | CSDN 下载自视觉中国 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例 ...

  5. Python分析「我们为什么这么穷」

  6. Paper English

    论文中的英语 单词 a arange 整理 ambiguity 含糊的 aggregate 总量 auxiliary 辅助的 alleviate 缓解 aberrant 异常的 akin 类似的 Ac ...

  7. java_线程、同步、线程池

    线程 Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例 Thread类常用方法 构造方法 public Thread():分配一个新的线程 ...

  8. 微信小程序通过二维码获取参数运行

    小程序开发过程中会遇到参数id会通过二维码获取,然后执行接口获取数据,但是难免会遇到带过来的参数出现乱码,这样就需要解码,多个参数时就需要进行处理取我们需要的字段值:小程序开发过程中会遇到参数id会通 ...

  9. 11 Linux(CentOS)用户与权限

    用户管理 root 在root用户下可以进行以下操作 useradd 用户名      新增用户 userdel 用户名       删除用户,同时应该删除家目录下的用户文件,与邮箱目录下的用户文件 ...

  10. java 多态二

    一 多态-转型 多态的转型分为向上转型与向下转型两种: 向上转型:当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程. 使用格式: 父类类型  变量名 = new 子类类型() ...