zoukankan      html  css  js  c++  java
  • @loj


    @description@

    一个可重复数字集合 S 的神秘数定义为最小的不能被 S 的子集的和表示的正整数。例如:

    S = {1,1,1,4,13}
    1 = 1
    2 = 1+1
    3 = 1+1+1
    4 = 4
    5 = 4+1
    6 = 4+1+1
    7 = 4+1+1+1

    8 无法表示为集合 S 的子集的和,故集合 S 的神秘数为 8。

    现给定 n 个正整数 a1 ... an, m 个询问,每次询问给定一个区间 [l, r] (l <= r),求由 al ... ar 所构成的可重复数字集合的神秘数。

    输入格式
    第一行一个整数 n,表示数字个数。
    第二行 n 个整数,从 1 编号。
    第三行一个整数 m,表示询问个数。
    以下 m 行,每行一对整数 l, r,表示一个询问。

    输出格式
    对于每个询问,输出一行对应的答案。

    样例输入
    5
    1 2 4 9 10
    5
    1 1
    1 2
    1 3
    1 4
    1 5
    样例输出
    2
    4
    8
    8
    8

    数据范围与提示
    对于 100% 的数据点, n, m <= 100000,∑a = 10^9。

    @solution@

    不妨先看对于一个集合怎么快速求它的 “神秘数”。
    众所周知子集和问题是 NPC 的,所以这个 “神秘数” 肯定是具有一定性质的。

    我们考虑一个个加入元素。假如加入某一元素 x 时,此时的 “神秘数” 为 k。
    如果用上 x 也无法拼出 k,则要么 x > k,要么 k - x 在加入 x 之前也无法被拼出。又因为 k 是最小的,只能说明 x > k。
    如果我们从小到大考虑一个集合内的所有元素,且 x > k,则 x 以后也 > k。此时 “神秘数” 就保持不变了。
    另一方面,假如 x <= k,则我们的 “神秘数” 将会从 k 变成 k + x。

    这样我们就可以 O(n) 从小到大扫一遍,维护一个 “神秘数” k。
    注意到 k 始终等于某个前缀的和 + 1,而这给了我们用数据结构维护的机会。
    进一步地,假如套到原题上面来,我们可以莫队 + 维护一棵线段树,每个位置维护 ((sum_{p=1}^{i-1}a_p) + 1 - a_i)。然后线段树上二分求第一个负数的位置。
    但是这还不够。复杂度 (O(nsqrt{n}log n)) 还是太慢了。

    考虑复杂度卡在,我们每一次只能往集合里塞一个数。
    假如说每一次当前所有 x <= k 的 x 全部塞进集合,会发生什么。
    设前一次的 “神秘数” 为 p,则前一次我们可以把 <= p 的全部塞进集合,得到新一次的 “神秘数” 为 q。
    新一轮中,我只能塞 p < x <= q 的数(太小的已经塞过了,太大的塞不进去)。假如能塞,则下一次的 “神秘数” >= 2*p。
    于是这种操作下:“神秘数” 呈指数级增长。

    那么对于每一个区间 [l, r],我们只需要找到 p < x <= q 的数的求和,得到新的 “神秘数”。
    这个是可持久化线段树的经典应用了。我们按 ai 从小到大的顺序往线段树里塞,得到 <= ai 的 n 棵线段树。

    @accepted code@

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    typedef pair<int, int> pii;
    #define mp make_pair
    #define fi first
    #define se second
    const int MAXN = 100000;
    struct node{
    	int sum;
    	node *ch[2];
    }t[40*MAXN + 5], *rt[MAXN + 5], *ncnt, *NIL;
    struct segtree{
    	segtree() {
    		NIL = ncnt = &t[0];
    		NIL->ch[0] = NIL->ch[1] = NIL;
    		NIL->sum = 0;
    	}
    	void pushup(node *x) {
    		x->sum = x->ch[0]->sum + x->ch[1]->sum;
    	}
    	node *newnode(const node *pre = NIL) {
    		ncnt++; *ncnt = *pre;
    		return ncnt++;
    	}
    	node *insert(const node *pre, int l, int r, int p, int k) {
    		node *x = newnode(pre); x->sum += k;
    		if( l == r ) return x;
    		int mid = (l + r) >> 1;
    		if( p <= mid ) x->ch[0] = insert(pre->ch[0], l, mid, p, k);
    		else x->ch[1] = insert(pre->ch[1], mid + 1, r, p, k);
    		pushup(x);
    		return x;
    	}
    	int query(const node *x, int l, int r, int ql, int qr) {
    		if( ql <= l && r <= qr )
    			return x->sum;
    		if( l > qr || r < ql )
    			return 0;
    		int mid = (l + r) >> 1;
    		return query(x->ch[0], l, mid, ql, qr) + query(x->ch[1], mid + 1, r, ql, qr);
    	}
    }T;
    int a[MAXN + 5]; pii b[MAXN + 5];
    int main() {
    	int n, m; scanf("%d", &n);
    	for(int i=1;i<=n;i++)
    		scanf("%d", &a[i]);
    	scanf("%d", &m);
    	for(int i=1;i<=n;i++)
    		b[i] = mp(a[i], i);
    	sort(b + 1, b + n + 1);
    	rt[0] = NIL;
    	for(int i=1;i<=n;i++)
    		rt[i] = T.insert(rt[i - 1], 1, n, b[i].se, b[i].fi);
    	for(int i=1;i<=m;i++) {
    		int l, r, nw = 0, x; scanf("%d%d", &l, &r);
    		while( true ) {
    			x = T.query(rt[nw], 1, n, l, r) + 1;
    			int y = upper_bound(b + 1, b + n + 1, mp(x, n + 1)) - b - 1;
    			if( nw == y ) break;
    			nw = y;
    		}
    		printf("%d
    ", x);
    	}
    }
    

    @details@

    实现基本没有细节。

    所以我太菜了。把正解思路擦边擦了个遍,就是没有想到正解的 “一次加入能加入的所有数”。
    这本来是道 sb 题,可惜我太 sb,切不出来。

  • 相关阅读:
    为什么 PCB 生产时推荐出 Gerber 给工厂?
    Fedora Redhat Centos 有什么区别和关系?
    【KiCad】 如何给元件给元件的管脚加上划线?
    MCU ADC 进入 PD 模式后出现错误的值?
    FastAdmin 生产环境升级注意
    EMC EMI 自行评估记录
    如何让你的 KiCad 在缩放时不眩晕?
    KiCad 5.1.0 正式版终于发布
    一次单片机 SFR 页引发的“事故”
    java基础之集合
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11683685.html
Copyright © 2011-2022 走看看