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;
}
posted @ 2022-03-13 14:06  Coros_Trusds  阅读(48)  评论(0编辑  收藏  举报