[ 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\n",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

  • 集合最小不能表示数

  • 迭代算法思想

posted @ 2020-10-18 14:10  基地AI  阅读(300)  评论(0编辑  收藏  举报