D. Blocking Elements

D. Blocking Elements

You are given an array of numbers a1,a2,,an. Your task is to block some elements of the array in order to minimize its cost. Suppose you block the elements with indices 1b1<b2<<bmn. Then the cost of the array is calculated as the maximum of:

  • the sum of the blocked elements, i.e., ab1+ab2++abm.
  • the maximum sum of the segments into which the array is divided when the blocked elements are removed. That is, the maximum sum of the following (m+1) subarrays: [1,b11], [b1+1,b21], [], [bm1+1,bm1], [bm+1,n] (the sum of numbers in a subarray of the form [x,x1] is considered to be 0).

For example, if n=6, the original array is [1,4,5,3,3,2], and you block the elements at positions 2 and 5, then the cost of the array will be the maximum of the sum of the blocked elements (4+3=7) and the sums of the subarrays (1, 5+3=8, 2), which is max(7,1,8,2)=8.

You need to output the minimum cost of the array after blocking.

Input

The first line of the input contains a single integer t (1t30000) — the number of queries.

Each test case consists of two lines. The first line contains an integer n (1n105) — the length of the array a. The second line contains n elements a1,a2,,an (1ai109) — the array a.

It is guaranteed that the sum of n over all test cases does not exceed 105.

Output

For each test case, output a single number — the minimum cost of blocking the array.

Example

input

3
6
1 4 5 3 3 2
5
1 2 3 4 5
6
4 1 6 3 10 7

output

7
5
11

Note

The first test case matches with the array from the statement. To obtain a cost of 7, you need to block the elements at positions 2 and 4. In this case, the cost of the array is calculated as the maximum of:

  • the sum of the blocked elements, which is a2+a4=7.
  • the maximum sum of the segments into which the array is divided when the blocked elements are removed, i.e., the maximum of a1, a3, a5+a6=max(1,5,5)=5.

So the cost is max(7,5)=7.

In the second test case, you can block the elements at positions 1 and 4.

In the third test case, to obtain the answer 11, you can block the elements at positions 2 and 5. There are other ways to get this answer, for example, blocking positions 4 and 6.

 

解题思路

  显然答案具有二段性,即如果存在一种合法方案使得分割点总和以及每个分割段的和都不超过 x,那么大于 x 的值也可以作为一个合法的答案,为此需要二分出最小的 x

  对于二分值 mid 则需要判断是否存在合法的分割方案。由于选择分割点相当于选择总和不超过 mid 的子序列,所以可以尝试 dp 找到使得每个分割段的和都不超过 mid 时分割点总和的最小值是多少,如果不超过 mid 则说明存在分割方案。

  定义 f(i) 表示将前 i 个元素进行分割,且分割点总和以及每个分割段的和都不超过 mid 的所有方案中,分割点总和的最小值。根据第 i 个元素是否为分割点进行状态划分:

  • 如果 ai 是分割点,那么状态转移方程为 f(i)=f(i1)+ai
  • 如果 ai 不是分割点,那么找到前一个分割点 aj,要求满足最后一个分割段的和不超过 mid,即 sisjmid,其中 si=k=1iai 表示前缀和。状态转移方程为 f(i)=minsisjmid{f(j1)+aj}

  其中定义 f(1)=f(0)=0a0=0

  显然第二个部分需要优化,注意到本质就是在 j[0,i1] 中找到所有满足 sjsimidj,然后对 f(j1)+aj 取最小值。所以可以用值域线段树来优化,在每个 si 处存储 f(i1)+ai 并维护最小值,询问就相当于求以 simid 为左端点的后缀的最小值。需要对 sisimid 离散化。

  最后如果 f(n)mid 则说明存在分割方案。

  AC 代码如下,时间复杂度为 O(nlognlogai)

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 2e5 + 10;
const LL INF = 0x3f3f3f3f3f3f3f3f;

int n;
int a[N];
LL s[N];
LL xs[N], sz;
LL f[N];
struct Node {
    int l, r;
    LL mn;
}tr[N * 4];

int find(LL x) {
    int l = 1, r = sz;
    while (l < r) {
        int mid = l + r >> 1;
        if (xs[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}

void build(int u, int l, int r) {
    tr[u] = {l, r, INF};
    if (l != r) {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
    }
}

void modify(int u, int x, LL c) {
    if (tr[u].l == tr[u].r) {
        tr[u].mn = min(tr[u].mn, c);
    }
    else {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, c);
        else modify(u << 1 | 1, x, c);
        tr[u].mn = min(tr[u << 1].mn, tr[u << 1 | 1].mn);
    }
}

LL query(int u, int l, int r) {
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].mn;
    int mid = tr[u].l + tr[u].r >> 1;
    LL ret = INF;
    if (l <= mid) ret = query(u << 1, l, r);
    if (r >= mid + 1) ret = min(ret, query(u << 1 | 1, l, r));
    return ret;
}

bool check(LL mid) {
    sz = 0;
    for (int i = 0; i <= n; i++) {
        xs[++sz] = s[i];
        xs[++sz] = s[i] - mid;
    }
    sort(xs + 1, xs + sz + 1);
    sz = unique(xs + 1, xs + sz + 1) - xs - 1;
    build(1, 1, sz);
    memset(f, 0x3f, n + 10 << 3);
    f[0] = 0;
    for (int i = 1; i <= n; i++) {
        modify(1, find(s[i - 1]), f[max(0, i - 2)] + a[i - 1]);
        f[i] = min(f[i - 1] + a[i], query(1, find(s[i] - mid), sz));
    }
    return f[n] <= mid;
}

void solve() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
        s[i] = s[i - 1] + a[i];
    }
    LL l = 1, r = 1e14;
    while (l < r) {
        LL mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    printf("%lld\n", l);
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        solve();
    }
    
    return 0;
}

  比赛的时候我是用上面的做法过的,还可以用单调队列来优化。注意到 sjsimid 中的 simid,随着 i 的增加 si 也会增加,因此 simid 也会增加,具有单调性。因此可以用单调队列来维护 j[0,i1]f(j1)+aj 的最小值。当枚举到 i,如果对头元素对应的 sj<simid 则弹出。

  AC 代码如下,时间复杂度为 O(nlogai)

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 2e5 + 10;

int n;
int a[N];
LL s[N];
LL f[N];
int q[N], hh, tt;

bool check(LL mid) {
    memset(f, 0x3f, n + 10 << 3);
    f[0] = 0;
    hh = 0, tt = -1;
    for (int i = 1; i <= n; i++) {
        // 把f(j-1)+a[j]加入单调队列中
        while (hh <= tt && f[max(0, q[tt] - 1)] + a[q[tt]] >= f[max(0, i - 2)] + a[i - 1]) {
            tt--;
        }
        q[++tt] = i - 1;    // 队列中存的是下标
        while (hh <= tt && s[q[hh]] < s[i] - mid) {    // 把队头不满足s[j]>=s[i]-mid的元素删除
            hh++;
        }
        if (hh <= tt) f[i] = min(f[i - 1] + a[i], f[max(0, q[hh] - 1)] + a[q[hh]]);
    }
    return f[n] <= mid;
}

void solve() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
        s[i] = s[i - 1] + a[i];
    }
    LL l = 1, r = 1e14;
    while (l < r) {
        LL mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    printf("%lld\n", l);
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        solve();
    }
    
    return 0;
}

 

参考资料

  Codeforces Round #922 (Div. 2) Editorial:https://codeforces.com/blog/entry/125300%E3%80%81

posted @   onlyblues  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2023-01-31 C. Remove the Bracket
Web Analytics
点击右上角即可分享
微信分享提示