前言

题目链接:洛谷

题意简述

长度为 \(n\) 的一串项链,每颗珠子是 \(k\) 种颜色之一。第 \(i\) 颗与第 \(i-1, i+1\) 颗珠子相邻,第 \(n\) 颗与第 \(1\) 颗也相邻。切两刀,把项链断成两条链。要求每种颜色的珠子只能出现在其中一条链中。求方案数量(保证至少存在一种),以及切成的两段长度之差绝对值的最小值。

题目分析

据我所知,这道题有如下做法:

  1. 线段树 + 增量法 + 最值 / 二分
  2. 哈希 + 双指针
  3. 分治 + 双指针

写一篇题解以总结并加深印象。

方法 \(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\):哈希 + 双指针

异或哈希

代码\(528\) 毫秒

和哈希

代码\(1.43\) 秒

方法 \(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 题解的更多相关文章

  1. Luogu3587[POI2015]POD - hash + 单调队列

    Solution 还是去看了题解. 感谢大佬的博客→  题解传送门 是一道思路比较新的题. 搞一个前缀和, 记录前 $i$ 个位置每种颜色的出现次数, 如果位置 $i$ 是 颜色 $a[i]$ 的最后 ...

  2. BZOJ3747:[POI2015]Kinoman——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=3747 https://www.luogu.org/problemnew/show/P3582 共有 ...

  3. P3587 [POI2015]POD

    题目描述 长度为n的一串项链,每颗珠子是k种颜色之一. 第i颗与第i-1,i+1颗珠子相邻,第n颗与第1颗也相邻.切两刀,把项链断成两条链.要求每种颜色的珠子只能出现在其中一条链中.求方案数量(保证至 ...

  4. POI2015 解题报告

    由于博主没有BZOJ权限号, 是在洛咕做的题~ 完成了13题(虽然有一半难题都是看题解的QAQ)剩下的题咕咕咕~~ Luogu3585 [POI2015]PIE Solution 模拟, 按顺序搜索, ...

  5. luogu P3592 [POI2015]MYJ

    题目链接 luogu P3592 [POI2015]MYJ 题解 区间dp 设f[l][r][k]表示区间l到r内最小值>=k的最大收益 枚举为k的位置p,那么包含p的区间答案全部是k 设h[i ...

  6. CF786B Legacy(线段树优化建边)

    模板题CF786B Legacy 先说算法 如果需要有n个点需要建图 给m个需要建边的信息,从单点(或区间内所有点)向一区间所有点连边 如果暴力建图复杂度\(mn^2\) 以单点连向区间为例,在n个点 ...

  7. POI2015题解

    POI2015题解 吐槽一下为什么POI2015开始就成了破烂波兰文题目名了啊... 咕了一道3748没写打表题没什么意思,还剩\(BZOJ\)上的\(14\)道题. [BZOJ3746][POI20 ...

  8. 【题解】PIE [POI2015] [P3585]

    [题解]\(PIE\) \([POI2015]\) \([P3585]\) 逼自己每天一道模拟题 传送门:\(PIE\) \([POI2015]\) \([P3585]\) [题目描述] 一张 \(n ...

  9. Bzoj 3747: [POI2015]Kinoman 线段树

    3747: [POI2015]Kinoman Time Limit: 60 Sec  Memory Limit: 128 MBSubmit: 553  Solved: 222[Submit][Stat ...

  10. [Poi2015]

    [POI2015]Łasuchy 一看以为是sb题 简单来说就是每个人获得热量要尽量多 不能找别人 首先这道题好像我自己找不到NIE的情况 很容易想到一个优化 如果一个数/2>另一个数 那么一定 ...

随机推荐

  1. #Powerbi 1分钟学会,SUMMARIZE函数,分组汇总并新建表

    今天我们来学习一个新的表函数,SUMMARIZE函数是DAX中的一个函数,它可以根据一列或多列对数据进行分组,并且可以使用指定的表达式为汇总后的表添加新列,形成一张新表. 一:基础语法 SUMMARI ...

  2. JavaScript:Function:函数(方法)对象

    <!DOCTYPE html><html>    <head>        <meta charset="utf-8">      ...

  3. STM32 CubeMX 学习:000-搭建开发环境

    背景 了解了 STM32 标准库以后,为了紧跟发展的潮流,我们以 CubeMx为基础 开始进行 Hal(Hardware Abstract Layer, 硬件抽象层)库的学习. CubeMx 是一个 ...

  4. 扫描版PDF目录制作指南

    目前网上找到的扫描版的电子书往往没有目录,这使得阅读变得非常困难.本文总结我的经验,介绍快速制作扫描版 PDF 目录的方法,以便更轻松地阅读扫描版电子书. 本文首先介绍手动制作目录的方法,之后介绍如何 ...

  5. Python性能测试框架:Locust实战教程

    01认识Locust Locust是一个比较容易上手的分布式用户负载测试工具.它旨在对网站(或其他系统)进行负载测试,并确定系统可以处理多少个并发用户,Locust 在英文中是 蝗虫 的意思:作者的想 ...

  6. Ez Forensics详解

    Ez Forensics详解 题目要求: 数据库版本 + 字符集格式 + 最长列名 示例:NSSCTF 步骤: 解压压缩包得到forensics.vmdk,.vmdk是虚拟机磁盘文件的元数据文件 可以 ...

  7. ELK Stack - Elasticsearch · 搜索引擎 · 部署应用 · 内部结构 · 倒排索引 · 服务接入

    系列目录 ELK Stack - Elasticsearch · 搜索引擎 · 全文检索 · 部署应用 · 内部结构 · 倒排索引 · 服务接入 ELK Stack - Kibana (待续) ELK ...

  8. Swift开发基础03-函数

    定义 形参默认是let,也只能是let func sum(v1: Int, v2: Int) -> Int { return v1 + v2 } sum(v1: 10, v2: 20) // 无 ...

  9. LeViT:Facebook提出推理优化的混合ViT主干网络 | ICCV 2021

    论文提出了用于快速图像分类推理的混合神经网络LeVIT,在不同的硬件平台上进行不同的效率衡量标准的测试.总体而言,LeViT在速度/准确性权衡方面明显优于现有的卷积神经网络和ViT,比如在80%的Im ...

  10. mysql:Windows修改MySQL数据库密码(修改或忘记密码)

    今天练习远程访问数据库时,为了方便访问,就想着把数据库密码改为统一的,以后我们也会经常遇到MySQL需要修改密码的情况,比如密码太简单.忘记密码等等.在这里我就借鉴其他人的方法总结几种修改MySQL密 ...