【题解】CatOJ C0458C 滑动窗口定期重构
标题 trick 的名字我也不知道是什么,就这样吧。
首先有显然的 dp 式子:\(f(i)=\min \{f(j) \times \max\{a_{j+1},\dots,a_i\}\}\)。考虑怎么去优化它。
有显然的 \(\mathcal O(n\log n)\):考虑线段树优化 dp。用增的单调栈维护 \(a\),若每次弹出顶部一个下标 \(p\),则 \([p+1,i]\) 的 \(\max\) 都被推平成 \(a_i\),栈维护一下 \(\max\) 连续段,于是问题变成区间加。分析一下连续段个数是 \(\mathcal O(n)\) 的。
Code(这份代码 5e5 的包 WA 了,但大致思路可以参考一下)
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb emplace_back
#define rep(_, __, ___) for (int _ = (__); _ <= (___); _++)
#define per(_, __, ___) for (int _ = (__); _ >= (___); _--)
// #define int long long
using ll = long long; using pii = pair<int, int>;
inline int read() {
char ch = getchar(); int s = 0, f = 1;
while (!isdigit(ch)) f = (ch == '-' ? -1 : 1), ch = getchar();
while (isdigit(ch)) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * f;
}
constexpr int N = 5e5 + 5, mod = 1e9 + 7;
int n, k, a[N], s1[N], t1, t2; ll pw[N]; tuple<int, int, int> s2[N];
#define ls id << 1
#define rs id << 1 | 1
ll mn[N << 2], tag[N << 2];
inline void maketag(int id, int x) {
tag[id] += x, mn[id] += x;
}
void upd(int a, int b, int x, int id = 1, int l = 1, int r = n) {
if (a <= l && b >= r) return maketag(id, x);
int mid = (l + r) >> 1; maketag(ls, tag[id]), maketag(rs, tag[id]), tag[id] = 0;
if (a <= mid) upd(a, b, x, ls, l, mid);
if (b > mid) upd(a, b, x, rs, mid + 1, r);
mn[id] = min(mn[ls], mn[rs]);
}
ll qry(int a, int b, int id = 1, int l = 1, int r = n) {
if (a <= l && b >= r) return mn[id];
int mid = (l + r) >> 1; ll res = 1e18; maketag(ls, tag[id]), maketag(rs, tag[id]), tag[id] = 0;
if (a <= mid) res = min(res, qry(a, b, ls, l, mid));
if (b > mid) res = min(res, qry(a, b, rs, mid + 1, r));
return res;
}
signed main() {
// freopen("knight.in", "r", stdin);
// freopen("knight.out", "w", stdout);
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
n = read(), k = read();
pw[0] = 1; rep (i, 1, n) a[i] = read(), pw[i] = pw[i - 1] * 23 % mod;
ll ans = 0, dp;
rep (i, 1, n) {
while (t1 && a[s1[t1]] < a[i]) t1--;
int x = s1[t1] + 1; s2[t2 + 1] = {get<1>(s2[t2]) + 1, i, 0}, t2++;
while (t2 && get<0>(s2[t2]) >= x) {
upd(get<0>(s2[t2]), get<1>(s2[t2]), a[i] - get<2>(s2[t2]));
t2--;
}
s2[++t2] = {x, i, a[i]}, s1[++t1] = i; dp = qry(max(i - k + 1, 1), i);
if (i < n) upd(i + 1, i + 1, dp);
ans += pw[n - i] * (dp % mod) % mod;
}
cout << ans % mod;
return 0;
}
/*
3 4 4 8 10 12
*/
进阶一下,上面的线段树优化 dp 可以每次修改的都是一个后缀,可以用可删堆维护 \(\min\),定期弹出过期元素即可。
于是我们得到了乱搞做法:把上面的可删堆换成压位 Trie,时间复杂度是 \(\mathcal O(n\log V/\log w)\) 的。
观察复杂度瓶颈在于可删堆的求 \(\min\)。注意到可删堆内的元素其实是在一段窗口里,所以有人类智慧的思考:考虑基于一个点 \(p\),每次重构时预处理出它到两边的前缀 / 后缀 \(\min\),每次查询的时候是可以将两段拼起来。当窗口内不包含 \(p\) 时,就取 \(p\) 为当前窗口中点重构即可。
分析一下复杂度:如果右端点不扩,势能是不断减小的,即 \(\mathcal O(len+\dfrac{len}{2}+\dfrac{len}{4}+...)=\mathcal O(len)\),由于右端点往右扩是均摊 \(\mathcal O(n)\) 的,所以复杂度即为 \(\mathcal O(\sum len)=\mathcal O(n)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb emplace_back
#define rep(_, __, ___) for (int _ = (__); _ <= (___); _++)
#define per(_, __, ___) for (int _ = (__); _ >= (___); _--)
#define int long long
using ll = long long; using pii = pair<int, int>;
inline int read() {
char ch = getchar(); int s = 0, f = 1;
while (!isdigit(ch)) f = (ch == '-' ? -1 : 1), ch = getchar();
while (isdigit(ch)) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * f;
}
constexpr int N = 1e7 + 5, mod = 1e9 + 7;
int n, k, st = 1, ed, mid = 1, dp[N], mn[N], pw[N];
struct node {signed l, r; int tag;} s[N];
inline int get(int i) {
return s[i].tag + dp[s[i].l - 1];
}
inline void rebuild() {
if (st > ed) return (void)(mid = st); mid = (st + ed) >> 1;
rep (i, st, ed) mn[i] = get(i);
per (i, mid - 1, st) mn[i] = min(mn[i], mn[i + 1]);
rep (i, mid + 2, ed) mn[i] = min(mn[i], mn[i - 1]);
}
signed main() {
// freopen("knight.in", "r", stdin);
// freopen("knight.out", "w", stdout);
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
n = read(), k = read(); int ans = 0;
pw[0] = 1; rep (i, 1, n) pw[i] = pw[i - 1] * 23 % mod;
rep (i, 1, n) {
int x = read();
// cerr << s[st].l << '*' << s[st].r << endl;
while (st <= ed && s[st].l < i - k + 1) {
if (s[st].r < i - k + 1) {
if (++st > mid) rebuild();
}
else {
s[st].l = i - k + 1; mn[st] = get(st);
if (st < mid) mn[st] = min(mn[st], mn[st + 1]);
}
}
// cerr << s[st].l << '$' << s[st].r << endl;
while (st <= ed && s[ed].tag <= x) if (--ed <= mid) rebuild();
// cerr << s[st].l << '&' << s[st].r << endl;
if (st > ed) s[++ed] = (node){max(1ll, i - k + 1), i, x};
else s[ed + 1] = (node){s[ed].r + 1, i, x}, ed++;
mn[ed] = get(ed); if (ed - 1 > mid) mn[ed] = min(mn[ed], mn[ed - 1]);
dp[i] = min(mn[ed], mn[st]); ans += pw[n - i] * (dp[i] % mod) % mod;
// cerr << i << ' ' << dp[i] << '\n';
}
cout << ans % mod;
return 0;
}
/*
3 4 4 8 10 12
*/
【题解】CatOJ C0458C 滑动窗口定期重构的更多相关文章
- LeetCode 周赛 342(2023/04/23)容斥原理、计数排序、滑动窗口、子数组 GCB
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 前天刚举办 2023 年力扣杯个人 SOLO 赛,昨天周赛就出了一场 Easy - Ea ...
- 单调队列优化&&P1886 滑动窗口题解
单调队列: 顾名思义,就是队列中元素是单调的(单增或者单减). 在某些问题中能够优化复杂度. 在dp问题中,有一个专题动态规划的单调队列优化,以后会更新(现在还是太菜了不会). 在你看到类似于滑动定长 ...
- luogu题解 UVA11536 【Smallest Sub-Array】最短set区间&滑动窗口
题目链接: https://www.luogu.org/problemnew/show/UVA11536 题目大意: 给定一个\(N,M,K\),构造这样的数列: \(x[1]=1,x[2]=2,x[ ...
- LeetCoded第239题题解--滑动窗口最大值
滑动窗口最大值 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧.你只可以看到在滑动窗口内的 k 个数字.滑动窗口每次只向右移动一位. 返回滑动窗口中的最大值. 进 ...
- TCP协议的滑动窗口协议以及流量控制
参考资料 http://blog.chinaunix.net/uid-26275986-id-4109679.html http://network.51cto.com/art/201501/4640 ...
- LeetCode编程训练 - 滑动窗口(Sliding Window)
滑动窗口基础 滑动窗口常用来解决求字符串子串问题,借助map和计数器,其能在O(n)时间复杂度求子串问题.滑动窗口和双指针(Two pointers)有些类似,可以理解为往同一个方向走的双指针.常用滑 ...
- POJ 2823 滑动窗口 单调队列
https://vjudge.net/problem/POJ-2823 中文:https://loj.ac/problem/10175 题目 给一个长度为 $N$ 的数组,一个长为 $K$ 的滑动窗体 ...
- Storm 实现滑动窗口计数和TopN排序
计算top N words的topology, 用于比如trending topics or trending images on Twitter. 实现了滑动窗口计数和TopN排序, 比较有意思, ...
- 算法与数据结构基础 - 滑动窗口(Sliding Window)
滑动窗口基础 滑动窗口常用来解决求字符串子串问题,借助map和计数器,其能在O(n)时间复杂度求子串问题.滑动窗口和双指针(Two pointers)有些类似,可以理解为往同一个方向走的双指针.常用滑 ...
- PAT(B) 1030 完美数列 - C语言 - 滑动窗口 & 双指针
题目链接:1030 完美数列 (25 point(s)) 给定一个正整数数列,和正整数 \(p\),设这个数列中的最大值是 \(M\),最小值是 \(m\),如果 \(M≤mp\),则称这个数列是完美 ...
随机推荐
- 浏览器扩展开发-Firefox临时载入附加组件(图)
科普:[FE] Quasar BEX 热加载区别: Chrome vs Firefox 1. 2. 3. Thats all. Refer:Firefox扩展开发 Link:https://www. ...
- [FAQ] 腾讯企业邮箱成员的名字如何多次更改 ?
可以通过给成员的邮箱增加别名,先点击成员信息最右侧的 "编辑",在编辑页面顶部的 "更多操作" 这个按钮中,比较隐蔽. Refer:腾讯邮箱成员名字更改 Lin ...
- 开发日志:windows 服务器禁用TLS1.0和TLS1.1协议使网站更安全
SSL/TLS 的版本 协议 发布时间 状态 SSL 1.0 未公布 未公布 SSL 2.0 1995 年 已于 2011 年弃用 SSL 3.0 1996 年 已于 2015 年弃用 TLS 1.0 ...
- k8s核心资源Job&&CronJob调度pod
一.Job与CronJob概念与与原理解读 Job 控制器用于管理 Pod 对象运行一次性任务,比方说我们对数据库备份,可以直接在 k8s 上启动一个 mysqldump 备份程序,也可以启动一个 p ...
- LLM基础能力实现-书生浦语大模型实战营学习笔记2&大语言模型4
大语言模型-4.LLM基础能力实现 书生浦语大模型实战营学习笔记-2.LLM基础能力实现 本文包括第二期实战营的第2课内容.本来是想在笔记中给官方教程做做补充的,没想到官方教程的质量还是相当高的,跟着 ...
- C 编程异常 — /usr/bin/ld: 找不到 -lm
问题:在编程程序的时候报错. /usr/bin/ld: 找不到 -lm /usr/bin/ld: 找不到 -lc 原因:缺少库文件. 解决: yum install -y libstdc++-stat ...
- angular自定义属性指令
在angular中有三种类型的指令: 组件–拥有模板的指令: 结构性指令–通过添加和移除DOM元素改变DOM布局的指令; 属性型指令–改变元素.组件或其他指令的外观和行为的指令: 组件是这三种指令中最 ...
- 智能调度_AIRIOT智能车队管理解决方案
客运.货运.汽车租赁.出租运营等行业对车辆管理.车队管理以及司乘人员的管理方式,逐渐向数字化和智能化转型.传统的依赖人工调度.记录和跟踪的管理模式已经难以满足业务发展需要,存在如下痛点: 实时监控与定 ...
- openstack 错误(报错)集合
1. 执行nova命令报错: ERROR (CommandError): You must provide a username or user ID via --os-username, --os- ...
- kubernetes 之二进制方式部署
我的资料链接:https://pan.baidu.com/s/18g0sar1N-FMhzY-FCMqOog 两种集群架构图 多master需要在集群上面加个lb,所有的node都需要连接lb,lb帮 ...