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,q≤105,
m
≤
1
0
14
mle10^{14}
m≤1014
题解
- 首先要发现一个重要的性质,每个段中间位置修改后会被分成两段,尽管这两段长度可能相差
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;
}
自我小结
- 这题的性质并不难推,但不一定会往这个方向想。
- 只要有一次确定长度、确定段、确定位置这样的思路,这题其实并不难。