CSP202112
01 序列查询
题目
标签
模拟?二分?差分?区间计算?
输入输出
数据范围
思路
考虑到边界情况,开辟一个大小至少为n+2的数组A,且A[0]=0, A[n+1]=N。
如何求 f ( x ) f(x) f(x)?
- 使用暴力,时间复杂度显然是 O ( N n ) O(Nn) O(Nn)
- 注意到A中的数严格递增,
f
(
x
)
=
i
⟺
A
i
≤
x
<
A
i
+
1
f(x)=i\iff A_i\le x<A_{i+1}
f(x)=i⟺Ai≤x<Ai+1,可以考虑使用二分求出
f
(
x
)
f(x)
f(x),时间复杂度为
O
(
N
l
o
g
n
)
O(Nlogn)
O(Nlogn)
- 理论上还可以进一步优化
- 又注意到A中的数都是整数,有
f
(
x
)
−
f
(
x
−
1
)
=
I
[
∃
i
(
A
[
i
]
=
x
)
]
f(x)-f(x-1) = I[\exist ~i(A[i]=x)]
f(x)−f(x−1)=I[∃ i(A[i]=x)],即区间
[
A
[
i
]
,
A
[
i
]
+
1
,
.
.
.
,
A
[
i
+
1
]
)
[A[i],A[i]+1,...,A[i+1])
[A[i],A[i]+1,...,A[i+1])满足题目所给提示。
- 使用瞪眼法也可以得到相同的结论,而且更加简单。
注意
- 无
代码
- 使用瞪眼法的代码
#include <bits/stdc++.h>
using namespace std;
int main( ){
int n, N;
cin >> n >> N;
vector<int> a(n+2);
a[0] = 0; a[n+1] = N;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
int ans = 0;
for (int i = 0; i <= n; ++i) {
ans += (a[i+1] - a[i]) * i;
}
cout << ans << endl;
return 0;
}
- 使用二分的代码
#include <bits/stdc++.h>
using namespace std;
int main( ){
int n, N;
cin >> n >> N;
vector<int> a(n+2);
int cur = 0;
a[0] = 0; a[n+1] = N;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
int ans = 0;
for (int i = 0; i < N; ++i) {
ans += upper_bound(a.begin(), a.end(), i) - a.begin() - 1;
}
cout << ans;
return 0;
}
02 序列查询新解
题目
标签
模拟?区间计算。
输入输出
数据范围
思路
题目明确表示了:禁止二分!
- 当然如果使用二分或者暴力还是能有70分
划分 f ( x ) f(x) f(x)的方法与上一题相同,但是由于N可能很大,无法使用二分。
注意到,对于给定的n和N, g ( x ) g(x) g(x)的区间划分是确定的,而 f ( x ) f(x) f(x)的区间划分还需要由 A A A数组确定。由于 f ( x ) 和 g ( x ) f(x)和g(x) f(x)和g(x)的定义域相同,这题实际考察点是:如何将 0 , 1 , … , N − 1 {0, 1, \dots, N-1} 0,1,…,N−1划分为多个区间,使划分得到的每个区间S满足 ( ∀ x , y ∈ S ) ( f ( x ) = f ( y ) ) & ( g ( x ) = g ( y ) ) (\forall x,y\in S)(f(x)=f(y))\&(g(x)=g(y)) (∀x,y∈S)(f(x)=f(y))&(g(x)=g(y))。同时,计算每个区间的长度以及 ∣ f ( x ) − g ( x ) ∣ |f(x)-g(x)| ∣f(x)−g(x)∣的值。
- 区间数量是否过多?
- 实际上,区间数量的一个上界为 3 n + 3 3n+3 3n+3。这是显然的,因为可以确定 g ( x ) g(x) g(x)的区间划分个数 N / ⌊ N n + 1 ⌋ ≤ N / ( N n + 1 + 1 ) ≤ n + 1 N/\lfloor\frac{N}{n+1}\rfloor\le N/(\frac{N}{n+1}+1)\le n+1 N/⌊n+1N⌋≤N/(n+1N+1)≤n+1。同时 f ( x ) f(x) f(x)划分出的每个区间,仅在一个端点落在 g ( x ) g(x) g(x)划分的区间内部时,使区间总数增加1,即最多产生 2 ( n + 1 ) 2(n+1) 2(n+1)个区间。所以,区间总数不会多于 3 n + 3 3n+3 3n+3.
- 考虑到每个区间的右端点一定是另一个区间的左端点(除最后一个),其实一个紧上界为 2 n + 3 2n+3 2n+3?
- 如何确定每个区间的长度?
- 鉴于我们已经计算出了区间数量的上界,且这个值并不算大,我们可以将所有区间的左端点(或右端点)记录下来,同时记录其是由
f
或
g
f或g
f或g分割得到的,以及其对应值
f
(
x
)
或
g
(
x
)
f(x)或g(x)
f(x)或g(x)。然后将按照端点位置排序,就得到了所有的新区间。
- 排序时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),求和为
O
(
n
)
O(n)
O(n),总复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
- 实际运行其实与另一种方法效率近似。
- 空间复杂度稍逊于下一种方法。
- 排序时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),求和为
O
(
n
)
O(n)
O(n),总复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
- 也可以从
f
(
x
)
(
或
g
(
x
)
)
f(x)(或g(x))
f(x)(或g(x))分割得到的区间开始,依次判断
g
(
x
)
(
或
(
f
(
x
)
)
g(x)(或(f(x))
g(x)(或(f(x))分割得到的每一个区间与其相交情况
- 线性时间复杂度,即 O ( n ) O(n) O(n)
- 代码逻辑很复杂。
- 鉴于我们已经计算出了区间数量的上界,且这个值并不算大,我们可以将所有区间的左端点(或右端点)记录下来,同时记录其是由
f
或
g
f或g
f或g分割得到的,以及其对应值
f
(
x
)
或
g
(
x
)
f(x)或g(x)
f(x)或g(x)。然后将按照端点位置排序,就得到了所有的新区间。
注意
- r = ⌊ N n + 1 ⌋ r=\lfloor\frac{N}{n+1}\rfloor r=⌊n+1N⌋可能使 g ( x ) g(x) g(x)分割得到的最后一个区间的右端点>N,要将其设为N。
- 耐心!
代码
- 从g(x)分割的区间开始,判断f(x)分割得到的每个区间与其相交情况
#include <bits/stdc++.h>
using namespace std;
int r;
vector<int> A;
int main( ) {
int n, N;
cin.tie(0);
ios::sync_with_stdio(false);
cin >> n >> N;
A.resize(n+2);
for (int i = 1; i <= n; ++i) {
cin >> A[i];
}
A[n+1] = N;
r = N / (n+1);
int cur = 0;
long long ans = 0;
/*******************
每段长度为r,共有k段,满足k*r>=N
--> 外循环条件
第i段为[i*r, (i+1)*r), 值为i, 且右端点不超过N
--> right = min(N, (i+1) * r)
考虑第i段与[A[cur], A[cur+1])的包含关系:
当A[cur] < right时, 有交, 否则没有
--> 内循环条件
相交部分贡献
-->(min(A[cur+1], right) - max(A[cur], i*r)) * abs(i-cur)
cur--必需,否则会丢失一段相交
*******************/
for (int i = 0; i * r < N; ++i) {
int right = min(N, (i+1) * r);
while (A[cur] < right) {
ans += (min(A[cur+1], right) - max(A[cur], i*r)) * abs(i-cur);
cur++;
}
cur--;
}
cout << ans;
return 0;
}
- 排序左端点后计算
#include <bits/stdc++.h>
using namespace std;
typedef struct Segment{
int ind; //左端点所在位置
int val; //对应f(ind)或g(ind)
bool f_div; //true时,为f分割得到
bool operator<(const Segment& s) {
return ind < s.ind;
}
}S;
int r;
vector<S> A;
int main( ) {
int n, N;
cin.tie(0);
ios::sync_with_stdio(false);
cin >> n >> N;
A.resize(n+2);
for (int i = 1; i <= n; ++i) {
cin >> A[i].ind;
A[i].f_div = true;
A[i].val = i;
}
A[n+1].ind = N; A[0].ind = 0;//不需要A[n+1]的val
A[n+1].f_div = true; A[0].f_div = true;
r = N / (n+1);
for (int i = 0, t = 0; t < N; ++i, t += r) {
S tp;
tp.ind = t;
tp.val = i;
tp.f_div = false;
A.push_back(tp);
}
sort(A.begin(), A.end());
long long cur_f = 0, cur_g = 0, ans = 0;
for (int i = 1; i < A.size(); ++i){
ans += abs(cur_f - cur_g) * (A[i].ind - A[i-1].ind);
if (A[i].f_div) cur_f = A[i].val;
else cur_g = A[i].val;
}
cout << ans;
return 0;
}