题解 [ABC147F] Sum Difference

模拟赛做到的,结果两个小时都没想出来。

题目要求一个人在等差数列中选一些数,另一个人选剩下的,求他们选的数差的不同个数。

那么设一个人选的和为 \(w\), 总和是 \(sum\), 两个人的差就是 \(2w-sum\), 因为 \(sum\) 不变,所以只要求出 \(w\) 的不同个数就可以了。

设一个人选的数的下标集合是 \(T\), 那么 \(w = x|T| + \sum_{i \in T} (i-1)d\),将 \(d\) 提出,再设 \(k = |T|, v = \sum_{i \in T} (i-1)\), 式子变成了 \(w = kx + vd\)。再想想 \(v\) 的取值范围,因为 \(i-1\) 是在 \([0, n-1]\) 之间连续的,所以我们非常好确定 \(v\) 的范围,就是 \([\sum_{i=0}^{k-1}i,\ i\cdot n - \sum_{i=1}^ki]\)。

考场上想到也许可以容斥,先把这所有的区间内的个数都加起来,然后找到类似 \(k_1 d = k_2 x\) 的式子去把重复的给减掉,但是这样细节繁多,而且时间上也过不去。

然后就是本题的关键想法:\(kx + vd\) 在模 \(d\) 意义下肯定是相同的。这意味着可以按 \(kx\) 分类,不同类中的肯定是不同的,所以只要处理 \(kx\) 模 \(d\) 相同的行的数就可以了。
我们可以把所有 \(kx\) 模 \(d\) 相同的数转换成数轴的一段。因为同一行的数都间隔 \(d\), 那么同一行就可以直接用 \(v\) 的值来表示,至于处理不同行,只需要将 \(v\) 的左右端点都加上 \(\frac{kx}{d}\) 就可以了。

然后就变成了简单的线段求并。

代码。

#include <cstdio>
#include <algorithm>
#include <map>
#include <vector>
#define int long long
int n, x, d, ans;
typedef std::vector<std::pair<int, int> > twt;
std::map<int, twt> map;
signed main() {
	scanf("%lld%lld%lld", &n, &x, &d);
	if(x == 0 && d == 0) return puts("1"), 0;
	if(x != 0 && d == 0) return printf("%lld\n", n+1), 0;
	if(d < 0) x = -x, d = -d;
	for(int i = 0; i <= n; i++) {
		int t = i*x, L = (i-1)*i/2+t/d, R = i*n-(i+1)*i/2+t/d;
		t %= d;
		if(t < 0) t += d;
		map[t].push_back(std::make_pair(L, R));
	}
	for(std::map<int, twt>::iterator it = map.begin(); it != map.end(); it++) {
		twt tmp = (*it).second;
		if(!tmp.size()) continue;
		std::sort(tmp.begin(), tmp.end());
		int l = tmp[0].first, r = tmp[0].second;
		for(int j = 1; j < (signed)tmp.size(); j++) 
			if(tmp[j].first > r) ans += r-l+1, l = tmp[j].first, r = tmp[j].second;
			else r = std::max(r, tmp[j].second);
		ans += r-l+1;
	}
	printf("%lld", ans);
}
上一篇:java-Jackson继承和反序列化


下一篇:Day4:the basic use of Set,%,Format and function