CF474E Pillars
树状数组优化 \(\rm dp\)。
题目大意
- 给定序列 \(h\),长度为 \(n\)。
- 找出一个 \(h\) 序列的子序列 \(b\)(设其长度为 \(m\)),使得
- 对于任意的 \(1\le i\lt m\),有 \(|b_{i+1}-b_i|\ge d\)。
- \(m\) 最大。
- 其中 \(d\) 是给定的。
- 您的程序要输出 \(m\) 和序列 \(b\) 在序列 \(h\) 中每个数的下标(下标从 \(1\) 开始)。
- \(1\le n\le 10^5\),\(0\le d\le 10^9\),\(1\le h_i\le 10^{15}\)。若 \(b\) 不唯一,输出任意一种。
题目分析
因为 \(a\) 范围很大,所以先排序去重到 \(b\) 数组。
令 \(dp_i\) 表示取到了第 \(i\) 个数且第 \(i\) 个数必选的最大长度。
然后有一个很裸的 \(\mathcal{O(n^2)}\) 思路,转移方程为 \(dp_i=\max\{dp_j\}+1(1\le j\lt i,|h_i-h_j|\ge d)\),枚举即可。
但这是 \(\rm codeforces\),不会给部分分。我们考虑优化,通过观察数据范围可以猜测应该是 \(\mathcal{O(n\log n)}\) 或 \(\mathcal{O(n\sqrt{n})}\) (极限 \(3e7\)),又看到 \(\max\{dp_j\}\) 和 \(1\le j\lt i\),可以自然地想到数据结构优化。
但是还有很麻烦的限制,就是 \(|h_i-h_j|\ge d\),即 \(h_i-h_j\ge d\) 或 \(h_j-h_i\ge d\),再换一下就是 \(h_j\le h_i-d\) 或 \(h_j\ge h_i+d\)。
然后在 \(b\) 中二分找到大于等于 \(h_i+d\) 的第一个位置 \(l\),二分找到小于等于 \(h_i-d\) 的第一个位置 \(r\)。随后需要知道 \(h_j\) 在区间 \([1,l]\) 或 \([r,\operatorname{upper}\_\operatorname{limit}]\) 内的 \(\max\{dp_j\}\)。
线段树和树状数组都可以维护,但树状数组代码容易些所以我选树状数组。其实是因为不会写
用两个树状数组维护这个最大值即可,注意因为一维树状数组(二维树状数组 √
都不用)只能维护前缀最大值,所以 \([r,\operatorname{upper}\_\operatorname{limit}]\) 需要转换为 \([1,\operatorname{upper}\_\operatorname{limit}-r+1]\)。
输出方案直接记录一下转移过来的下标,然后用栈输出即可。
代码
//2022/3/12
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
#include <climits>//need "INT_MAX","INT_MIN"
#include <cstring>//need "memset"
#include <numeric>
#include <algorithm>
#include <stack>
#define int long long
#define enter putchar(10)
#define debug(c,que) cerr << #c << " = " << c << que
#define cek(c) puts(c)
#define blow(arr,st,ed,w) for(register int i = (st);i <= (ed); ++ i) cout << arr[i] << w;
#define speed_up() cin.tie(0),cout.tie(0)
#define mst(a,k) memset(a,k,sizeof(a))
#define Abs(x) ((x) > 0 ? (x) : -(x))
const int mod = 1e9 + 7;
inline int MOD(int x) {
if(x < 0) x += mod;
return x % mod;
}
namespace Newstd {
char buf[1 << 21],*p1 = buf,*p2 = buf;
inline int getc() {
return p1 == p2 && (p2 = (p1 = buf) + fread(buf,1,1 << 21,stdin),p1 == p2) ? EOF : *p1 ++;
}
inline int read() {
int ret = 0,f = 0;char ch = getc();
while (!isdigit(ch)) {
if(ch == '-') f = 1;
ch = getc();
}
while (isdigit(ch)) {
ret = (ret << 3) + (ret << 1) + ch - 48;
ch = getc();
}
return f ? -ret : ret;
}
inline void write(int x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace Newstd;
using namespace std;
const int ma = 1e5 + 5;
int a[ma],b[ma],dp[ma],pre[ma];
//dp[i]:取到了第 i 个数且第 i 个数必选的最大长度
stack<int>ans;
int n,m,num;
struct BIT {
int tr[ma];
#define lowbit(x) (x & -x)
inline int max(int x,int y) {
return dp[x] >= dp[y] ? x : y;
}
inline void update(int x,int k) {
for (;x <= num;x += lowbit(x)) {
tr[x] = max(tr[x],k);
}
}
inline int query(int x) {
int res = 0;
for (;x;x -= lowbit(x)) {
res = max(res,tr[x]);
}
return res;
}
#undef lowbit
} bit1,bit2;
#undef int
int main(void) {
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
#define int long long
n = read(),m = read();
for (register int i = 1;i <= n; ++ i) a[i] = b[i] = read();
sort(b + 1,b + n + 1);
num = unique(b + 1,b + n + 1) - b - 1;
for (register int i = 1;i <= n; ++ i) {
int l = upper_bound(b + 1,b + num + 1,a[i] - m) - b - 1,r = lower_bound(b + 1,b + num + 1,a[i] + m) - b;
int res = max(bit1.query(l),bit2.query(num - r + 1));
dp[i] = dp[res] + 1,pre[i] = res;
int pos = lower_bound(b + 1,b + num + 1,a[i]) - b;
bit1.update(pos,i),bit2.update(num - pos + 1,i);
}
int now = 0;
for (register int i = 1;i <= n; ++ i) {
if (dp[now] < dp[i]) {
now = i;
}
}
printf("%lld\n",dp[now]);
while (now) {
ans.push(now);
now = pre[now];
}
while (!ans.empty()) {
printf("%lld ",ans.top());
ans.pop();
}
return 0;
}