P4919 Marisa采蘑菇
知识点:扫描线,树状数组
原题面:Luogu
更好的阅读体验:My blog。
最后扯一句,魔理沙世界第一可爱.jpg [1]
简述
给定一长度为 的数列 ,参数 。给定 次询问。
每次询问给定区间 ,求区间内有多少个数,满足在区间内的出现次数与区间外的出现次数之差小于等于给定的常数 。
,,。
3S,128MB。
分析
对于一个询问区间 ,设 为权值 在整个数列中出现的次数。考虑某种权值 对该区间有贡献的条件,显然为 。上式解得:
考虑离线询问并排序,通过对询问进行扫描线固定询问的右端点。考虑在询问右端点单调右移的同时,对于每种权值,都维护它有所贡献的询问的左端点范围 。当询问的左端点 时,区间 内这种权值出现次数满足上式。右端点右移到下一个同权值位置时更新区间。如下图所示:
当前缀 中某权值出现次数满足上式时,有贡献的区间是数列的一段完整前缀,且需要注意 的初始取值,详见代码。
当扫描线枚举的右端点为 ,维护上述信息后,询问 的答案即为覆盖了左端点 的区间的个数。
发现上述算法需要支持区间修改,单点求和,树状数组维护即可,总复杂度 级别。
代码
复制复制//知识点:扫描线,树状数组 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #define LL long long const int kN = 1e6 + 10; //============================================================= struct Q { int l, r, id; } q[kN]; int n, m, k, col[kN], ans[kN]; int last[kN], ne[kN], first[kN], sum[kN]; //ne[i]:col[i] 之后第一个等于 col[i] 的位置 //first[i]:颜色 i 在数列中第一次出现的位置 //sum[i]:颜色 i 在数列中出现次数之和 int cnt[kN], L[kN], R[kN]; //在扫描线过程中维护,cnt[i]:到扫描位置为止 i 的出现次数 //L[i]~R[i]:当前扫描位置作为询问右端点时,颜色 i 有贡献的询问左端点范围 //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Chkmax(int &fir, int sec) { if (sec > fir) fir = sec; } void Chkmin(int &fir, int sec) { if (sec < fir) fir = sec; } bool CMP(Q fir_, Q sec_) { if (fir_.r != sec_.r) return fir_.r < sec_.r; return fir_.l < sec_.l; } namespace Bit { #define low(x) (x&-x) const int kLim = 1e6; int t[kLim + 10]; void Add(int pos_, int val_) { ++ pos_; for (int i = pos_; i <= kLim; i += low(i)) t[i] += val_; } int Sum(int pos_) { ++ pos_; int ret = 0; for (int i = pos_; i; i -= low(i)) ret += t[i]; return ret; } void Modify(int l_, int r_, int val_) { Add(l_, val_); Add(r_ + 1, -val_); } } void Init() { n = read(), m = read(), k = read(); for (int i = 1; i <= n; ++ i) { col[i] = read(); if (++ sum[col[i]] == 1) first[col[i]] = i; last[col[i]] = n; } for (int i = n; i >= 1; -- i) { ne[i] = last[col[i]]; last[col[i]] = i; } for (int i = 1; i <= m; ++ i) q[i] = (Q) {read(), read(), i}; std::sort(q + 1, q + m + 1, CMP); } void Modify(int pos_) { int c = col[pos_], s = sum[c]; ++ cnt[c]; if (R[c]) Bit::Modify(L[c], R[c], -1); //减去之前颜色 i 对询问左端点的贡献 if (2 * cnt[c] >= s - k) R[c] = R[c] ? ne[R[c]] : first[c]; //右端点左移,增加区间内该权值出现次数。 if (2 * cnt[c] > s + k) L[c] = L[c] ? ne[L[c] - 1] + 1 : first[c] + 1; //左端点右移,减小区间内该权值出现次数。注意开区间。 if (R[c]) Bit::Modify(L[c], R[c], 1); } //============================================================= int main() { Init(); for (int i = 1, nowr = 0; i <= m; ++ i) { while (nowr < q[i].r && nowr < n) Modify(++ nowr); //扫描位置更新 ans[q[i].id] = Bit::Sum(q[i].l); } for (int i = 1; i <= m; ++ i) printf("%d\n", ans[i]); return 0; }
作者@Luckyblock,转载请声明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】