cf 1355C, Count Triangles,区间前缀和或者组合计数

题意

三角形的三条边满足条件 1 ≤ A ≤ x ≤ B ≤ y ≤ C ≤ z ≤ D ≤ 5 ⋅ 1 0 5 1\leq A\leq x\leq B\leq y \leq C\leq z\leq D\leq 5\cdot 10^5 1AxByCzD5105,求有多少组 { x , y , z } \{x,y,z\} {x,y,z}能够构成一个合法的三角形。

题解

很明显,合法三角形必满足 x + y > z x+y>z x+y>z,那么如果每次固定 x x x的值,而 y y y z z z的值可以在区间内随意取的话,这就转化为了一个求区间交集的问题了, x + y x+y x+y的区间为 [ B + x , C + x ] [B+x,C+x] [B+x,C+x] z z z的区间为 [ C , D ] [C,D] [C,D],当然这里的解法又分为两种。

1. 直接计数求解

这里区间不管是哪种方式求解方式都是一样的,我这里比较奇怪,大家看看就好。
遍历 x x x的值从 [ A , B ] [A,B] [A,B],那么 z − x z-x zx的区间为 [ C − x + 1 , D − x + 1 ] [C-x+1,D-x+1] [Cx+1,Dx+1],这里直接 + 1 +1 +1省去了边界重合时的麻烦,这样 y y y的值 [ B , C ] [B,C] [B,C]只要大于等于 z − x z-x zx的值就行了。
考虑三种情况:
1. C < C − x + 1 C<C-x+1 C<Cx+1的情况,答案直接为0。

2. B > = D − x + 1 B>=D-x+1 B>=Dx+1的情况,答案显然为 ( B − C + 1 ) ∗ ( r − l + 1 ) (B-C+1)*(r-l+1) (BC+1)(rl+1),简化令 r = D − x + 1 , l = C − x + 1 r=D-x+1,l=C-x+1 r=Dx+1,l=Cx+1.
在这里插入图片描述
3. 两个区间有部分重合的情况:
在这里插入图片描述
那么观察可得重合部分的左端点 L = m a x ( b , l ) L=max(b,l) L=max(b,l),右端点 R = m i n ( c , r ) R=min(c,r) R=min(c,r)
z = L z=L z=L时,答案为 C − L + 1 C-L+1 CL+1,在 z = L + 1 z=L+1 z=L+1时,答案为 C − L C-L CL,在 z = R z=R z=R时,答案为 C − R + 1 C-R+1 CR+1,所以根据等差数列求和公式可得答案为 ( R − L + 1 ) ∗ ( C − L + 1 + C − R + 1 ) / 2 (R-L+1)*(C-L+1+C-R+1)/2 (RL+1)(CL+1+CR+1)/2
在上图的第二种情况中,因为 C − x + 1 < b C-x+1<b Cx+1<b,所以还需加上 ( B − l ) ∗ ( C − B + 1 ) (B-l)*(C-B+1) (Bl)(CB+1)这一部分的,因为这一部分 y y y取所有的值都符合条件。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

int main() {
	ll a, b, c, d;
	cin >> a >> b >> c >> d;
	ll res = 0;
	for (ll i = a; i <= b; i++) {
		ll l = c-i+1, r = d-i+1;
		if (l > c) continue;
		if (r <= b) {
			res += (r-l+1)*(c-b+1);
		} else {
			ll x = max(b, l), y = min(c, r);
			res += (y-x+1)*(c-x+1+c-y+1)/2;
			if (l < b) {
				res += (b-l)*(c-b+1);
			}
		}
	}
	cout << res << endl;
}

2.区间转为前缀和求解

x x x遍历 [ A , B ] [A,B] [A,B],那么则得到 B − A + 1 B-A+1 BA+1个区间 [ B + x , C + x ] [B+x,C+x] [B+x,C+x],把每个区间的左端点 B + x B+x B+x设为 + 1 +1 +1,右端点 C + x + 1 C+x+1 C+x+1设为 − 1 -1 1,再对整个数组求一个前缀和,那么就得到了 x + y = x y x+y=xy x+y=xy能得到的值 x y xy xy的个数。
然后再遍历 z z z的值 [ C , D ] [C,D] [C,D],看 x y xy xy的值有多少个比 z z z大的,都加起来即为答案,比 z z z大的值的个数可以再求一个前缀和得到。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 1e6 + 10;
ll pre[maxn];

int main() {
	ll a, b, c, d;
	cin >> a >> b >> c >> d;
	for (ll i = a; i <= b; i++) {
		pre[b+i] += 1;
		pre[c+i+1] -= 1;
	}
	// 第一次前缀和得到一个位置上有几个
	for (ll i = 1; i < maxn; i++) {
		pre[i] += pre[i-1];
	}
	// 第二次前缀和得到位置i之前的所有的和
	for (ll i = 1; i < maxn; i++) {
		pre[i] += pre[i-1];
	}
	ll cnt = 0;
	for (ll z = c; z <= d; z++) {
		cnt += pre[maxn-1] - pre[z];
	}
	cout << cnt << endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值