HDU 6606 Distribution of books 二分答案+权值线段树优化dp

原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=6606

题意

有n本书,你需要把它分成k组,每组必须有一个,而且分组必须是连续的,最后多余的书可以丢掉。每组书的权值和为 s u m i sum_{i} sumi,现在我们求
m a x ( s u m 1.. s u m k ) 最 小 max(sum1..sumk)最小 max(sum1..sumk)

分析

一般求最大值最小或最小值最大的问题都是用二分答案,所以我们往这方面去想。直接设mid为每组的最大值,那么我们则用这个标准取划分组,最后如果分组数大于k,就是符合要求的。

然后考虑怎么求分组的数量,我们设dp[i]代表前i个数最多可以分成的组数,然后转移方程可以很快写出
d p [ i ] = m a x ( d p [ i ] , d p [ j ] + 1 ) [ s u m [ i ] − s u m [ j ] < = m i d ] dp[i]=max(dp[i], dp[j]+1)[sum[i]-sum[j]<=mid] dp[i]=max(dp[i],dp[j]+1)[sum[i]sum[j]<=mid]

如果用朴素方法遍历,那么复杂度是O(N^2)的,所以这时候要套个数据结构维护一下,转化一下思路,我们要找当前可以转移的状态中最大的,那么就是 s u m [ j ] ∈ [ s u m [ i ] − m i d , i n f ] sum[j]∈[sum[i]-mid,inf] sum[j][sum[i]mid,inf],因此我们建一棵权值线段树去维护区间最大值就可以了,数据范围很大可以先离散化。

Code

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define re register
typedef long long ll;
typedef pair<ll, ll> PII;
typedef unsigned long long ull;
const int N = 2e5 + 10, M = 1e6 + 5, INF = 0x3f3f3f3f;
const int MOD = 1e9+7;
struct node {
    int l, r;
    int maxx;
}t[N<<2];
ll sum[N], a[N];
int dp[N];
vector<ll> ve;
void push_up(int u) {
    t[u].maxx = max(t[u<<1].maxx, t[u<<1|1].maxx);
}
void build(int u, int l, int r) {
    t[u].l = l, t[u].r = r, t[u].maxx = 0;
    if (l == r) return;
    int mid = (l + r) >> 1;
    build(u<<1, l, mid);
    build(u<<1|1, mid+1, r);
    push_up(u);
}
void modify(int u, int pos, int val) {
    if (t[u].l == t[u].r) {
        t[u].maxx = max(t[u].maxx, val);
        return;
    }
    int mid = (t[u].l + t[u].r) >> 1;
    if (pos <= mid) modify(u<<1, pos, val);
    else modify(u<<1|1, pos, val);
    push_up(u);
}

int query(int u, int ql, int qr) {
    if (ql <= t[u].l && qr >= t[u].r) return t[u].maxx;
    int mid = (t[u].l + t[u].r) >> 1;
    int Max = 0;
    if (ql <= mid) Max = max(query(u<<1, ql, qr), Max);
    if (qr > mid) Max = max(query(u<<1|1, ql, qr), Max);
    return Max;
}
int n, m;
bool check(ll mid) {
    build(1, 1, ve.size());
    for (int i = 1; i <= n; i++) {
        int pos = lower_bound(ve.begin(), ve.end(), sum[i] - mid) - ve.begin() + 1;
        int now = lower_bound(ve.begin(), ve.end(), sum[i]) - ve.begin() + 1;
        int Max = query(1, pos, ve.size());
        if (!Max) {
            if (sum[i] > mid) dp[i] = 0;
            else dp[i] = 1;
        } else {
            dp[i] = Max + 1;
        }
        modify(1, now, dp[i]);
    }
    return query(1, 1, ve.size()) >= m;
}
void solve() {
    int T; cin >> T; while (T--) {
        cin >> n >> m; ve.clear();
        for (int i = 1; i <= n; i++) cin >> a[i], sum[i] = sum[i-1] + a[i], ve.push_back(sum[i]);
        sort(ve.begin(), ve.end());
        ve.erase(unique(ve.begin(), ve.end()), ve.end());
        ll l = -1e15, r = 1e15;
        while (l <= r) {
            ll mid = (l + r) >> 1;
            if (check(mid)) r = mid - 1;
            else l = mid + 1;
        }
        cout << l << endl;
    }
}

signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("input", "r", stdin);
    freopen("output", "w", stdout);
#endif
    solve();
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值