P1295 [TJOI2011]书架
题目链接
题目大意
给出一个长度为 \(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)+1\sim 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] << '\n';
return 0;
}