Codeforces 487B Strip (ST表+线段树维护DP 或 单调队列优化DP)
题目链接 Strip
题意 把一个数列分成连续的$k$段,要求满足每一段内的元素最大值和最小值的差值不超过$s$,
同时每一段内的元素个数要大于等于$l$,
求$k$的最小值。
考虑$DP$
设$dp[i]$为前$i$个数字能划分成区间个数的最小值。
则$dp[i] = min(dp[j] + 1)$
于是下一步就是求符合条件的j的范围。
构建$ST$表,支持区间查询最大值和最小值。
对于每一个位置$x$,我们知道$max(a[i]...a[x]) - min(a[i]...a[x])$肯定是随着i的减小非递减的。$(i <= x)$
于是我们就可以通过二分求出符合条件的$max(a[i]...a[x]) - min(a[i]...a[x])$的最小值,记为$c[x]$
当不存在可以转移到$dp[x]$的$dp[i]$时,$c[x]$为$-1$。
$n <= 100000$,考虑用线段树优化。
单点更新,区间查询最小值。
时间复杂度$O(nlogn)$
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i)
#define dec(i, a, b) for (int i(a); i >= (b); --i)
#define lson i << 1, L, mid
#define rson i << 1 | 1, mid + 1, R typedef long long LL; const int N = 1e5 + 10;
const int A = 18; int f[N][A], g[N][A];
int a[N], lg[N], c[N], dp[N];
int n, s, l;
int L, R; int t[N << 2]; void ST(){
rep(i, 1, n) f[i][0] = a[i];
rep(j, 1, 17) rep(i, 1, n)
if ((i + (1 << j) - 1) <= n) f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]); rep(i, 1, n) g[i][0] = a[i];
rep(j, 1, 17) rep(i, 1, n)
if ((i + (1 << j) - 1) <= n) g[i][j] = max(g[i][j - 1], g[i + (1 << (j - 1))][j - 1]); } inline int solvemin(int l, int r){
int k = lg[r - l + 1];
return min(f[l][k], f[r - (1 << k) + 1][k]);
} inline int solvemax(int l, int r){
int k = lg[r - l + 1];
return max(g[l][k], g[r - (1 << k) + 1][k]);
} inline void pushup(int i){ t[i] = min(t[i << 1], t[i << 1 | 1]);} void build(int i, int L, int R){
if (L == R){ t[i] = 1 << 30; return;}
int mid = (L + R) >> 1;
build(lson);
build(rson);
pushup(i);
} void update(int i, int L, int R, int x, int val){
if (L == R && L == x){ t[i] = min(t[i], val); return;}
int mid = (L + R) >> 1;
if (x <= mid) update(lson, x, val);
else update(rson, x, val);
pushup(i);
} int query(int i, int L, int R, int l, int r){
if (L == l && R == r) return t[i];
int mid = (L + R) >> 1;
if (r <= mid) return query(lson, l, r);
else if (l > mid) return query(rson, l, r);
else return min(query(lson, l, mid), query(rson, mid + 1, r));
} int main(){ rep(i, 1, 1e5 + 1) lg[i] = (int)log2((double)(i)); scanf("%d%d%d", &n, &s, &l);
rep(i, 1, n) scanf("%d", a + i); ST(); if (l <= 1) c[1] = 1; else c[1] = -1;
rep(i, 2, n){
L = 1, R = i - l + 1;
if (R < 1){ c[i] = -1; continue; }
if (solvemax(R, i) - solvemin(R, i) > s){ c[i] = -1; continue;}
while (L + 1 < R){
int mid = (L + R) >> 1;
if (solvemax(mid, i) - solvemin(mid, i) <= s) R = mid;
else L = mid + 1;
} if (solvemax(L, i) - solvemin(L, i) <= s) c[i] = L;
else c[i] = R;
} dp[0] = 0;
rep(i, 1, n) dp[i] = 1 << 30; build(1, 1, n + 1);
update(1, 1, n + 1, 1, 0); rep(i, 1, n){
if (c[i] == -1) continue;
int now = query(1, 1, n + 1, c[i], i - l + 1);
dp[i] = min(dp[i], now + 1);
update(1, 1, n + 1, i + 1, dp[i]);
} if (dp[n] < (1 << 30)) printf("%d\n", dp[n]);
else puts("-1");
return 0;
}
上面这个方法很容易想,但是写起来略有难度。
其实有一种更好的方法。
用单调队列来优化。
对于当前的这个$x$,向后扫描,扫到y的时候如果发现区间$[x, y]$不符合题意了。
那么其实这个时候$x$这个位置已经没用了。
因为如果$[x,y]$不符合题意,那么$[x, y + 1]$肯定是不符合题意的。
于是我们可以用单调队列优化,用集合来维护最大值和最小值。
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i)
#define dec(i, a, b) for (int i(a); i >= (b); --i) const int N = 1e5 + 10; int n, s, l, now;
int a[N], f[N];
multiset <int> st, v; int main(){ scanf("%d%d%d", &n, &s, &l);
rep(i, 1, n) scanf("%d", a + i); now = 1;
f[0] = 0;
rep(i, 1, n){
f[i] = 1 << 30;
st.insert(a[i]);
if (i - now + 1 >= l) v.insert(f[i - l]);
while (*st.rbegin() - *st.begin() > s){
st.erase(st.find(a[now]));
auto it = v.find(f[now - 1]);
if (it != v.end()) v.erase(it);
// if (i - now + 1 >= l) v.erase(v.find(f[now - 1]));
++now;
}
if (!v.empty()) f[i] = *v.begin() + 1;
} printf("%d\n", f[n] >= (1 << 30) ? -1 : f[n]);
return 0;
}
Codeforces 487B Strip (ST表+线段树维护DP 或 单调队列优化DP)的更多相关文章
- 51nod 1766 树上的最远点对 | LCA ST表 线段树 树的直径
51nod 1766 树上的最远点对 | LCA ST表 线段树 树的直径 题面 n个点被n-1条边连接成了一颗树,给出a~b和c~d两个区间,表示点的标号请你求出两个区间内各选一点之间的最大距离,即 ...
- codeforces Good bye 2016 E 线段树维护dp区间合并
codeforces Good bye 2016 E 线段树维护dp区间合并 题目大意:给你一个字符串,范围为‘0’~'9',定义一个ugly的串,即串中的子串不能有2016,但是一定要有2017,问 ...
- P4381 [IOI2008]Island(基环树+单调队列优化dp)
P4381 [IOI2008]Island 题意:求图中所有基环树的直径和 我们对每棵基环树分别计算答案. 首先我们先bfs找环(dfs易爆栈) 蓝后我们处理直径 直径不在环上,就在环上某点的子树上 ...
- bzoj 1699: [Usaco2007 Jan]Balanced Lineup排队【st表||线段树】
要求区间取min和max,可以用st表或线段树维护 st表 #include<iostream> #include<cstdio> using namespace std; c ...
- Codeforces GYM 100114 D. Selection 线段树维护DP
D. Selection Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100114 Descriptio ...
- [Codeforces]817F. MEX Queries 离散化+线段树维护
[Codeforces]817F. MEX Queries You are given a set of integer numbers, initially it is empty. You sho ...
- BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)
前言 刚开始看着两道题感觉头皮发麻,后来看看题解,发现挺好理解,只是代码有点长. BZOJ 3672[NOI2014]购票 中文题面,题意略: BZOJ 3672[NOI2014]购票 设f(i)f( ...
- Codeforces 1304F1/F2 Animal Observation(单调队列优化 dp)
easy 题目链接 & hard 题目链接 给出一张 \(n \times m\) 的矩阵,每个格子上面有一个数,你要在每行选出一个点 \((i,t)\),并覆盖左上角为 \((i,t)\), ...
- BZOJ1791 [Ioi2008]Island 岛屿[基环树+单调队列优化DP]
基环树直径裸题. 首先基环树直径只可能有两种形式:每棵基环树中的环上挂着的树的直径,或者是挂在环上的两个树的最大深度根之间的距离之和. 所以,先对每个连通块跑一遍,把环上的点找出来,然后对环上每个点跑 ...
随机推荐
- Feign-请求不同注册中心的服务
场景 需要通过Feign Client请求,其他注册中心或者其他Restful服务. 临时方案 Feign 请求转为RestTemplate http请求. 优点:能适应,feign环境和非feign ...
- react native 在window 7上配置开发环境-Andorid
参照官方配置:https://facebook.github.io/react-native/docs/getting-started.html 因为在配置的过程中遇到很多问题,在此记录一下. 1.j ...
- windows下使用gcc完成头文件和目标文件编译
环境要求 安装了gcc win+r然后输入cmd , dos界面输入 gcc -v 查看有没有安装gcc 进入正题 新建 text.c文件键入如下代码: #include <stdio.h> ...
- CentOS7支持中文显示
1.查看系统是否安装有中文语言包 locale -a | grep "zh_CN" 命令含义:列出所有可用的公共语言环境的名称,包含有"zh_CN" 若 ...
- tkinter学习-文本框
阅读目录 Entry 输入框 Text 文本框 Entry: 说明:输入控件,用于显示简单的文本内容 属性:在输入框中用代码添加和删除内容,同样也是用insert()和delete()方法 from ...
- linux文件权限更改命令chmod及数字权限
chmod -change file mode bits :更改文件权限 chmod是用来改变文件或者目录权限的命令,但只有文件的属主和超级用户(root)才有这种权限. 更改文件权限的2种方式: 一 ...
- 【css】【动画】【转发】旋转动画
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <s ...
- (原)iOS 用recursiveDescription打印View
今天要做一个搜索功能,用到UISearchBar 无奈背景太丑,就自定义了一个,首先用View私有方法打印一下searchBar的层次, 具体修改代码如下 for (UIView *view in _ ...
- python基础学习笔记——生成器与推导式
生成器 首先我们来看看什么是个生成器,生成器本质就是迭代器 在python中有三种方式来获取生成器 1.通过生成器函数 2.通过各种推到式来实现生成器 3.通过数据的转换也可以获取生成器 首先,我们先 ...
- joyoi1864 守卫者的挑战
#include <algorithm> #include <iostream> #include <cstdio> using namespace std; in ...