题目链接
CodeForce1485B. Replace and Keep Sorted
思路
一开始没有思路,只想到枚举每个替换的值。但这样是 O ( n 2 ) O(n^2) O(n2)算法,会超时。
记输入的数组为 a [ n ] , i ∈ [ 1 , n ] a[n], i\in[1,n] a[n],i∈[1,n],即与题目中角标保持一致。
发现每个可替换的数必须仍然在区间之间。因此可以构造
d
i
f
f
[
i
]
diff[i]
diff[i]
d
i
f
f
[
i
]
=
a
[
i
+
1
]
−
a
[
i
−
1
]
−
2
diff[i]=a[i+1]-a[i-1]-2
diff[i]=a[i+1]−a[i−1]−2
其中
d
i
f
f
[
i
]
(
1
≤
i
≤
n
)
diff[i]\quad(1\le i \le n)
diff[i](1≤i≤n)对应每个元素位于区间内部时的可替换方案数。
由于当前位置i必须大于前一个元素而小于后一个元素。
可取的取值个数是 ( a [ i − 1 ] , a [ i + 1 ] ) (a[i-1],a[i+1]) (a[i−1],a[i+1]),再除去它自身。
故有 d i f f [ i ] = ( a [ i + 1 ] − a [ i − 1 ] ) − 2 diff[i]=(a[i+1]-a[i-1])-2 diff[i]=(a[i+1]−a[i−1])−2
- 为了方便观察,令 a [ 0 ] = 0 , a [ n + 1 ] = k + 1 a[0]=0,a[n + 1]=k + 1 a[0]=0,a[n+1]=k+1,这样就方便处理边界情况,避免越界。
- 但注意到存在 a [ r ] < k a[r] < k a[r]<k以及 a [ l ] > 1 a[l] > 1 a[l]>1的情况。所以对于查询的端点 l , r l,r l,r只有单侧限制而不是双侧限制。
- 记查询区间
[
l
,
r
]
[l,r]
[l,r]的方案数为
P
l
,
r
P_{l,r}
Pl,r,于是
P
l
,
r
=
∑
i
=
l
+
1
r
−
1
d
i
f
f
[
i
]
+
P
l
+
P
r
P_{l,r}=\sum_{i=l+1}^{r-1} diff[i]+P_l+P_r
Pl,r=i=l+1∑r−1diff[i]+Pl+Pr
其中 P l P_l Pl代表左端点 l l l的方案总数, P r P_r Pr代表右端点 r r r的方案总数。
处理端点
对于查询端点
l
l
l,只要位置
l
l
l替换的值小于
a
[
l
+
1
]
a[l+1]
a[l+1]就可以了,再除去
a
[
l
]
a[l]
a[l],故有
a
[
l
+
1
]
−
2
a[l+1]-2
a[l+1]−2个。
对于查询端点
r
r
r,只要位置
r
r
r替换的值大于
a
[
r
−
1
]
a[r-1]
a[r−1]就可以了,再除去
a
[
r
]
a[r]
a[r],故有
k
−
a
[
r
−
1
]
−
1
k - a[r-1] - 1
k−a[r−1]−1个。
至此拼凑以上三部分即得到总的方案数。
k − a [ r − 1 ] − 1 k - a[r-1] - 1 k−a[r−1]−1一定非负。
考虑 a [ r − 1 ] = k a[r-1]=k a[r−1]=k的情况,此时 k − a [ r − 1 ] − 1 = − 1 < 0 k - a[r-1] - 1=-1<0 k−a[r−1]−1=−1<0
但由题设知 a r − 1 < a r < k a_{r-1}<a_r<k ar−1<ar<k,故符合题意的 a r a_r ar不存在,矛盾。
故 a [ r − 1 ] < k a[r-1]<k a[r−1]<k,则必有 k − a [ r − 1 ] − 1 ≥ 0 k - a[r-1] - 1\ge0 k−a[r−1]−1≥0
对于 a [ l + 1 ] − 2 ≥ 0 a[l+1]-2\ge0 a[l+1]−2≥0同理。
代码
#include<bits/stdc++.h>
using namespace std;
int n, q, k;
int l, r;
typedef long long LL;
int main() {
cin >> n >> q >> k;
vector<LL> a(n + 2);
vector<LL> diff(n + 1), preSum(n + 2); // diff[i]表示a[i] (其中 l < i < r)进行替换有多少种选择
a[0] = 0; // pad 0, k + 1
a[n + 1] = k + 1;
for (int i = 1; i <= n; ++i)
cin >> a[i];
for (int i = 1; i <= n; ++i)
diff[i] = a[i + 1] - a[i - 1] - 2; // 除去他自己和两边的端点
for (int i = 1; i <= n; ++i)
preSum[i + 1] = preSum[i] + diff[i];
while (q--) {
cin >> l >> r;
auto ans = preSum[r] - preSum[l + 1] // [l+1,r-1]位于内部,则根据diff的前缀和进行计算
+ (k - a[r - 1] - 1) // 比a[r-1]大的,除去a[r],共有k - a[r-1] - 1个
+ a[l + 1] - 2; // a[l+1]和a[l]不能取,故-2
printf("%lld\n", ans);
}
}