题目链接
题目大意
给出一个长度为 (n) 的序列 (a) ,请将 (h) 分成若干段,满足每段数字之和都不超过 (m) ,最小化每段的最大值之和。
解题思路
先考虑 (30) 分做法
定义 (dp[i]) 表示将前 (i) 个数分成若干段的最小代价
那么易推出 (dp[i] = max(dp[i] , dp[j - 1] + max(a[j] sim a[i]))) ,最后答案为 (dp[n])
考虑 $ 100$ 分做法
定义 (l[i]) 为左边第一个大于 (a[i]) 的数的位置 , (l[i])可以用单调栈求出
定义 (get(i)) 为满足 (a[get(i)] + ... + a[i] <= m) 的临界位置 , (get(i))可以用二分来求
那么 (dp_i = min(min(dp_{get(i)} sim dp_{l_i}) , min(dp_{l_i + 1} sim dp_i) + a_i))
可以用线段树维护三个信息 (ans , pre , lazy)
其中 (ans) 表示 (dp[j] , pre) 表示 (dp[j - 1] ,lazy) 表示 (max(a[j] sim a[i])),那么 (ans = pre + lazy)
现从 (a1) 开始遍历,每加入一个 (ai) 会有以下三步
1、(l(i)+1sim i) 区间的 (lazy) 变为 (a_i)((get_i sim l_i) 区间的 (lazy) 不变)
2、第 (i) 个位置的 (ans = dp_{i - 1})
3、(dp_i = min(ans_{get_i} sim ans_i))
所以我们只要用线段树进行区间修改,单点修改,区间查询即可
AC_Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF (0x3f3f3f3f3fll);
const int N = 3e5 + 10;
struct Tree{
int l , r ;
int mi , ans , lazy;
}tree[N << 2];
void push_up(int rt)
{
tree[rt].mi = min(tree[rt << 1].mi , tree[rt << 1 | 1].mi);
tree[rt].ans = min(tree[rt << 1].ans , tree[rt << 1 | 1].ans);
}
void push_down(int rt)
{
if(tree[rt].lazy == INF) return ;
int x = tree[rt].lazy;
tree[rt].lazy = INF;
tree[rt << 1].ans = tree[rt << 1].mi + x;
tree[rt << 1 | 1].ans = tree[rt << 1 | 1].mi + x;
tree[rt << 1 | 1].lazy = x , tree[rt << 1].lazy = x;
}
void build(int l , int r , int rt , int *a)
{
tree[rt].l = l , tree[rt].r = r;
if(l == r)
{
tree[rt].mi = tree[rt].ans = a[l];
tree[rt].lazy = INF;
return ;
}
int mid = l + r >> 1;
build(l , mid , rt << 1 , a);
build(mid + 1 , r , rt << 1 | 1 , a);
push_up(rt);
}
void update(int pos , int val, int rt)
{
int l = tree[rt].l , r = tree[rt].r;
if(l == r)
{
tree[rt].mi = val;
return ;
}
if(tree[rt].lazy != INF) push_down(rt);
int mid = l + r >> 1;
if(pos <= mid) update(pos , val , rt << 1);
else update(pos , val , rt << 1 | 1);
push_up(rt);
}
void update_range(int L , int R , int rt , int val)
{
if(tree[rt].r<L||tree[rt].l>R) return;
int l = tree[rt].l , r = tree[rt].r;
if(L <= l && r <= R)
{
tree[rt].lazy = val;
tree[rt].ans = tree[rt].mi + val;
return;
}
if(tree[rt].lazy != INF) push_down(rt);
int mid = l + r >> 1;
if(L <= mid) update_range(L , R , rt << 1 , val);
if(R > mid) update_range(L , R , rt << 1 | 1 , val);
push_up(rt);
}
int query_min(int L , int R , int rt)
{
int l = tree[rt].l , r = tree[rt].r;
if(L <= l && r <= R)
{
return tree[rt].ans;
}
if(tree[rt].lazy != INF) push_down(rt);
int mid = l + r >> 1;
int ans = INF;
if(L <= mid) ans = min(ans , query_min(L , R , rt << 1));
if(R > mid) ans = min(ans , query_min(L , R , rt << 1 | 1));
return ans;
}
int a[N] , l[N] , dp[N] , sum[N] , n , m;
stack<int>sta;
int get(int i)
{
int l = 0 , r = i , res = 0;
while(l <= r)
{
int mid = l + r >> 1;
if(sum[i] - sum[mid - 1] > m) l = mid + 1;
else r = mid - 1 , res = mid;
}
return max(1LL , res);
}
signed main()
{
for(int i = 0 ; i <= N - 10 ; i ++) dp[i] = INF;
cin >> n >> m;
a[1] = 0 , dp[1] = 0;
build(1 , n + 10 , 1 , dp) , update(1 , 0 , 1);
for(int i = 2 ; i <= n + 1 ; i ++) cin >> a[i] , sum[i] = sum[i - 1] + a[i];
for(int i = 1 ; i <= n + 1 ; i ++)
{
while(sta.size() && a[sta.top()] < a[i]) sta.pop();
if(sta.size()) l[i] = sta.top();
else l[i] = 0;
sta.push(i);
}
for(int i = 2 ; i <= n + 1 ; i ++)
{
update(i , dp[i - 1] , 1);
int x = get(i) , y = l[i];
update_range(y + 1 , i , 1 , a[i]);
dp[i] = query_min(x , i , 1);
}
cout << dp[n + 1] << '
';
return 0;
}