[SDOI2010] 城市规划 题解
前言
题目链接:洛谷。
题意简述
树套环上求至少间隔两个位置的最大独立集。
(树套环,即树上每个结点都是一个结点或环)
题目分析
将题目拆解成树上 DP 和环上 DP 即可。用 tarjan 缩点就行。
树上 DP
先来看看树上 DP。
显然每个点有三个状态:不选中且周围没选中、选中、不选中但在选中的点的旁边。记成 \(f[yzh][0/1/2]\)。设 \(xym\) 是 \(yzh\) 的一个孩子。
先来看看 \(f[yzh][0]\)。由于她不选中,且不在一个选中的旁边,可以从孩子的 \(0\)、\(2\) 状态转移而来。
\]
\(f[yzh][1]\)。由于她选中,那么孩子在之前必须没被选中,且不在选中的点的旁边,即只能从孩子的 \(0\) 状态转移而来。注意加上她的价值。
\]
\(f[yzh][2]\)。她能且只能从一个孩子的 \(1\) 状态转移而来,其他儿子要么是 \(0\) 状态,要么是 \(2\) 状态。取最大值。
\]
这样,得到 \(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][1]\)。不妨把 \(yzh\) 的价值计算到树里。环里前一个位置之前不能选。那么就是 \(f[yzh][1]\) 和 \(g[yzh - 1][0]\) 的和。
\]
\(g[yzh][2]\)。这里需要分类讨论,这个 \(2\) 状态可能是应为环里前一个位置是 \(1\) 状态,或者树里达到了 \(2\) 状态。取最大值即可。
\]
以上就是转移方程。重点在环的特殊性。所以套路化地想到,把左侧强制设为某一个状态,那么右侧只有部分状态是有效的。为了之后树形 DP,我们不妨把这个环的“上顶点”移到我们“拉下来”的数组的右侧。
- 若左侧是 \(0\) 状态。
那么右侧三个状态都合法。 - 若左侧是 \(1\) 状态。
那么右侧只能是环上 DP 出来的 \(0\) 状态,并且在反映到树形 DP 上时是 \(2\) 状态。 - 若左侧是 \(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] 城市规划 题解的更多相关文章
- luogu 2478 [SDOI2010]城市规划 仙人掌上dp.
LINK:城市规划 以前ls 让写的时候由于看不懂题目+以为在图中的环上dp非常困难所以放弃治疗了. 现在终于能把题目看懂了 泪目... 题目其实就是在说 给出一张图这个有一个非常好的性质 满足每个点 ...
- BZOJ3456:城市规划——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=3456 求出n个点的简单(无重边无自环)无向连通图数目 模数很熟悉,先敲一个NTT. 然后通过推导式 ...
- 【BZOJ-1952】城市规划 [坑题] 仙人掌DP + 最大点权独立集(改)
1952: [Sdoi2010]城市规划 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 73 Solved: 23[Submit][Status][ ...
- BZOJ-1951 古代猪文 (组合数取模Lucas+中国剩余定理+拓展欧几里得+快速幂)
数论神题了吧算是 1951: [Sdoi2010]古代猪文 Time Limit: 1 Sec Memory Limit: 64 MB Submit: 1573 Solved: 650 [Submit ...
- 图论杂项细节梳理&模板(虚树,圆方树,仙人掌,欧拉路径,还有。。。)
orzYCB 虚树 %自为风月马前卒巨佬% 用于优化一类树形DP问题. 当状态转移只和树中的某些关键点有关的时候,我们把这些点和它们两两之间的LCA弄出来,以点的祖孙关系连成一棵新的树,这就是虚树. ...
- bzoj AC倒序
Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...
- 【题解】古代猪文 [SDOI2010] [BZOJ1951] [P2480]
[题解]古代猪文 [SDOI2010] [BZOJ1951] [P2480] 在那山的那边海的那边有一群小肥猪.他们活泼又聪明,他们调皮又灵敏.他们自由自在生活在那绿色的大草坪,他们善良勇敢相互都关心 ...
- 【题解】SDOI2010所驼门王的宝藏(强连通分量+优化建图)
[题解]SDOI2010所驼门王的宝藏(强连通分量+优化建图) 最开始我想写线段树优化建图的说,数据结构学傻了233 虽然矩阵很大,但是没什么用,真正有用的是那些关键点 考虑关键点的类型: 横走型 竖 ...
- 【题解】P4841 城市规划(指数型母函数+多项式Ln)
[题解]P4841 城市规划 P4841 城市规划 超级弱化版本(DP):POJ - 1737 两张图不同当且仅当边的分布不一样的时候,带编号最后乘一个阶乘即可,现在最主要的问题就是"联通& ...
- 【题解】P2480 [SDOI2010]古代猪文 - 卢卡斯定理 - 中国剩余定理
P2480 [SDOI2010]古代猪文 声明:本博客所有题解都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。 题目描述 猪王国的文明源远流长,博大精 ...
随机推荐
- 两个Excel表格核对 excel表格中# DIV/0 核对两个表格的差异,合并运算VS高级筛选
两个Excel表格核对 excel表格中# DIV/0 核对两个表格的差异,合并运算VS高级筛选 1.两列顺序一样的数据核对 方法1:加一个辅助列,=B2=C2 结果为FALSE的就是不相同的 方 ...
- VMWare配置处理器个数和实际电脑CPU核心线程数关系
配置说明 处理器数量 :指CPU内核数量(例如:4C / 8C),并不是指CPU颗数. 每个处理的核心数量:指CPU中的线程(4C8T中的8T),并不是指核心(Core)数量. 示例配置 处理器数量 ...
- 06-Python类与对象
什么是类 百度百科: 类是对象的抽象,对象是对客观事物的抽象. 用通俗的话来说: 类是类别的意思,是数据类型. 对象是类别下的具体事物. 也就是说: 类是数据类型,对象是变量. 比如: 自定义一种数据 ...
- 虚拟机安装Linux CENTOS 07 部署NET8 踩坑大全
首先下载centos07镜像,建议使用阿里云推荐的地址: https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/?spm=a2c6h.25603 ...
- Win11在VMWare中无tpm条件下安装
Win11在VMWare中无tpm条件下安装 在条件不满足提示的窗口下. 按shift+F10打开cmd, 输入regedit打开注册表, 按如下路径新建三个值后即可 [HKEY_LOCAL_MACH ...
- 最新扣子(Coze)实战案例:扣子卡片的制作及使用,完全免费教程
♂️ 大家好,我是斜杠君,手把手教你搭建扣子AI应用. ☘️ 本文是<AI应用开发系列教程之扣子(Coze)实战教程>,完全免费学习. 关注斜杠君,可获取完整版教程. 如果想学习AI应用 ...
- Linux 进程运行状态
背景: 以下有关的知识点是在多进程拷贝的时候,执行了sync导致卡死导致的. Linux进程状态:R (TASK_RUNNING),可执行状态.只有在该状态的进程才可能在CPU上运行.而同一时刻可能有 ...
- uCos 学习:0-有关概念
先说一下UCOSIII:Micrium在2009年推出了UCOSIII,相对于之前的UCOSII版本,在性能上有了进一步的提升,主要是支持时间片轮调度,极短的关中断事件等. 可剥夺多任务管理: 什么是 ...
- 金蝶云·苍穹追光者开发大赛,点燃高校AI应用创新之火
在 2024 年的政府工作报告中,"人工智能 +" 行动被提出,标志着人工智能成为推动我国新质生产力发展的关键力量.与此同时,今年的高考作文有一道题目也聚焦于人工智能,再次凸显了这 ...
- CF187D 题解
模拟考最后一题是这道题,要是数组开大就场切了,最后不小心挂了 \(15\) 分. 以下是考场思路: 考虑这样一个问题,所有时间对 \(r+g\) 取余是可以的.毕竟红绿灯是一个循环. 再考虑这样一个东 ...