zoukankan      html  css  js  c++  java
  • [ 2020牛客NOIP赛前集训营-提高组(第一场)] 牛牛的凑数游戏

    Problem

    题目地址

    Solution

    集合最小不能表示数

    定义 :对于一个多重数集 (S),对于一非负整数 (x),若存在 (S'⊆ S)(S') 中所有数字之和恰好等于 (x),则说 (S) 可以表示 (x) 。显然对于任意的多重数集都可以表示 (0),因为空集是所有集合的子集。定义集合 (S) 的最小不能表示数为,一个最小的非负整数 (x)(S) 不能表示 (x)。举个例子来说,例如 (S={1,2,3,8,9}) ,那么集合 (S) 的最小不能表示数就为 (7)

    如何求一个集合的最小不能表示数?

    将数字按升序排列,假设前 (i) 个数可以表示的数是 ([0,k]),那么如果 (a_{i+1} le k+1),则前 (i+1) 个数可以表示的数有 ([0,k+a_{i+1}]),否则前 (i+1) 个数可以表示的数是 ([0,k]),进一步可以推断出整个集合的的最小不能表示数就是 (k+1)

    相当于我们要将数字排序,然后用 (sum) 记录前 (i) 个数的和。如果 (a_{i} le sum+1),就把 (a_i) 加入 (sum),否则如果 (a_{i} > sum),那么 (sum+1) 就是这个集合最小不能表示数。求单个集合时间复杂度 (O(n log n)) (排序)。

    迭代算法求解

    排序求解法是我们直接推导得到的,我们可以考虑用一种迭代法:

    不将数字排序,把 (n) 个数扫一遍,将 (le sum+1) 的数加入 (sum),上一轮已经加入过 (sum) 的数不能再加入。记新的 (sum)(sum')。用 (sum') 代替 (sum),重复迭代上述操作。若第 (k) 轮操作后 (sum = sum')(没有数被加入进去)则算法结束 break。

    接近 (log n) 层迭代,每层迭代 (O(n)) 的复杂度,时间复杂度 (O(n log n))

    进一步优化

    一共 (m) 个查询区间,将查询区间离线,同时进行 (m) 个查询区间的迭代求解。用树状数组处理每个查询区间,统计该区间 (le) 当前区间的 (sum+1) 的数之和。树状数组从左到右把数加入处理一遍可以把 (m) 的查询区间全部处理完,时间复杂度 (O(n log n))

    总复杂度 (O(n log^2 n))

    Code

    Talk is cheap.Show me the code.

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    inline int read() {
        int x = 0, f = 1; char ch = getchar();
        while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
        while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
        return x * f;
    }
    const int N = 1e5+7;
    int n,m,ntmp;
    int a[N],ida[N],L[N],R[N],sum[N],pre[N],c[N],btmp[N];
    vector<pair<int,int> > query[N];
    void Add(int x,int y) {
    	while(x <= ntmp) {
    		c[x] += y; x += x & -x;
    	}
    }
    int Ask(int x) {
    	int res = 0;
    	while(x > 0) {
    		res += c[x]; x -= x & -x;
    	}
    	return res;
    }
    int Getpos(int x) {
    	int l = 1, r = ntmp, mid, ans;
    	while(l <= r) {
    		mid = (l + r) >> 1;
    		if(btmp[mid] <= x) {
    			ans = mid;
    			l = mid + 1;
    		} else r = mid - 1;
    	}
    	return ans;
    }
    signed main()
    {
    	n = read(), m = read();
    	for(int i=1;i<=n;++i) btmp[i] = a[i] = read();
    	sort(btmp+1, btmp+1+n);
    	ntmp = unique(btmp+1, btmp+1+n) - btmp - 1;
    	for(int i=1;i<=n;++i) ida[i] = lower_bound(btmp+1, btmp+1+ntmp, a[i]) - btmp;
    	for(int i=1;i<=m;++i) {
    		L[i] = read(), R[i] = read();
    		query[L[i]-1].push_back(make_pair(i, -1));
    		query[R[i]].push_back(make_pair(i, 1));
    	}
    	for(int i=1;i<=m;++i) pre[i] = -1;
    	while(true) {
    		bool flag = 1;
    		cout << endl << endl;
    		for(int i=1;i<=m;++i) {
    			if(sum[i] != pre[i]) {
    				flag = 0; break;
    			}
    		}
    		if(flag == 1) break;
    		for(int i=1;i<=m;++i) pre[i] = sum[i];
    		memset(c ,0, sizeof(c));
    		for(int i=1;i<=n;++i) {
    			Add(ida[i],a[i]);
    			for(int j=0;j<query[i].size();++j) {
    				int qid = query[i][j].first, f = query[i][j].second;
    				int sid = Getpos(pre[qid] + 1);
    				sum[qid] += Ask(sid) * f;
    			}
    		}
    		for(int i=1;i<=m;++i) sum[i] -= pre[i];
    	}
    	for(int i=1;i<=m;++i) printf("%lld
    ",sum[i]+1);
    	return 0;
    }
    /*
    10 10
    10 6 2 7 1 4 1 6 3 4 
    2 3
    1 9
    6 6
    2 8
    1 4
    2 7
    4 5
    7 7
    6 8
    7 10
    
    
    1
    41
    1
    28
    1
    22
    2
    2
    2
    2
    */
    

    Summary

    • 集合最小不能表示数

    • 迭代算法思想

  • 相关阅读:
    语法专题:错误处理机制
    语法专题:数据类型的转换
    运算符:其他运算符,运算顺序
    运算符:二进制位运算符
    运算符:布尔运算符
    运算符:比较运算符
    day09 继承、super、this、抽象类
    day08 String类、Static关键字、Arrays类、Math类
    day06 Scanner类、Random类、匿名对象、ArrayList类
    day05 encapsulation
  • 原文地址:https://www.cnblogs.com/BaseAI/p/13834898.html
Copyright © 2011-2022 走看看