我简单说一下我的思路:
先 O(n) 预处理出来前缀和。
在每一次询问的时候:从第一个位置开始,预处理到第2n个位置,如果当前区间的值大于了k,就记录好这个区间,大概就是这样子的。
if (now + a[i] > k)
{
++ cnt;
st[cnt] = lst, ed[cnt] = i - 1;
lst = i, now = 0;
}
然后这样每一个点都被唯一的包含在了一个块中。
在查询的时候,就类似分块。O(n)枚举 i 到 i+n−1 这个区间里面有多少个块,如果开头的散块和最后的散块的权值相加 ≤k就算成一个块。
我在提交的时候WA on #21,有点懵逼,不知道我这个算法是否正确,或者如果不正确哪里可以优化一下的
#include <bits/stdc++.h>
#define int long long
inline int rd(void)
{
int x = 0, f = 1; char ch = getchar();
while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); }
while (isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
return x * f;
}
const int N = 3e6 + 10;
int n, q, a[N], sum[N];
int bel[N], st[N], ed[N];
int pos[N];
auto main() -> signed
{
n = rd(), q = rd();
for (int i = 1; i <= n; i ++ ) {
a[i] = rd();
a[n + i] = a[i];
}
for (int i = 1; i <= 2 * n; i ++ ) {
sum[i] = sum[i - 1] + a[i];
}
while (q -- )
{
int k = rd();
int lst = 1, now = 0, cnt = 0;
for (int i = 1; i <= 2 * n; i ++ ) {
if (now + a[i] > k)
{
++ cnt;
st[cnt] = lst, ed[cnt] = i - 1;
lst = i, now = 0;
}
now += a[i];
}
if (now) {
++ cnt;
st[cnt] = lst, ed[cnt] = 2 * n;
}
for (int i = 1; i <= cnt; i ++ ) {
for (int j = st[i]; j <= ed[i]; j ++ )
bel[j] = i;
}
int min = 0x3f3f3f3f;
for (int i = 1; i <= n; i ++ ) {
int l = i, r = i + n - 1;
int bell = bel[l] + 1, belr = bel[r] - 1;
int ans = std::max(belr - bell + 1, 0ll);
if (sum[ed[bel[l]]] - sum[l - 1] + sum[r] - sum[st[bel[r]] - 1] <= k)
ans ++ ;
else ans += 2;
min = std::min(ans, min);
}
std::cout << min << std::endl;
}
return 0;
}