【每日一题】codeforces 1355C(1800)(数论 and 前缀和)

想她一次就背十个单词,当我英语过六级后,我就去告诉她,我很在意她
每日一题,坚持使我快乐


今日份快乐:codeforces 1355C 传送门
明天份快乐:codeforces 20C 传送门


题目大意:

给出四个整数a,b,c 和 d,满足 a ≤ \leq b ≤ \leq c ≤ \leq d。
求:有多少组x,y 和 z,他们可以构成三角形,并且满足 a ≤ \leq x ≤ \leq b ≤ \leq y ≤ \leq c ≤ \leq z ≤ \leq d。

分析:

题目的数据范围在 1e6,那么肯定要用 O(n)或者更高效的算法来解决,我只会写出 O(n2) 的算法,参考了官方题解,给出一个O(n)的写法。


众所周知,三角形的存在定理是任意两边之和大于第三边,我们这里有 x ≤ \leq y ≤ \leq z,所有只需要满足 x + y > z 就ok。我们用类似与容斥的思想,假设 s = x + y,我们去统计等于 s 的(x,y)有多少组。这个 s 有什么用呢?我们来举个例子,假设 s = 5 对应的(x,y)有 10组,当 z = 4时,s = 5 对应的 10 组(x,y)都可和 z 构成三角形。


现在问题就变成了如何统计 s 出现的个数,我们令 x = i,因为 y 的取值区间为 [b, c],则 x + y 的范围是 [i+b, i+c],我们要对在区间 [i+b, i+c] 的 s 出现个数+1,这样我们就可以通过 x 的值去统计 s。
在统计好 s 以后,我们令 z = i ,计算 sz+1 到 sd 的和,这便是当 z = i 时可以构成的三角形的个数。z 的取值为 [c, d],遍历就OK。当然,高效计算 sz+1 到 sd 的和sumz 肯定要用前缀和。

这里有一个问题,如何高效的做到区间 [i+b, i+c] 的 s 出现个数+1
比如现在我们要对 [5, 10] 这个区间的 s 出现个数 +1
第一步:标记 s5 和 s11

si 4 5 6 7 8 9 10 11
numi 0 1 0 0 0 0 0 -1

第二步:计算 num[i] += num[i-1]

si 4 5 6 7 8 9 10 11
numi 0 1 1 1 1 1 1 0

通过这两步,我们就可以对区间 [5, 10]中的 num 进行 +1。
在应用时,我们先对所有要操作的区间进行第一步操作,最后只进行一次第二步,就可以高效的去实现区间+1的操作了

代码

#include 
#define ll long long
using namespace std;

const int maxn = 1e6+10;
ll sum[maxn] = {0};

int main(){
	
	ios::sync_with_stdio(false);
	
	ll a, b, c, d;
	cin >> a >> b >> c >> d;
	
	for(int i = a; i <= b; i++){       //  对 s 进行区间标记 
		sum[i + b]++;       	   // 区间的开始标记  +1 
		sum[i + c + 1]--;	   // 区间结束的后一位标记为 -1
	}
	
	for(int i = 1; i <= d + d; i++){        // 计算 s 出现的个数,注意 s 的范围应该是 d + d 
		sum[i] += sum[i-1];		// 这里的 sum 就是 s 出现的个数 
	}
	
	for(int i = 1; i <= d + d; i++){        // 计算 s 的前缀和
		sum[i] += sum[i-1];		// 这里的 sum 被更新为前缀和
	}
	
	ll ans = 0;
	for(int i = c; i <= d; i++){ 	 	 //  通过前缀和计算每个 z 对答案的贡献 
		ans += sum[d + d] - sum[i];      
	}
	
	cout << ans << endl;
	
	return 0;
}

坚持的时候很狼狈,等成功以后,丑的还是丑的

你可能感兴趣的:(每日一题)