题意
三角形的三条边满足条件 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 1≤A≤x≤B≤y≤C≤z≤D≤5⋅105,求有多少组 { 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
z−x的区间为
[
C
−
x
+
1
,
D
−
x
+
1
]
[C-x+1,D-x+1]
[C−x+1,D−x+1],这里直接
+
1
+1
+1省去了边界重合时的麻烦,这样
y
y
y的值
[
B
,
C
]
[B,C]
[B,C]只要大于等于
z
−
x
z-x
z−x的值就行了。
考虑三种情况:
1.
C
<
C
−
x
+
1
C<C-x+1
C<C−x+1的情况,答案直接为0。
2.
B
>
=
D
−
x
+
1
B>=D-x+1
B>=D−x+1的情况,答案显然为
(
B
−
C
+
1
)
∗
(
r
−
l
+
1
)
(B-C+1)*(r-l+1)
(B−C+1)∗(r−l+1),简化令
r
=
D
−
x
+
1
,
l
=
C
−
x
+
1
r=D-x+1,l=C-x+1
r=D−x+1,l=C−x+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
C−L+1,在
z
=
L
+
1
z=L+1
z=L+1时,答案为
C
−
L
C-L
C−L,在
z
=
R
z=R
z=R时,答案为
C
−
R
+
1
C-R+1
C−R+1,所以根据等差数列求和公式可得答案为
(
R
−
L
+
1
)
∗
(
C
−
L
+
1
+
C
−
R
+
1
)
/
2
(R-L+1)*(C-L+1+C-R+1)/2
(R−L+1)∗(C−L+1+C−R+1)/2。
在上图的第二种情况中,因为
C
−
x
+
1
<
b
C-x+1<b
C−x+1<b,所以还需加上
(
B
−
l
)
∗
(
C
−
B
+
1
)
(B-l)*(C-B+1)
(B−l)∗(C−B+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
B−A+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;
}