\[\texttt{Description} \]
给一个长度为 \(n\) 的序列 \(a\) 。
对于第 \(i\) 秒,我们可以选择若干个位置上的数加上 \(2^{i-1}\) 。
问最少在第几秒的时候,可以使得 \(a_1 \leq a_2\leq...\leq a_n\) 。
\[\texttt{Solution} \]
-
首先不难注意到,对于前 \(i\) 秒,我们可以使得一个数加上 \(x\in[1,2^i-1]\)。
-
也就是说对于前 \(i\) 秒,我们可以让一个数 \(x\) 变成 \([x,x+2^i-1]\) 内的任意一个数。
-
考虑一下这个式子 \(a_1 \leq a_2\leq...\leq a_n\) ,显然,\(a_i\) 越小,则 \(a_{i+1}\) 至 \(a_n\) 越有 " 操作空间 " 。
-
又因为操作只会导致数变大,所以我们显然是不要去动这个 \(a_1\) 。
-
接着考虑一下 \(a_2\) :
- 若 \(a_1 \leq a_2\) ,则已经满足题目要求,为了给后面的数留 " 操作空间 " 所以不要动它。
- 若 \(a_1 > a_2\) ,则二分最小 \(x\) 的满足 \(a_1 \leq a_2 + 2^x-1\) ,用 \(x\) 更新答案,再令 \(a_2 = a_1\) 。
-
以此类推,我们接着考虑 \(a_3,a_4,...,a_n\) ,假设我们现在考虑到第 \(i\) 位:
- 若 \(a_{i-1} \leq a_i\) ,则已经满足题目要求,为了给后面的数留 " 操作空间 " 所以不要动它。
- 若 \(a_{i-1} > a_i\) ,则二分最小 \(x\) 的满足 \(a_{i-1} \leq a_i + 2^x-1\) ,用 \(x\) 更新答案,再令 \(a_i = a_{i-1}\) 。
-
这样考虑完这 \(n\) 位即可求出答案,\(\mathcal{O(n \log \log size)}\),其中 \(size\) 表示的是值域大小。
\[\texttt{Code} \]
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
namespace IO {
static char buf[1 << 20], *fs, *ft;
inline char gc() {
if (fs == ft) {
ft = (fs = buf) + fread(buf, 1, 1 << 20, stdin);
if (fs == ft) return EOF;
}
return *fs ++;
}
#define gc() getchar()
inline int read() {
int x = 0, f = 1; char s = gc();
while (s < '0' || s > '9') {if (s == '-') f = -f; s = gc();}
while (s >= '0' && s <= '9') {x = x * 10 + s - '0'; s = gc();}
return x * f;
}
} using IO :: read;
const int N = 200100;
int n;
long long a[N];
void work() {
n = read();
for (int i = 1; i <= n; i ++)
a[i] = read();
int ans = 0;
for (int i = 2; i <= n; i ++) {
if (a[i - 1] <= a[i]) continue;
int l = 1, r = 33;
while (l < r) {
int mid = (l + r) / 2;
long long delta = (1ll << mid) - 1;
if (a[i - 1] <= a[i] + delta) r = mid; else l = mid + 1;
}
ans = max(ans, l);
a[i] = a[i - 1];
}
printf("%d\n", ans);
}
int main() {
int T = read();
while (T --) work();
return 0;
}
\[\texttt{Thanks} \ \texttt{for} \ \texttt{watching} \]