# 練題 洛谷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; } :::