题解:P12646 [KOI 2024 Round 1] 升序 & P12642 [KOI 2024 Round 1] 加倍
首先先来看一下这道题的弱化版:P12642 [KOI 2024 Round 1] 加倍。
考虑对于每个
我们发现这个
所以我们要对
但还没完,我们会发现一个问题:对于原本就符合条件的
所以我们还要考虑处理出一个大小为负的
然后考虑如何求答案,我们要先用前缀和“还原”
还有一个细节:可能会出现某个
void solve()
{
cin >> n;
for(int i = 1; i <= n; i ++){
cin >> a[i];
}
for(int i = 1; i <= n - 1; i ++){
if(a[i] > a[i + 1]){
int t = a[i + 1];
while(a[i] > t) t *= 2, c[i] ++;
}
else{
int x = 0, t = a[i];
while(t * 2 <= a[i + 1]){
t *= 2, x ++;
}
c[i] = -x;
}
s[i] = max(0LL, s[i - 1] + c[i]);
}
int ans = 0;
for(int i = 1; i <= n; i ++){
ans += s[i];
}
cout << ans << '\n';
}
下面是加强版(本题)。
还是接着弱化版思路中的
或
和
解释一下第一步:根据初始公式可知
显然只要预处理出
注:在计算时请搞清楚左右端点的包含关系,以及我们当前的计算公式的针对情况、当前算的
c 数组的下标范围等边界情况。在我的代码里会注明。
然后看正常情况。我们的瓶颈就在于和
考虑预处理出对于所有的
但时间复杂度呢?或者说, 对于每个询问的
时间复杂度
#include <bits/stdc++.h>
#define int long long // ll!!!
using namespace std;
const int maxn = 3e5 + 7;
int n, q, a[maxn], c[maxn], p[maxn], s[maxn], s2[maxn];
stack<int> st;
void solve()
{
cin >> n >> q;
for(int i = 1; i <= n; i ++){
cin >> a[i];
}
// 统计c数组
for(int i = 1; i <= n - 1; i ++){
if(a[i] > a[i + 1]){
int t = a[i + 1];
while(a[i] > t) t *= 2, c[i] ++;
}
else{
int x = 0, t = a[i];
while(t * 2 <= a[i + 1]){
t *= 2, x ++;
}
c[i] = -x;
}
}
// 预处理前缀和
for(int i = 1; i <= n; i ++){
s[i] = s[i - 1] + c[i];
s2[i] = s2[i - 1] + i * c[i];
}
// 预处理p[i]
for(int i = n; i >= 0; i --){
while(!st.empty() && s[st.top()] >= s[i]) st.pop(); // 找右边第一个小于s[i]的元素
p[i] = !st.empty()? st.top() : n + 1; // 如果没有,就赋值n+1
st.push(i);
}
auto sum = [&](int l, int r){ // 快速计算非零的区间前缀和
return (r + 1) * (s[r] - s[l - 1]) - (s2[r] - s2[l - 1]);
};
while(q --){
int l, r; cin >> l >> r;
if(l == r){
cout << 0 << '\n'; continue;
}
int ans = 0;
int i = l - 1; // 我们默认i是前缀和<0的位置,也就是i+1才是前缀和非0的位置。所以注意i从l-1开始。
while(i <= r - 1){ // 有效的范围应该是l ~ r-1,因为c[r]指的是位置r对r+1的操作次数,不计入在内
ans += sum(i + 1, min(p[i] - 1, r - 1));
i = p[i];
}
cout << ans << '\n';
}
}
signed main()
{
ios :: sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
solve();
return 0;
}