Luogu P3886 [JLOI2009]神秘的生物 最小表示法,轮廓线DP,插头DP,动态规划
亲手写掉的第一道最小表示法!哈哈哈太开心啦~
不同于以往的几个插头\(dp\),这个题目的轮廓线是周围的一圈\(n\)个格子。而其所谓“插头”也变成了相邻格子的所属连通分量编号,并不是直接把前面几个题的思想往上套就可以轻松解决的了。这里我们就要采用一种叫最小表示法的东西来表示它的连通性信息啦~
(其实感觉是不是称之为逐格递推的轮廓线\(dp\)比较好。。。
而最小表示法是什么呢?举个例子,现在有这样一个序列\((5,5,3,2,4,1,3,2)\),序列中的每一个数代表第\(i\)个格子所属的连通分量。因为编号是人为设置的,所以其每一个编号也可以一一映射成等效的其他编号。为了连通性表示的规范和不重不漏,我们就把这个序列整理成意义等效的前提下字典序最小的形式,对这个序列就是\((1,1,2,3,4,5,2,3)\)。
整理的过程很简单,既然让字典序最小,那就尽可能让前面的比后面的小。从前向后遍历,如果当前这个编号第一次出现,就把它记录下来,换成新的编号\(tot+1\)。否则就直接将其改为已经记录下来的对应编号。
void min_express (int &zt) {
// zt 表示状态,tot 表示新整理的最大编号。
int tot = 0, id[N] = {0};
for (int i = 0; i < n; ++i) {
int now = get_wei (zt, i);
if (!now) continue;
if (id[now] != 0) {
zt = alt_wei (zt, i, id[now]);
} else {
zt = alt_wei (zt, i, id[now] = ++tot);
}
}
}
改完以后就可以考虑状态的转移了。这一点比较好考虑,都是插头\(dp\)的套路了。状态转移的讲解摘自洛谷日报——插头\(dp\)。(因为我太懒了\(QwQ\))
- 不选
当然得考虑状态合理,无下插头或下插头所在联通块还有其他插头,否则下插头被孤立而不形成联通块了
大家发现没有?
这里每个状态仅合理而已,并不能确定这是可取的最终状态,因为最后得保证只有一个联通块(其实上文也),详细请看 $update$
那为什么不考虑右插头呢?而要判断下插头呢?
因为下插头以后再也不会判断了,所以要考虑状态合理,右插头到下一行自然会考虑状态
右插头来自上一个转移状态,很可能形成新块 $7$ ,故这样转移条件会变得严苛,最终答案通常会小于正确答案
- 选
(1) !b1 and !b2 单独形成新块,此块命名为7
(2) b1 or b2 此块与插头相连,更新联通块状态,这就是伟大的最小表示法
一些解释:
\(Q\):为什么把新的连通块设置为7?
- \(A\):在最小表示法下,连通块标号从小到大记录。为了防止冲突,我们随手采用一个可以采用的里面最大的,到后面还会进行整理。
\(Q\):连上其他的分量应该怎么操作?
- \(A\):直接改就完事了\(=\_=\)。比如这个题里面,只要\(b1\)和\(b2\)有一个选中,你就可以连上其他分量,为了省一种讨论,我对目前这个格子采用了取两个分量较大值的方法,然后把其他编号等于两个分量编号较小值且不为\(0\)的块也改成这个较大分量的编号。
\(Code\):
#include <bits/stdc++.h>
using namespace std;
const int N = 9 + 5;
const int INF = 2e9;
const int base = 599999;
const int M = 600000 + 5;
void cmax (int &x, int y) {x = max (x, y);}
int ans = -INF;
int n, w[N][N];
int cur, las, cnt[2];
int nxt[M], head[M], dp[2][M], Hash[2][M];
int get_wei (int zt, int wei) {
return (zt >> (wei * 3)) % 8; // 8 进制状压
}
int alt_wei (int zt, int wei, int val) {
return zt - ((get_wei (zt, wei) - val) << (wei * 3));
}
int count (int zt, int val) {
int ret = -1;
for (int i = 0; i < n; ++i) {
ret += (get_wei (zt, i) == val);
}
return ret;
}
void min_express (int &zt) {
int tot = 0, id[N] = {0};
for (int i = 0; i < n; ++i) {
int now = get_wei (zt, i);
if (!now) continue;
if (id[now] != 0) {
zt = alt_wei (zt, i, id[now]);
} else {
zt = alt_wei (zt, i, id[now] = ++tot);
}
}
}
bool can_use (int zt) {
int tot0 = 0, tot1 = 0;
for (int i = 0; i < n; ++i) {
tot0 += get_wei (zt, i) == 0;
tot1 += get_wei (zt, i) == 1;
if (get_wei (zt, i) > 1) {
return false;
}
}
if (!tot1) return false;
return true;
}
void update (int zt, int val) {
min_express (zt);
if (can_use (zt)) {
ans = max (ans, val);
}
int _zt = zt % base;
for (int i = head[_zt]; i; i = nxt[i]) {
if (Hash[cur][i] == zt) {
cmax (dp[cur][i], val); return;
}
}
nxt[++cnt[cur]] = head[_zt];
head[_zt] = cnt[cur];
Hash[cur][cnt[cur]] = zt;
dp[cur][cnt[cur]] = val;
}
void print (int x, int y) {
cout << "r = " << x << " c = " << y << endl;
for (int i = 1; i <= cnt[cur]; ++i) {
cout << "zt = " << Hash[cur][i] << " ";
for (int j = 0; j < n; ++j) {
if (j != 0) cout << "_";
cout << get_wei (Hash[cur][i], j);
}
cout << " val = " << dp[cur][i] << endl;
}
}
int solve () {
update (0, 0);
// print (0, 0);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
las = cur, cur ^= 1, cnt[cur] = 0;
memset (head, 0, sizeof (head));
for (int k = 1; k <= cnt[las]; ++k) {
int zt = Hash[las][k];
int b1 = (j >= 2 ? get_wei (zt, j - 2) : 0);
int b2 = (j >= 1 ? get_wei (zt, j - 1) : 0);
int val = dp[las][k];
// 1. 不选当前格
if (!b2 || count (zt, b2)) {
// 没有插头 b2 或者 插头 b2 与轮廓线上其他的格子连通
update (alt_wei (zt, j - 1, 0), val); // 当前格子置为不选
}
if (!b1 && !b2) { // 新建连通分量
update (alt_wei (zt, j - 1, 7), val + w[i][j]);
}
if (b1 || b2) { // 连上其他的分量
int id = max (b1, b2), _zt = zt;
_zt = alt_wei (_zt, j - 1, id);
for (int i = 0; i < n; ++i) {
if (get_wei (_zt, i) == 0) continue;
if (get_wei (_zt, i) == min (b1, b2)) {
_zt = alt_wei (_zt, i, id);
}
}
update (_zt, val + w[i][j]);
}
}
// print (i, j);
}
}
return ans;
}
int main () {
// freopen ("data.in", "r", stdin);
cin >> n;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
cin >> w[i][j];
}
}
cout << solve () << endl;
}
Luogu P3886 [JLOI2009]神秘的生物 最小表示法,轮廓线DP,插头DP,动态规划的更多相关文章
- [JLOI2009]神秘的生物
题目链接 题目大意 给定一个\(n*n\)的矩阵,从其中选取恰好一个连通块,使选取的格子所对应的权值和最大. \(n\leq 9\) 解题思路 由于\(n\)特别小,考虑插头dp. 和一般的插头dp不 ...
- [JLOI2009]神秘的生物——轮廓线DP
原题链接 题目大意 \(n\times n\)的带权方阵,选一个权值最大的连通块 Solution 一眼连通性DP,然后就没了 转移很好想的啦,简单讨论一下就行了 有一个坑点,就是不能一个格子都不选, ...
- Luogu P3170 [CQOI2015]标识设计 状态压缩,轮廓线,插头DP,动态规划
看到题目显然是插头\(dp\),但是\(n\)和\(m\)的范围似乎不是很小.我们先不考虑复杂度设一下状态试试: 一共有三个连通分量,我们按照\(1,2,3\)的顺序来表示一下.轮廓线上\(0\)代表 ...
- luogu P5043 【模板】树同构 hash 最小表示法
LINK:模板 树同构 题目说的很迷 给了一棵有根树 但是重新标号 言外之意还是一棵无根树 然后要求判断是否重构. 由于时无根的 所以一个比较显然的想法暴力枚举根. 然后做树hash或者树的最小表示法 ...
- HDU 4162 Shape Number (最小表示法)
题意:给你一串n个数,求出循环来看一阶差的最小字典序:数字串看成一个顺时针的环,从某一点开始顺时针循环整个环,保证字典序最小就是答案 例如给你 2 1 3 就会得到(1-2+8 注意题意负数需要加8) ...
- POJ 1635 树的最小表示法/HASH
题目链接:http://poj.org/problem?id=1635 题意:给定两个由01组成的串,0代表远离根,1代表接近根.相当于每个串对应一个有根的树.然后让你判断2个串构成的树是否是同构的. ...
- HDU 2609 最小表示法
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2609 题意:给定n个循环链[串],问有多少个本质不同的链[串](如果一个循环链可以通过找一个起点使得和 ...
- HDU 4162 最小表示法
题目:http://acm.hdu.edu.cn/showproblem.php?pid=4162 题意:给定一个只有0-7数字组成的串.现在要由原串构造出一个新串,新串的构造方法:相邻2个位置的数字 ...
- POJ 1509 最小表示法
题目链接:http://poj.org/problem?id=1509 题意:给定一个字符串,求一个起点使字符串从该起点起的字符串字典序最小[题目的字符串起点从1开始] 思路:最小表示法模板题 #de ...
随机推荐
- Reactjs之Axios、fetch-jsonp获取后台数据
1.新增知识点 /** Axios获取服务器数据(无法跨域,只能让后台跨域获取数据) react获取服务器APi接口的数据: react中没有提供专门的请求数据的模块.但是我们可以使用任何第三方请求数 ...
- node.js入门经典 初读笔记
2.1 npm是什么 npm(Node Package Manager)是Node.js的包管理器.它允许开发人员在Node.js应用程序中创建.共享并重用模块. 2.3 安装模块 npm insta ...
- 更新操作 关于json字符串的拼接、json字符串与json对象之间的转换
更新操作 后台 /** * 更新人员 * @return "updateSdr" */ public String updateTheSdr(){ jsonstr = " ...
- 用linux主机做网关搞源地址转换(snat)
一.原理图 二.环境 外网 A:192.168.100.20 (vmnet1) 网关 B:192.168.100.10 (vmnet1) 192.168.200.10 (vmnet2) ...
- A1139-引爆炸弹 计蒜客 bfs剪枝
题目链接 https://nanti.jisuanke.com/t/A1139 在一个 n \times mn×m 的方格地图上,某些方格上放置着炸弹.手动引爆一个炸弹以后,炸弹会把炸弹所在的行和列上 ...
- k8s--kubernetes存储之Volume
Volume 容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题.首先,当容器崩溃时, kubelet会重启它,但是容器中的文件将丢失--容器以干净的状态(镜像最初的状态 ...
- Linux系统中使用Nignx配置反向代理负载均衡
目录 使用nginx实现动静分离的负载均衡集群 使用nginx实现负载均衡和动静分离 使用nginx实现动静分离的负载均衡集群 Nginx官网源码包下载链接:http://nginx.org/en/d ...
- PTA(Basic Level)1011.A+B和C
给定区间 [−231,231] 内的 3 个整数 A.B 和 C,请判断 A+B 是否大于 C. 输入格式: 输入第 1 行给出正整数 T (≤10),是测试用例的个数.随后给出 T 组测试用例,每组 ...
- PythonDay12
day12内置_函数 今日内容 生成器 推导式 内置函数一 生成器 什么是生成器?生成器的本质就是一个迭代器 迭代器是python自带的 生成器是程序员自己写的一种迭代器 生成器编写方式: 1.基于函 ...
- transition 带的参数什么意思
过渡transition:属性名 过渡效果时间 时间速度曲线 开始时间 默认值: all 0 e ...