前言

题目链接:洛谷

题意简述

树套环上求至少间隔两个位置的最大独立集。

(树套环,即树上每个结点都是一个结点或环)

题目分析

将题目拆解成树上 DP 和环上 DP 即可。用 tarjan 缩点就行。

树上 DP

先来看看树上 DP。

显然每个点有三个状态:不选中且周围没选中、选中、不选中但在选中的点的旁边。记成 \(f[yzh][0/1/2]\)。设 \(xym\) 是 \(yzh\) 的一个孩子。

先来看看 \(f[yzh][0]\)。由于她不选中,且不在一个选中的旁边,可以从孩子的 \(0\)、\(2\) 状态转移而来。

\[f[yzh][0] = \sum \max \Big \lbrace f[xym][0], f[xym][2] \Big \rbrace
\]

\(f[yzh][1]\)。由于她选中,那么孩子在之前必须没被选中,且不在选中的点的旁边,即只能从孩子的 \(0\) 状态转移而来。注意加上她的价值。

\[f[yzh][1] = \operatorname{val}(yzh) + \sum f[xym][0]
\]

\(f[yzh][2]\)。她能且只能从一个孩子的 \(1\) 状态转移而来,其他儿子要么是 \(0\) 状态,要么是 \(2\) 状态。取最大值。

\[f[yzh][2] = f[xym'][1] + \sum _ {xym \neq xym'} \max \Big \lbrace f[xym][0], f[xym][2] \Big \rbrace
\]

这样,得到 \(20 \%\) 树的部分分。

环上 DP

先把环“拉下来”,即把环按照一定顺序存下来。类似于树,记 \(g[i][0/1/2]\) 表示考虑到了环上前 \(i\) 个点,第 \(i\) 个点状态下最大价值,再记 \(f[i][0/1/2]\) 表示从第 \(i\) 个点,走到子树里,树形 DP 的结果。先不考虑其他的,来看看转移方程。

\(g[yzh][0]\)。在树里,她不能被选中,即 \(f[yzh][0]\);在环里,前一个位置不能是 \(1\) 状态。

\[g[yzh][0] = f[yzh][0] + \max \lbrace g[yzh - 1][0], g[yzh - 1][2] \rbrace
\]

\(g[yzh][1]\)。不妨把 \(yzh\) 的价值计算到树里。环里前一个位置之前不能选。那么就是 \(f[yzh][1]\) 和 \(g[yzh - 1][0]\) 的和。

\[g[yzh][1] = f[yzh][1] + g[yzh - 1][0]
\]

\(g[yzh][2]\)。这里需要分类讨论,这个 \(2\) 状态可能是应为环里前一个位置是 \(1\) 状态,或者树里达到了 \(2\) 状态。取最大值即可。

\[g[yzh][2] = \max \Big \lbrace g[yzh - 1][1] + f[yzh][0], \max \lbrace g[yzh - 1][0], g[yzh - 1][2] \rbrace + f[yzh][2] \Big \rbrace

\]

以上就是转移方程。重点在环的特殊性。所以套路化地想到,把左侧强制设为某一个状态,那么右侧只有部分状态是有效的。为了之后树形 DP,我们不妨把这个环的“上顶点”移到我们“拉下来”的数组的右侧。

  1. 若左侧是 \(0\) 状态。

    那么右侧三个状态都合法。
  2. 若左侧是 \(1\) 状态。

    那么右侧只能是环上 DP 出来的 \(0\) 状态,并且在反映到树形 DP 上时是 \(2\) 状态。
  3. 若左侧是 \(2\) 状态。

    那么右侧 \(0\) 状态或 \(2\) 状态都是合法的。

这样看起来没有问题了。但是,还是会错,给出我对拍出来的一组数据:

10 11
5 1 1 1 3 5 7 2 8 9
1 2
2 3
3 4
4 5
5 1
6 7
7 8
8 9
9 10
10 6
1 6

答案显然是 \(13\),可是我们输出了 \(14\)。(好吧,可能有那么一点不显然)

为什么呢?问题就出在了我们对左侧 \(0\) 状态的考虑。这个位置的右边可能后来被选中了,而在树形 DP 上传答案的时候,右侧也是选中的,这样两者中间只间隔了 \(1\) 个,冲突了。

如何解决呢?再强制以下就好了:一遍强制左侧右边那个位置不能选中,就可以放心转移了。

代码

代码实现很清晰。略去了快读。注意用 vector 可能会含泪 MLE \(90\) 分,用链式前向星即可。可能有之前卡空间的遗物?

#include <vector>
#include <array>
#include <cstdio>
#include <stack>
using namespace std; using node = array<int, 3>; inline int max(int a, int b, int c) {
return max(max(a, b), c);
} struct Graph{
struct node{
int to, nxt;
} edge[2000010 << 1];
int eid, head[1000010];
inline void add(int u, int v){
edge[++eid] = {v, head[u]};
head[u] = eid;
}
inline node & operator [] (const int x){
return edge[x];
}
} xym; int n, m, val[1000010], ans;
vector<int> yzh[1000010]; int dfn[1000010], low[1000010], timer;
stack<int> st;
int sccno[1000010], scc_cnt;
void tarjan(int now, int fr){
dfn[now] = low[now] = ++timer, st.push(now);
for (int i = xym.head[now], to; to = xym[i].to, i; i = xym[i].nxt) {
if (!dfn[to]) tarjan(to, now), low[now] = min(low[now], low[to]);
else if (to != fr) low[now] = min(low[now], dfn[to]);
}
if (low[now] >= dfn[now]){
++scc_cnt;
while (true) {
int tp = st.top(); st.pop();
yzh[scc_cnt].push_back(tp), sccno[tp] = scc_cnt;
if (tp == now) break;
}
}
} void move_to_end(vector<int> &vec, int x) {
vector<int> res;
int len = vec.size(), pos = 0;
for (int i = 0; i < len; ++i) {
if (vec[i] == x) {
pos = i;
break;
}
}
for (int i = pos + 1; i < len; ++i)
res.push_back(vec[i]);
for (int i = 0; i < pos; ++i)
res.push_back(vec[i]);
res.push_back(x);
vec = move(res);
} bool vis[1000010];
node dfs(int now, int top, int fa) {
vis[now] = true;
move_to_end(yzh[now], top); // 把 top 放在最后
int tot = yzh[now].size();
vector<node> f(tot, {0, 0, 0}), dp(tot, {0, 0, 0});
for (int i = 0; i < tot; ++i) {
int son = yzh[now][i];
f[i][0] = 0, f[i][1] = val[son], f[i][2] = 0;
int mx = -0x3f3f3f3f;
for (int j = xym.head[son], to; to = xym[j].to, j; j = xym[j].nxt) {
int bl = sccno[to];
if (bl == fa || bl == now) continue;
node yzh = dfs(bl, to, now);
f[i][0] += max(yzh[0], yzh[2]);
f[i][1] += yzh[0];
f[i][2] += max(yzh[0], yzh[2]);
mx = max(mx, yzh[1] - max(yzh[0], yzh[2]));
}
f[i][2] += mx;
}
node res = {-0x3f3f3f3f, -0x3f3f3f3f, -0x3f3f3f3f}; yzh[now].clear();
yzh[now].shrink_to_fit(); if (tot == 1) {
res[0] = f[0][0];
res[1] = f[0][1];
res[2] = f[0][2];
return res;
} // 左边是空的 并且可能被 1 占用
dp[0][0] = f[0][0], dp[0][1] = -0x3f3f3f3f, dp[0][2] = -0x3f3f3f3f;
for (int i = 1; i < tot; ++i) {
dp[i][0] = f[i][0] + max(dp[i - 1][0], dp[i - 1][2]);
dp[i][1] = f[i][1] + dp[i - 1][0];
dp[i][2] = max(dp[i - 1][1] + f[i][0], max(dp[i - 1][0], dp[i - 1][2]) + f[i][2]);
}
res[0] = max(res[0], dp[tot - 1][0]);
res[2] = max(res[2], dp[tot - 1][2]); // 左边是空的 并且不能被 1 占用
dp[0][0] = f[0][0], dp[0][1] = -0x3f3f3f3f, dp[0][2] = -0x3f3f3f3f;
for (int i = 1; i < tot; ++i) {
dp[i][0] = f[i][0] + max(dp[i - 1][0], dp[i - 1][2]);
if (i != 1) dp[i][1] = f[i][1] + dp[i - 1][0];
else dp[i][1] = -0x3f3f3f3f;
dp[i][2] = max(dp[i - 1][1] + f[i][0], max(dp[i - 1][0], dp[i - 1][2]) + f[i][2]);
}
res[0] = max(res[0], dp[tot - 1][0]);
res[1] = max(res[0], dp[tot - 1][1]);
res[2] = max(res[2], dp[tot - 1][2]); // 左边在真的旁边
dp[0][0] = -0x3f3f3f3f, dp[0][1] = -0x3f3f3f3f, dp[0][2] = f[0][2];
for (int i = 1; i < tot; ++i) {
dp[i][0] = f[i][0] + max(dp[i - 1][0], dp[i - 1][2]);
dp[i][1] = f[i][1] + dp[i - 1][0];
dp[i][2] = max(dp[i - 1][1] + f[i][0], max(dp[i - 1][0], dp[i - 1][2]) + f[i][2]);
}
res[0] = max(res[0], dp[tot - 1][0]);
res[2] = max(res[2], dp[tot - 1][2]); // 左边是真的
dp[0][0] = -0x3f3f3f3f, dp[0][1] = f[0][1], dp[0][2] = -0x3f3f3f3f;
for (int i = 1; i < tot; ++i) {
dp[i][0] = f[i][0] + max(dp[i - 1][0], dp[i - 1][2]);
dp[i][1] = f[i][1] + dp[i - 1][0];
dp[i][2] = max(dp[i - 1][1] + f[i][0], max(dp[i - 1][0], dp[i - 1][2]) + f[i][2]);
}
res[2] = max(res[2], dp[tot - 1][0]); f.clear(), f.shrink_to_fit();
dp.clear(), dp.shrink_to_fit(); return res;
} signed main() {
read(n), read(m);
for (int i = 1; i <= n; ++i) read(val[i]);
for (int i = 1, u, v; i <= m; ++i) {
read(u), read(v);
xym.add(u, v), xym.add(v, u);
}
for (int i = 1; i <= n; ++i) if (!dfn[i]) tarjan(i, 0);
for (int i = 1; i <= n; ++i)
if (!vis[sccno[i]]) {
node res = dfs(sccno[i], i, 0);
ans += max(res[0], res[1], res[2]);
}
printf("%d", ans);
return 0;
}

后记 & 反思

没有什么是分类讨论解决不了的。如果有,那是你没有讨论细致。

[SDOI2010] 城市规划 题解的更多相关文章

  1. luogu 2478 [SDOI2010]城市规划 仙人掌上dp.

    LINK:城市规划 以前ls 让写的时候由于看不懂题目+以为在图中的环上dp非常困难所以放弃治疗了. 现在终于能把题目看懂了 泪目... 题目其实就是在说 给出一张图这个有一个非常好的性质 满足每个点 ...

  2. BZOJ3456:城市规划——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=3456 求出n个点的简单(无重边无自环)无向连通图数目 模数很熟悉,先敲一个NTT. 然后通过推导式 ...

  3. 【BZOJ-1952】城市规划 [坑题] 仙人掌DP + 最大点权独立集(改)

    1952: [Sdoi2010]城市规划 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 73  Solved: 23[Submit][Status][ ...

  4. BZOJ-1951 古代猪文 (组合数取模Lucas+中国剩余定理+拓展欧几里得+快速幂)

    数论神题了吧算是 1951: [Sdoi2010]古代猪文 Time Limit: 1 Sec Memory Limit: 64 MB Submit: 1573 Solved: 650 [Submit ...

  5. 图论杂项细节梳理&模板(虚树,圆方树,仙人掌,欧拉路径,还有。。。)

    orzYCB 虚树 %自为风月马前卒巨佬% 用于优化一类树形DP问题. 当状态转移只和树中的某些关键点有关的时候,我们把这些点和它们两两之间的LCA弄出来,以点的祖孙关系连成一棵新的树,这就是虚树. ...

  6. bzoj AC倒序

    Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...

  7. 【题解】古代猪文 [SDOI2010] [BZOJ1951] [P2480]

    [题解]古代猪文 [SDOI2010] [BZOJ1951] [P2480] 在那山的那边海的那边有一群小肥猪.他们活泼又聪明,他们调皮又灵敏.他们自由自在生活在那绿色的大草坪,他们善良勇敢相互都关心 ...

  8. 【题解】SDOI2010所驼门王的宝藏(强连通分量+优化建图)

    [题解]SDOI2010所驼门王的宝藏(强连通分量+优化建图) 最开始我想写线段树优化建图的说,数据结构学傻了233 虽然矩阵很大,但是没什么用,真正有用的是那些关键点 考虑关键点的类型: 横走型 竖 ...

  9. 【题解】P4841 城市规划(指数型母函数+多项式Ln)

    [题解]P4841 城市规划 P4841 城市规划 超级弱化版本(DP):POJ - 1737 两张图不同当且仅当边的分布不一样的时候,带编号最后乘一个阶乘即可,现在最主要的问题就是"联通& ...

  10. 【题解】P2480 [SDOI2010]古代猪文 - 卢卡斯定理 - 中国剩余定理

    P2480 [SDOI2010]古代猪文 声明:本博客所有题解都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。 题目描述 猪王国的文明源远流长,博大精 ...

随机推荐

  1. JSONObject应用Json字符串和Object对象之间的转换,Map封装数据思路

    JSONObject应用Json字符串和Object对象之间的转换,Map封装数据思路 package com.example.core.mydemo.json5; import com.alibab ...

  2. hbase的优缺点

    一. 一个关于hbase介绍全面的博客地址 https://www.csdn.net/gather_22/MtTaEgysNjYwOS1ibG9n.html 优点: 1,方便高效的压缩数据. 2,支持 ...

  3. 认真学习CSS3-问题收集-101号-莫名其妙的row行高

    其他人都有事情,有些事情只好自己上阵,自己做,最踏实! 做了两个基本一样的页面,都是采用bootsrap+jquey+js的技术,业务内容就是简单的查询,加上一些简单的效果,没有啥特别的内容. 由于历 ...

  4. 11-DNS域名解析服务

    背景 我们都知道,用ip可以唯一标识互联网上的主机. 从前,互联网的主机非常的少.我们都可以记住每台Server的ip. 就像是大哥大时期,电话非常少,电话号码也就非常少,我们都能记住某个人的电话. ...

  5. 手把手带你使用JWT实现单点登录

    JWT(英文全名:JSON Web Token)是目前最流行的跨域身份验证解决方案之一,今天我们一起来揭开它神秘的面纱! 一.故事起源 说起 JWT,我们先来谈一谈基于传统session认证的方案以及 ...

  6. yb课堂 用户模块个人中心 《四十一》

    Personal.vue <template> <div> <div class="container"> <div class=&quo ...

  7. mac电脑好用的工具总结

    制作gif:https://gfycat.com/gifbrewery 制作gif(超级好用,制作速度快,压缩图片小):https://www.cockos.com/licecap/ 解压工具:htt ...

  8. idea 提交代码到GitHub

    配置账户 配置Git安装目录 一般默认识别,其他参数不变 配置GitHub账户 提交到GitHub 1.VCS->import into version control -> share ...

  9. 2 手机号登录时,调的不是login接口,而是注册的一个接口

    手机号登录时,调的不是login接口,而是注册的一个接口

  10. 解码 xsync 的 map 实现

    解码 xsync 的 map 实现 最近在寻找 Go 的并发 map 库的时候,翻到一个 github 宝藏库,xsync (https://github.com/puzpuzpuz/xsync) . ...