[POI2015] POD 题解
前言
题目链接:洛谷。
题意简述
长度为 \(n\) 的一串项链,每颗珠子是 \(k\) 种颜色之一。第 \(i\) 颗与第 \(i-1, i+1\) 颗珠子相邻,第 \(n\) 颗与第 \(1\) 颗也相邻。切两刀,把项链断成两条链。要求每种颜色的珠子只能出现在其中一条链中。求方案数量(保证至少存在一种),以及切成的两段长度之差绝对值的最小值。
题目分析
据我所知,这道题有如下做法:
- 线段树 + 增量法 + 最值 / 二分
- 哈希 + 双指针
- 分治 + 双指针
写一篇题解以总结并加深印象。
方法 \(1\):线段树 + 增量法 + 最值 / 二分
分别对于每种颜色考虑。对于一种颜色,把这个环分成了若干段,我们要切一定是截取某一段的一部分,要不然就会跨过一个位置,不合法。暴力的想法是枚举一个端点,对每种颜色分别考虑,把合法的位置标记。最后被标记了 \(k\) 次的位置就是合法的位置。
优化也很简单,对于两个相邻的位置,需要更改的地方并不多。考虑增量法,把端点 \(i - 2 \sim i - 1\) 移动到 \(i - 1 \sim i\),只会对 \(i - 1\) 这个位置对应的颜色 \(col_{i - 1}\) 造成影响。我们把上一次出现 \(col_{i - 1}\) 的位置到 \(i - 2 \sim i - 1\) 减 \(1\),再把 \(i - 1 \sim i\) 到下一次出现的位置之间加 \(1\),就完成了更新。
这很线段树啊,先把边权右放,即用 \(i\) 这个位置的值代表 \(i - 1 \sim i\) 这条边,修改变成了区间加。第一个问题,即方案数是求区间最值和区间最值数量,很简单。
对于第二个问题,为了让答案尽量小,即让其中一段尽可能接近 \(\cfrac{n}{2}\),我们先找到那个位置 \(p\),这时候序列被 \(p\) 和 \(i\) 分成了三个部分,对于每个部分求区间最值出现的最靠近 \(p\) 的位置,容易发现就是最左边或最右边的位置,计算下距离,对答案取个 \(\min\) 即可。注意,那个部分的最值必须也要满足要求才能对答案有贡献。当然,也可以线段树上二分出最左边或最右边的位置。
此外,注意有些颜色可能在序列中只出现了一次,我们无论怎么切都不会使它们不合法,所以实现的时候特判一下就好了。以及环上的处理略为繁琐。
时间复杂度:\(\Theta(n \log n)\),瓶颈在线段树,但逝大常数,要卡卡常。
方法 \(2\):哈希 + 双指针
如果进一步分析问题很容易发现,环是一个幌子,我们只需要找到一段区间,对于每种颜色,要么全都在里面出现了,要么一次都没有出现,那么截取出来一定是合法的。因为对于令一半,每种颜色也可能是全都出现了或者一次都没有出现。
我们可以设计出一个哈希函数,对于每一种颜色全部出现和一次都没出现出现是等价的。
异或哈希:如果某种颜色出现了 \(c\) 次,让前 \(c - 1\) 出现的位置的权值设置成随机数,最后一位设置成前 \(c - 1\) 位的权值异或和。这样,如果有一个区间完全包含或完全不包含所有的位置,对于这一种颜色,异或和均为 \(0\)。当然,对于一个区间里的每一个颜色异或和都为 \(0\) 的话,这就是一个合法区间。哈希嘛,不需要考虑不同颜色之间的影响。于是问题变成了统计有多少区间异或和为 \(0\)。搞个前缀异或和,变成了前缀异或和相等的所有位置之间任意取构成的区间。如果某一种全缀和出现了 \(x\) 次,那么方案数增加 \(\cfrac{x(x - 1)}{2}\)。第二个问题,就是最接近 \(\cfrac{n}{2}\) 的两个位置,排序后双指针即可。
和哈希:设计一个哈希函数 \(F(x)\),对于一个局面我们定义其权值为 \(\sum \limits _ {i = 1} ^ {k} yzh[i] \cdot F(cnt[i])\),其中 \(yzh[i]\) 为每一种颜色对应的随机权,\(cnt[i]\) 为颜色 \(i\) 在这个局面出现的次数。\(F(x)\) 应满足对于 \(x = 0\) 和 $x = $ 颜色 \(i\) 在整个序列出现的总次数,返回值相同,对于其他 \(x\) 返回个随机值即可。那么不就和异或哈希后续处理一样了吗?
时间复杂度:\(\Theta(n \log n)\),瓶颈在排序,但挺快的。
方法 \(3\):分治 + 双指针
首先来考虑简化模型。对于相互包含的颜色,如 \(\texttt{A} \cdots \texttt{B} \cdots \texttt{A} \cdots \texttt{B}\),我们既要满足放在一段 \(\texttt{A}\) 中,也要满足放在一段 \(\texttt{B}\) 中,那么综合考虑,\(\texttt{A}\) 和 \(\texttt{B}\) 是没有区别的,那为什么不合并起来呢?发现简化后的模型,一种颜色只会出现在另一种颜色的某一段中。
不妨考虑一个颜色 \(\texttt{A}\),它出现在序列上是形如 \(\texttt{A} \cdots \texttt{A} \cdots \texttt{A}\) 的,不妨记作 \(f(\texttt{A})\),那么我们选取的一段一定是在相邻的一段 \(\texttt{A} \cdots \texttt{A}\) 中。我们分别考虑每一段。
每一段是形如 \(\texttt{A} f(\texttt{B}) f(\texttt{C}) \cdots f(\texttt{K}) \texttt{A}\) 的。显然被分成了若干子问题,递归解决每一个 \(f(\texttt{B} \cdots \texttt{K})\),接下来只用考虑选出的区间是完整的若干连续的子段,如 \(f(\texttt{B})\),\(f(\texttt{B}) f(\texttt{C})\) 等等。设 \(f(\texttt{B}) f(\texttt{C}) \cdots f(\texttt{K})\) 有 \(k\) 个子问题。那么对于当前对方案数的贡献是在 \(k + 1\) 个点中选出两个,其中构成的连续段就是选出段,方案数是 \(\cfrac{(k + 1) k}{2}\)。第二个问题同之前解决方法一样,双指针即可。
分治部分很好处理,那来说说预处理合并颜色怎么搞。不妨设 \(nxt_i\) 表示下一次出现 \(i\) 这个位置的颜色的位置,如果没有则是 \(0\)。那么对于相交的两个颜色,这样一段:\(j \cdots i \cdots nxt_j \cdots nxt_i\),即对于 \(i\),\(\exists j \in [1, i - 1] \land nxt_j \in (i, nxt_i)\)。对于前一个条件,我们从左到右扫过来,对于当前的 \(i\),只用考虑之前被扫过的 \(j\)。对于后一个条件,先考虑一个 \(nxt\) 从顶到底递增的单调栈,每次先把 \(nxt_j \leq i\) 的弹栈弹掉,对于剩下的元素,和 \(< nxt_i\) 的每个合并一下,但是时间复杂度会假。如果要保证时间复杂度,即每个元素只被加进去一次,弹出去一次,我们在访问的时候就要弹掉。
对于 \(i\) 之后的一个 \(k\),如果 \(i\) 和 \(k\) 满足交叉,那么这么做没有什么问题。但是如果有 \(nxt_k \leq nxt_i\) 并且存在一个被弹掉的 \(j\),满足 \(nxt_k > nxt_j\),就会出现问题,可见下图。
当然,类似红绿关系的可以不止两种,可以继续套。我们用上述单调栈合并后的结果是下图。
发现,合并后,如果还存在上述遗留情况的交叉,会越过处理后同一种颜色的相邻两段。而在处理前遗留情况是越过某一颜色的最后一次出现的位置。所以再用相同的算法处理一遍即可。
至于颜色的合并,使用并查集即可。
至此,我们完成了本题的求解。时间复杂度 \(\Theta(n \alpha (k))\)。瓶颈在于并查集。
代码
均给出了详细注释,供大家参考。
方法 \(1\):线段树 + 增量法 + 最值 / 二分
方法 \(2\):哈希 + 双指针
异或哈希
和哈希
方法 \(3\):分治 + 双指针
是最优解,才 \(200\) 毫秒左右。
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAX = 1 << 25;
char buf[MAX], *p = buf;
#define isdigit(x) ('0' <= x && x <= '9')
#define getchar() *p++
#define check(x) ans = min(ans, abs(n - ((x) << 1)))
inline void read(int &x) {
char ch = 0; x = 0;
for (; !isdigit(ch); ch = getchar());
for (; isdigit(ch); x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar());
}
const int N = 1e6 + 10;
const int inf = 0x3f3f3f3f;
int n, k, val[N], ans = inf;
long long cnt;
int fa[N], rk[N];
int get(int x) { return fa[x] == x ? x : fa[x] = get(fa[x]); }
void merge(int a, int b) {
if ((a = get(a)) == (b = get(b))) return;
if (rk[a] < rk[b]) swap(a, b);
fa[b] = a;
if (rk[a] == rk[b]) ++rk[a];
}
// 并查集
int fir[N], lst[N], nxt[N], pre[N];
inline int trans(int x) { return x ^ n ? x + 1 : 1; } // x 的下一个位置
inline int snart(int x) { return x ^ 1 ? x - 1 : n; } // x 的上一个位置
inline int dis(int x, int y) { return x - y + 1; } // y ~ x 这样一段的距离
void solve(int x) {
for (int i = trans(x); i != nxt[x]; i = trans(pre[i])) // i 是这种颜色出现的第一个位置,pre[i] 就是最后一个位置
for (int j = i; nxt[j] != i; j = nxt[j]) // 把这种颜色每一段分治解决
solve(j);
int cnt = 0;
for (int i = trans(x), j = i; i != nxt[x]; ++cnt, i = trans(pre[i])) { // 双指针找接近 n/2 的方案
while (j != nxt[x] && dis(pre[i], j) > n >> 1) j = trans(pre[j]);
check(dis(pre[i], j));
check(dis(pre[i], nxt[snart(j)]));
}
::cnt += 1ll * cnt * (cnt + 1);
}
int stack[N], top;
inline void init() {
memset(lst, 0, sizeof (int) * (k + 1)), top = 0;
for (int i = 1; i <= n; ++i) nxt[i] = 0, nxt[lst[val[i]]] = i, lst[val[i]] = i;
for (int i = 1; i <= n; ++i) if (nxt[i]) {
while (top && nxt[stack[top]] <= i) --top;
while (top && nxt[stack[top]] <= nxt[i]) merge(val[i], val[stack[top--]]);
stack[++top] = i;
} // 单调栈操作
for (int i = 1; i <= n; ++i) val[i] = get(val[i]); // 直接设置成合并后的颜色
}
signed main() {
fread(buf, 1, MAX, stdin);
read(n), read(k);
for (int i = 1; i <= n; ++i) read(val[i]);
for (int i = 1; i <= k; ++i) fa[i] = i;
init(), init(); // 预处理两遍
memset(lst, 0, sizeof (int) * (k + 1));
for (int i = 1; i <= n; ++i) {
if (!fir[val[i]]) fir[val[i]] = i;
else nxt[lst[val[i]]] = i;
lst[val[i]] = i;
}
for (int i = 1; i <= n; ++i) {
if (!nxt[i]) nxt[i] = fir[val[i]];
pre[nxt[i]] = i;
} // 处理前驱后继
int i = 1; do solve(i), i = nxt[i]; while (i != 1); // 最外层调用
printf("%lld %d", cnt >> 1, ans);
return 0;
}
后记
希望本文能够帮助你熟悉每一种方法。也希望我以后遇到这种题别寄了啊!
[POI2015] POD 题解的更多相关文章
- Luogu3587[POI2015]POD - hash + 单调队列
Solution 还是去看了题解. 感谢大佬的博客→ 题解传送门 是一道思路比较新的题. 搞一个前缀和, 记录前 $i$ 个位置每种颜色的出现次数, 如果位置 $i$ 是 颜色 $a[i]$ 的最后 ...
- BZOJ3747:[POI2015]Kinoman——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=3747 https://www.luogu.org/problemnew/show/P3582 共有 ...
- P3587 [POI2015]POD
题目描述 长度为n的一串项链,每颗珠子是k种颜色之一. 第i颗与第i-1,i+1颗珠子相邻,第n颗与第1颗也相邻.切两刀,把项链断成两条链.要求每种颜色的珠子只能出现在其中一条链中.求方案数量(保证至 ...
- POI2015 解题报告
由于博主没有BZOJ权限号, 是在洛咕做的题~ 完成了13题(虽然有一半难题都是看题解的QAQ)剩下的题咕咕咕~~ Luogu3585 [POI2015]PIE Solution 模拟, 按顺序搜索, ...
- luogu P3592 [POI2015]MYJ
题目链接 luogu P3592 [POI2015]MYJ 题解 区间dp 设f[l][r][k]表示区间l到r内最小值>=k的最大收益 枚举为k的位置p,那么包含p的区间答案全部是k 设h[i ...
- CF786B Legacy(线段树优化建边)
模板题CF786B Legacy 先说算法 如果需要有n个点需要建图 给m个需要建边的信息,从单点(或区间内所有点)向一区间所有点连边 如果暴力建图复杂度\(mn^2\) 以单点连向区间为例,在n个点 ...
- POI2015题解
POI2015题解 吐槽一下为什么POI2015开始就成了破烂波兰文题目名了啊... 咕了一道3748没写打表题没什么意思,还剩\(BZOJ\)上的\(14\)道题. [BZOJ3746][POI20 ...
- 【题解】PIE [POI2015] [P3585]
[题解]\(PIE\) \([POI2015]\) \([P3585]\) 逼自己每天一道模拟题 传送门:\(PIE\) \([POI2015]\) \([P3585]\) [题目描述] 一张 \(n ...
- Bzoj 3747: [POI2015]Kinoman 线段树
3747: [POI2015]Kinoman Time Limit: 60 Sec Memory Limit: 128 MBSubmit: 553 Solved: 222[Submit][Stat ...
- [Poi2015]
[POI2015]Łasuchy 一看以为是sb题 简单来说就是每个人获得热量要尽量多 不能找别人 首先这道题好像我自己找不到NIE的情况 很容易想到一个优化 如果一个数/2>另一个数 那么一定 ...
随机推荐
- Css var 简述
Css var 语法 var(custom-property-name, value) - custom-property-name 必须 变量必须以 --开头 后面可以是英文.数字连接符,区分大小写 ...
- 【解决方案】Java 互联网项目中消息通知系统的设计与实现(上)
目录 前言 一.需求分析 1.1发送通知 1.2撤回通知 1.3通知消息数 1.4通知消息列表 二.数据模型设计 2.1概念模型 2.2逻辑模型 三.关键流程设计 本篇小结 前言 消息通知系统(not ...
- GIS数据获取:土地利用与土壤属性、DEM、水体水系数据
本文对目前主要的土壤属性.地表覆盖.数字高程模型与水体水系矢量数据获取网站加以整理与介绍. 本文为"GIS数据获取整理"专栏中第三篇独立博客,因此本文全部标题均由" ...
- 02-HTML知识点
01 元素的介绍 02 元素的属性 03 元素的嵌套关系 04 HTML结构分析 4.1 文档声明[这个不叫元素] 4.2 html元素 4.3 head元素 主要用来写文档的配置信息 05 HTML ...
- LangChain和Hub的前世今生
作为LLM(大模型)开发框架的宠儿,LangChain在短短几年内迅速崛起,成为开发者们不可或缺的工具.本文将带你探讨LangChain和LangChainHub的发展历程. 1. LLM开发框架的宠 ...
- 【FAQ】HarmonyOS SDK 闭源开放能力 —Map Kit(2)
1.问题描述: 能否设置点击地图,地图标记上的文字不消失? 解决方案: 你好,这个功能设计本身就是点击屏幕marker的信息窗消失:如果用户只是想信息窗中的文字一直展示,可以不用信息窗实现 ,建议可以 ...
- 超大容量 | 瑞芯微RK3588J工业核心板新增16GB DDR + 128GB eMMC配置!
作为瑞芯微的金牌合作伙伴,创龙科技在2023年9月即推出搭载瑞芯微旗舰级处理器RK3588J的全国产工业核心板--SOM-TL3588. SOM-TL3588工业核心板是基于瑞芯微RK3588J/RK ...
- ARM+DSP!全志T113-i+玄铁HiFi4开发板硬件说明书(1)
前 言 本文档主要介绍开发板硬件接口资源以及设计注意事项等内容,测试板卡为全志T113-i+玄铁HiFi4开发板.由于篇幅问题,本篇文章共分为上下两集,点击账户可查看更多内容详情,开发问题欢迎留言,感 ...
- Unity 中使用Geomotry Shader(几何着色器)扩展点创建其他图形(并实现处理锯齿)
问题背景: 我们开发中需要有"点"对象,可以是像素的(不具备透视效果),始终等大,还有就是3D场景下的矢量点(随相机距离透视变化的). 问题解决思路: 方案1:使用GS扩充顶点,并 ...
- P9576 题解
赛时没仔细想,赛后才发现并不难. 将 \(l,r\) 与 \(l',r'\) 是否相交分开讨论. 假若不相交,那么 \(l',r' < l\) 或者 \(l',r' > r\) 并且 \( ...