前言

题目链接:洛谷

题意简述

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

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

题目分析

将题目拆解成树上 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. es6.6.1 索引的增加,查询,修改,删除

    1.新增 test2/user2/1/_create PUT操作{"name":"qiqi","age":17} 2.查询 test2/us ...

  2. 给你的博客加上个Live2D看板娘吧

    Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` 给你的博客加上个Live2D看板娘吧 日期:2017-12 ...

  3. python 注册nacos 进行接口规范定义

    背景: 一般场景 python服务经常作为java下游的 算法服务或者 数据处理服务 但是使用http 去调用比较不灵活,通过注册到nacos上进行微服务调用才是比较爽的 1.定义feginapi的接 ...

  4. Xcode编译错误看不到错误详情

    问题描述 Xcode提示错误后想详细看报错信息无论如何双击都不见弹出错误详情. 解决办法 不知道是bug还是苹果婊又自作主张改变用户习惯了,需要点击navigator最右边的一个图标,在里面找到相应的 ...

  5. 记录一下第一次webSocket通信成功

    webSocket前端代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset=& ...

  6. setsocket、getsocket 函数详解

    背景 以前用到socket的时候会调用setsocket进行设置,现在整理有关的笔记的时候,重新查阅资料发现有点奇怪,发现大家比较少使用到这个. setsocket/getsocket #includ ...

  7. Pypi配置API Token

    技术背景 在许久之前写的一篇博客中,我们介绍过使用twine向pypi上传我们自己的开源包的方法.最近发现这个方法已经不再支持了(报错信息如下所示),现在最新版需要使用API Token进行文件上传, ...

  8. oeasy教您玩转vim - 8 - # 追加文本

    追加文本 回忆上节课内容 我们这次深入了 i 命令 i 在当前的光标之前插入 I 在本行文本的最前面插入 还有一些常用的编辑命令 . 重做 u 撤销 ctrl+r 还原 关于插入,还有什么讲究吗? 类 ...

  9. [oeasy]python0081_ANSI序列由来_终端机_VT100_DEC_VT选项_终端控制序列

    更多颜色 回忆上次内容 上次 首先了解了RGB颜色设置 可以把一些抽象的色彩名字 落实到具体的 RGB颜色 计算机所做的一切 其实就是量化.编码 把生活的一切都进行数字化 标准 是ANSI制定的 这个 ...

  10. CF1929B Sasha and the Drawing 题解

    CF1929B 题意 给定一个 \(n\times n\) 的正方形,已知正方形最多有 \(4\times n-2\) 条对角线,要求要有至少 \(k\) 条对角线经过至少一块黑色方格,求至少要将几条 ...