• 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;
    }
    

    自我小结

    • 这题的性质并不难推,但不一定会往这个方向想。
    • 只要有一次确定长度、确定段、确定位置这样的思路,这题其实并不难。
    哈哈哈哈哈哈哈哈哈哈
  • 相关阅读:
    仲兆鹏 160809329 第5次
    仲兆鹏160809329-第四次作业
    160809329 仲兆鹏 3
    张浩飞 160809307 (12)
    160809307 张浩飞
    160809307 张浩飞
    160809307 张浩飞(8)
    160809307 张浩飞(7)
    160809307 张浩飞
    160809307 张浩飞 5
  • 原文地址:https://www.cnblogs.com/LZA119/p/14437608.html
Copyright © 2020-2023  润新知