题目:http://acm.hdu.edu.cn/showproblem.php?pid=6606
题意:n个数取前若干数,分成k段,使分到的段和的最大值最小。
思路:要求最大值最小,考虑二分答案。对答案x,设dp[i]表示将前i个数分成k份的方案数,有dp[i] = max(dp[j])+1,满足sum[i]-sum[j]<=x(sum[i]为前缀和)。由于n<=2e5,必然不能O(n^2)做。分析至此需要的操作有查询最大值dp[j]满足sum[j]>=sum[i]-x、单点修改dp[i],建立权值线段树维护dp数组即可。
代码:
#include <bits/stdc++.h>
#define LL long long
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
const int maxn = 2e5+5;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
LL n, t, k, len, Max[maxn<<2], sum[maxn], a[maxn];
vector<LL>vec;
int getid(LL x){return lower_bound(vec.begin(), vec.end(), x)-vec.begin()+1;}
void update(int rt, int l, int r, int pos, int val){
if(l == r) {Max[rt] = val; return ;}
int mid = l+r >> 1;
if(pos <= mid) update(ls, l, mid, pos, val);
else update(rs, mid+1, r, pos, val);
Max[rt] = max(Max[ls], Max[rs]);
}
int query(int rt, int l, int r, int L, int R){
if(L <= l && R >= r) return Max[rt];
int mid = l+r >> 1, res = -inf;
if(L <= mid) res = max(res, query(ls, l, mid, L, R));
if(R > mid) res = max(res, query(rs, mid+1, r, L, R));
return res;
}
bool judge(LL x){
memset(Max, -inf, sizeof Max);
update(1, 1, len, getid(0), 0);
for(int i=1; i<=n; i++){
int pos1 = getid(sum[i]-x), pos2 = getid(sum[i]);
if(pos1 > len) continue;
int tmp = query(1, 1, len, pos1, len) + 1;
if(tmp >= k) return true;
else update(1, 1, len, pos2, tmp);
}return false;
}
int main()
{
scanf("%d", &t);
while(t--){
vec.clear();
scanf("%d%lld", &n, &k);
for(int i=1; i<=n; i++){
scanf("%lld", &a[i]);
sum[i] = sum[i-1] + a[i];
vec.push_back(sum[i]);
} vec.push_back(0);
sort(vec.begin(), vec.end()); vec.erase(unique(vec.begin(), vec.end()), vec.end()); len = vec.size();
LL l = -INF, r = INF, ans;
while(l <= r){
LL mid = l+r >> 1;
if(judge(mid)) ans = mid, r = mid-1;
else l = mid+1;
}
cout << ans << endl;
}
}