JZOJ 6957. 【2020.01.19冬令营模拟】板凳(前缀和+二分)

JZOJ 6957. 【2020.01.19冬令营模拟】板凳

题目大意

  • 长为 m m m的序列,初始有若 n n n位置为 1 1 1,其余为 0 0 0,每次找到最长且靠左的一段全 0 0 0序列,将该段中间位置修改为 1 1 1 q q q次询问求第 x x x次修改的位置。
  • n , q ≤ 1 0 5 n,qle10^5 n,q105 m ≤ 1 0 14 mle10^{14} m1014

题解

  • 首先要发现一个重要的性质,每个段中间位置修改后会被分成两段,尽管这两段长度可能相差 1 1 1,但这两段再继续分割下去每一层的长度也只有两种可能的取值,也就是不同的段长最多只有 2 n log ⁡ n 2nlog n 2nlogn
  • 这样可以预处理出所有的段长及从头至尾出现的次数,从长到短前缀和后,询问时经过二分就可以得到该次修改的段长 l l l
  • 另一个前缀和对每种长度分开处理,用它可以二分出该段在最初的哪个段上,以及是第几个长为 l l l的段。
  • 在这个段上,记录下左右端点向下递归,每次用预处理的方法统计左边一半有多少个长为 l l l的段,决定每次向左儿子还是右儿子递归,最后即可以确定位置。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define N 100010
ll a[N], b[N], f[N * 60];
struct node {
	ll id, x, s;
}c[N * 60], c0[N * 60];
int tot = 0;
int cmp(node x, node y) {
	if(x.x == y.x) return x.id < y.id;
	return x.x > y.x;
}
void ins(int id, ll x0, ll x, ll y0, ll y) {
	if(x0) {
		if(c0[tot].id == id && c0[tot].x == x0) c0[tot].s += x;
		else c0[++tot].id = id, c0[tot].x = x0, c0[tot].s = x;
	}
	if(y0) {
		if(c0[tot].id == id && c0[tot].x == y0) c0[tot].s += y;
		else c0[++tot].id = id, c0[tot].x = y0, c0[tot].s = y;
	}
	if(y0) {
		if(x0 % 2 == 0) ins(id, x0 / 2, x, x0 / 2 - 1, x + y * 2);
		else ins(id, x0 / 2, x * 2 + y, x0 / 2 - 1, y);
	}
	else {
		if(x0 % 2 == 0) ins(id, x0 / 2, x, x0 / 2 - 1, x);
		else if(x0 > 1) ins(id, x0 / 2, x * 2, 0, 0);
	}
}
ll ct(ll l, ll r, ll ls) {
	if(r - l + 1 < ls) return 0;
	int tot1 = tot;
	ins(-1, r - l + 1, 1, 0, 0);
	ll s = 0;
	for(int i = tot1 + 1; i <= tot; i++) if(c0[i].x == ls) s += c0[i].s;
	tot = tot1;
	return s;
}
ll solve(ll l, ll r, ll t, ll ls) {
	if(r - l + 1 < ls) return 0;
	if(r - l + 1 == ls) return (l + r) / 2;
	ll s = ct(l, (l + r) / 2 - 1, ls);
	if(s >= t) return solve(l, (l + r) / 2 - 1, t, ls);
	return solve((l + r) / 2 + 1, r, t - s, ls);
}
int main() {
	ll m, x;
	int n, Q, i;
	scanf("%lld%d%d", &m, &n, &Q);
	for(i = 1; i <= n; i++) scanf("%lld", &a[i]), b[i] = a[i];
	sort(b + 1, b + n + 1);
	b[0] = 0, b[n + 1] = m + 1;
	for(i = 0; i <= n; i++) {
		if(b[i + 1] > b[i] + 1) ins(i, b[i + 1] - b[i] - 1, 1, 0, 0);
	}
	sort(c0 + 1, c0 + tot + 1, cmp);
	int tp = 0;
	for(i = 1; i <= tot; i++) {
		if(tp && c0[i].x == c[tp].x) c[tp].s += c0[i].s; else c[++tp] = c0[i];
	}
	for(i = 1; i <= tp; i++) f[i] = f[i - 1] + c[i].s;
	for(i = 2; i <= tot; i++) if(c0[i].x == c0[i - 1].x) c0[i].s += c0[i - 1].s;
	while(Q--) {
		scanf("%lld", &x);
		if(x <= n) printf("%lld
", a[x]); 
		else {
			x -= n;
			int l = 1, r = tp, t;
			while(l <= r) {
				int mid = (l + r) / 2;
				if(f[mid] >= x) t = mid, r = mid - 1; else l = mid + 1;
			}
			ll ls = c[t].x, t0 = x - f[t - 1];
			l = 1, r = tot;
			while(l <= r) {
				int mid = (l + r) / 2;
				if(c0[mid].x > ls) l = mid + 1; 
				else if(c0[mid].x < ls) r = mid - 1;
				else if(c0[mid].s >= t0) t = mid, r = mid - 1;
				else l = mid + 1;
			}
			if(c0[t].x == c0[t - 1].x) t0 -= c0[t - 1].s;
			t = c0[t].id;
			printf("%lld
", solve(b[t] + 1, b[t + 1] - 1, t0, ls));
		}
	} 
	return 0;
}

自我小结

  • 这题的性质并不难推,但不一定会往这个方向想。
  • 只要有一次确定长度、确定段、确定位置这样的思路,这题其实并不难。
哈哈哈哈哈哈哈哈哈哈
原文地址:https://www.cnblogs.com/LZA119/p/14437608.html