# 練題 洛谷P1295 [TJOI2011]書架 (2022.2.17)
###### tags: `競程筆記`
## 題意
https://www.luogu.com.cn/problem/P1295
給出一個長度為 $n$ 的序列 $h$,請將 $h$ 分成若干段,滿足每段數字之和都不超過 $m$,最小化每段的最大值之和。
$n \le 10^5$, $h_i \le m \le 10^9$
## 想法
很容易推出dp式
$dp(i) = \min(dp(j)+\max(h_{j+1}...h_{i})), ~~j=1\cdots i-1$ 且 $sum(h_{j+1}...h_i)\le m$
但直接做是 $O(n^2)$
定義$mx(j)$為 $h_{j+1}$ 到 $h_i$ 的最大值,發現當i遞增時,可以視為$mx(i)$的動態更新,而$dp(i)$就是合法區間內 $dp(j)+mx(j)$ 的最小值。
可以轉化為資結問題:給序列$a,b$,求下列兩種操作:
1. 區間修改$a$的值
2. 求區間內$a_i+b_i$的最小值
這題還要利用$mx$序列必遞減,$dp$序列必遞增,所以比較好維護。
* 操作1:先二分出要「改值」的區間,然後改值打懶標。我用一個vector存「$mx$值」及「該值最左邊出現的位置」的 pair,不然如果是直接紀錄就沒意義了
* 操作2:先利用前綴和,二分出$j$的合法區間,然後就一般線段樹會做的事
* push:兩個子區間的所有 $mx$ 都要被改成某數,因此 $mx+dp$ 的最小值一定出現在 $dp$ 值最小的那個位置,也就是「該區間的最左邊」
* pull:直接繼承兩個子區間的$data$的較小者
:::spoiler AC Code
```cpp=
// Cookie197
// the people who invented competitive programming must be ******* crazy
// why am i still here suffering while i can do something else more valuable?
// WHY???
#pragma GCC optimize("O4,unroll-loops,no-stack-protector")
#pragma GCC optimize("Ofast")
#pragma GCC target("avx,avx2,fma")
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<string>
#include<iomanip>
#include<math.h>
#include<unordered_map>
#include<numeric>
#include<random>
using namespace std;
#define Why_does_competitive_programming_even_exist ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define ll long long
#define pii pair<ll,ll>
#define pdd pair<double ,double>
#define mp make_pair
//#define mod 998244353
#define mod 1000000007
#define endl "\n"
#define inf (ll)(1e18)
#define out(x) cout << #x << " = " << x <<endl;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
ll arr[100005],pre[100005];
ll n,m;
ll dp[100005];
struct node{
ll tag,data;
// tag = a的改值tag data = a+b的min
}st[400005];
void push(ll l,ll r,ll id){
if (!st[id].tag) return;
st[id*2].tag = st[id].tag;
st[id*2+1].tag = st[id].tag;
st[id*2].data = st[id].tag + dp[l]; // 可以醬是因為dp是遞增
ll mid = (l+r)>>1;
st[id*2+1].data = st[id].tag + dp[mid+1];
st[id].tag = 0;
}
void pull(ll l,ll r,ll id){
st[id].data = min(st[id*2].data, st[id*2+1].data);
}
void a_change(ll l,ll r,ll L,ll R,ll id,ll x){
if (R<l || r<L) return;
if (l<=L && R<=r){
st[id].tag = x;
st[id].data = x+dp[L];
return;
}
push(L,R,id);
ll mid = (L+R)>>1;
a_change(l,r,L,mid,id*2,x), a_change(l,r,mid+1,R,id*2+1,x);
pull(L,R,id);
}
ll query(ll l,ll r,ll L,ll R,ll id){
if (R<l || r<L) return 1e9;
if (l<=L && R<=r){
return st[id].data;
}
push(L,R,id);
ll mid = (L+R)>>1;
return min(query(l,r,L,mid,id*2),query(l,r,mid+1,R,id*2+1));
}
vector<pii> mx; // value of mx, starting pos
signed main(){
Why_does_competitive_programming_even_exist;
n=read(); m=read(); n++;
for (ll i=2;i<=n;i++) arr[i]=read();
for (ll i=2;i<=n;i++) pre[i] = pre[i-1]+arr[i];
mx.push_back(mp(arr[2],1));
a_change(1,1,1,n,1,arr[2]);
for (ll i=2;i<=n;i++){
ll left=2,right=i,mid=(left+right)/2;
while(left<right){
mid=(left+right)/2;
if (pre[i] - pre[mid-1] > m) left=mid+1;
else right=mid;
}
dp[i] = query(left-1,i-1,1,n,1);
int posinmx = lower_bound(mx.begin(),mx.end(),mp(arr[i+1],1e9),greater<pii>()) - mx.begin();
int pos = mx [posinmx] .second;
if (posinmx!=(int)mx.size()){
a_change(pos,i,1,n,1,arr[i+1]);
while(1){
if (mx.empty()) break;
if (mx.back().second>=pos) mx.pop_back();
else break;
}
mx.push_back(mp(arr[i+1],pos));
}else{ // 是目前最小的
a_change(i,i,1,n,1,arr[i+1]);
mx.push_back(mp(arr[i+1],i));
}
}
cout<<dp[n]<<endl;
}
:::