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,切不出来。

  • 相关阅读:
    Numpy数据处理函数
    数据库-创建表
    描述统计学:表格法和图形法
    最短路 || POJ 1511 Invitation Cards
    搜索 || DFS || UOJ 146 信息传递
    最短路 || UOJ 19 寻找道路
    最短路 || POJ 1847 Tram
    博弈论
    筛选法 || POJ 3292 Semi-prime H-numbers
    团队第一次作业
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11683685.html
Copyright © 2011-2022 走看看