• Luogu P4587 神秘数


    题目大意

    给定一个有 (n) 个正整数的可重集 (S) ,有 (m) 次询问,格式为 (l_i,r_i) ,要求输出集合 (left{a_j | a_j in S,j in [l_i,r_i] ight}) 的子集和不能构成最小正整数。
    其中 (1 leq n leq 10^5)(1 leq m leq 10^5)(sum_{i = 1}^{n} a_i leq 10^9)

    题解

    假设此时已有若干个数可以构成的数的区间为 ([1,p])
    此时若再给一个数 (q) ,容易证明,当 (q leq p + 1) 时,该区间可以变为 ([1,p+q]) ,否则不能。

    对于每一个询问,我们对 ([l_i,r_i]) 中的数从小到大进行排序,并从小到大开始遍历。
    若此时前 (j - 1) 个数可表示的区间为 ([1,p]),如果存在 (a_j,a_{j+1},dots,a_k) 满足不大于 (p+1) 的条件,则该区间可以扩大到 ([1,p+sum_{r=j}^{k}a_r]) 。若不存在,即 (sum_{r=j}^{k}a_r = 0),则答案为 (p + 1)
    此次遍历结束后,下一次遍历可以找大小在 ([p+1+1,p+sum_{r=j}^{k}a_r + 1]) 的范围内的数。
    即若这次遍历的数区间为 ([x+1,y+1]),可表示的数的范围由 ([1,y]) 变为 ([1,y+z]),则下一次遍历的数的区间为 ([y+2,y+z+1])
    很显然套主席树即可。

    #include <cstdio>
    #include <algorithm>
    
    using std::max;
    using std::min;
    
    #define MAX_N (100000 + 5)
    #define MAX_M (100000 + 5)
    #define MAX_SIZE 3400000
    #define INF 1000000000
    
    #define SUBMIT
    
    #ifdef SUBMIT
    
    #define SIZE (1 << 21) 
    #define Getchar() (pr1 == pr2 && (pr2 = (pr1 = fr) + fread(fr, 1, SIZE, stdin), pr1 == pr2) ? EOF : *pr1++) 
    
    char fr[SIZE], * pr1 = fr, * pr2 = fr;
    
    #else
    
    #define Getchar() getchar()
    
    #endif
    
    struct Tree
    {
        int sum;
        int l, r;
    };
    
    int n, m;
    int root[MAX_M], num;
    Tree s[MAX_SIZE];
    
    int Read();
    void Insert(int, int, int, int, int);
    int Query(int, int, int, int, int, int);
    
    int main()
    {
    	n = Read();
    	for (int i = 1; i <= n; ++i)
    	{
    		root[i] = ++num;
    		Insert(root[i - 1], root[i], 1, INF, Read());
    	}
    	m = Read();
    	int l, r, x, y, sum;
    	while (m--)
    	{
    		l = Read();
    		r = Read();
    		x = 1;
    		y = 0;
    		while (1)
    		{
    			sum = Query(root[l - 1], root[r], 1, INF, x, y + 1);
    			if (!sum) break;
    			x = y + 2;
    			y += sum;
    		}
    		printf("%d
    ", y + 1);
    	}
        return 0;
    }
    
    int Read() 
    {
        int res = 0;
        char ch = Getchar();
        while(ch < '0' || ch > '9')
    	{
    		ch = Getchar();
    	}
        while(ch >= '0' && ch <= '9') 
        {
            res = res * 10 + ch - '0';
            ch = Getchar();
        }
        return res;
    }
    
    void Insert(int i1, int i2, int L, int R, int val)
    {
    	if (L == R)
    	{
    		s[i2].sum = s[i1].sum + val;
    		return;
    	}
    	int mid = L + R >> 1;
    	if (val <= mid)
    	{
    		s[i2].l = ++num;
    		s[i2].r = s[i1].r;
    		Insert(s[i1].l, s[i2].l, L, mid, val);
    	}
    	else
    	{
    		s[i2].l = s[i1].l;
    		s[i2].r = ++num;
    		Insert(s[i1].r, s[i2].r, mid + 1, R, val);
    	}
    	s[i2].sum = s[s[i2].l].sum + s[s[i2].r].sum;
    }
    
    int Query(int i1, int i2, int L, int R, int lt, int rt)
    {
    	if (lt <= L && R <= rt) return s[i2].sum - s[i1].sum;
        int mid = L + R >> 1, res = 0;
        if (max(L, lt) <= min(mid, rt) && s[s[i2].l].sum - s[s[i1].l].sum)
        {
    		res += Query(s[i1].l, s[i2].l, L, mid, lt, rt);
        }
        if (max(mid + 1, lt) <= min(R, rt) && s[s[i2].r].sum - s[s[i1].r].sum)
        {
        	res += Query(s[i1].r, s[i2].r, mid + 1, R, lt, rt);
        }
        return res;
    }
    

    当然,还有另一种做法,若可表示数的范围为 ([1,y]) ,则在大小为 ([1,y]) 的范围内找数,若他们的和 (sum geq y) ,则区间可变为 ([1,sum+1]) ,否则区间无法扩大。

  • 相关阅读:
    Qt图片显示
    C# 对话框使用整理
    C# AutoResetEvent 使用整理
    C++ 模板
    superset使用
    superset部署
    kafka修改topic副本数
    c# 生成自定义图片
    c# 解决Randoms伪随机重复问题
    c# 图片加水印
  • 原文地址:https://www.cnblogs.com/kcn999/p/13444986.html
Copyright © 2020-2023  润新知