前言

题目链接:洛谷

题意简述

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

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

题目分析

将题目拆解成树上 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. mysql删除主键索引,删除索引语法

    mysql删除主键索引,删除索引语法 ### Incorrect table definition; there can be only one auto column and it must be ...

  2. 微信支付普通商户与AppID账号关联管理-授权

    微信支付普通商户与AppID账号关联管理 二.名词解释 名词 释义 微信支付普通商户 公司企业.政府机关.事业单位.社会组织.个体工商户.个人卖家.小微商户.(微信支付商户接入指引) AppID 已通 ...

  3. python提取特定格式的数据

    Excel Grid Data Converter 知识点总结 本文档总结了 ExcelGridConverter.py 脚本所涉及的关键 Python 知识点.该脚本用于从多个 Excel 文件中提 ...

  4. 使用AWS SageMaker进行机器学习项目

    使用AWS SageMaker进行机器学习项目 本文主要介绍如何使用AWS SageMaker进行机器学习项目. 1. 题目 使用的题目为阿里天池的"工业蒸汽量预测",题目地址为: ...

  5. arm linux 移植 iperf3

    背景 新做的硬件需要有进行一些板级接口测试:关于网络的测试很多时候只是停留在 ping 通:能够使用就算了.不知道网络的丢包率,也不知道网络吞吐的性能. 因此,需要使用一些专业化的工具来进行测试:查阅 ...

  6. 通过 hexo 生成静态博客

    通过 hexo 生成静态博客 背景 在对比了很多博客网站以后,我决定开始慢慢迁移我的文章,以后有时间的话还会搭建自己的网站,目前主流的静态博客生成器有三个: jekyll, hexo, hugo. 静 ...

  7. 典型性相关分析在SPSS中的实现

    典型性相关分析是研究两组变量(每组变量中都可能有多个指标)之间相关关系的一种多元统计方法.它能够揭示出两组变量之间的内在联系. 本文着重模型在spss中的应用,通过一道例题解释各个指标的意义.详细推导 ...

  8. Spring Boot集成Mybatis分页插件pagehelper

    引入依赖 <!--分页插件开始--> <dependency> <groupId>com.github.pagehelper</groupId> < ...

  9. Dubbo广播机制源码解读

    总结/朱季谦 先前在测试环境遇到过一个问题,即Dubbo广播机制,在对各个提供者节点进行广播操作过程中,存在最前面的两个节点出现异常的情况,但后边的其他节点仍能正常同步的情况.我以前就知道Dubbo的 ...

  10. Luogu P2036 [COCI2008-2009 #2] PERKET

    今天我们来看一道题:Luogu P2036 [COCI2008-2009 #2] PERKET 这道题不难,典型的暴力枚举 由于食材数量随机,无法直接用循环解,但是可以使用递归 \(MY_{CODE: ...