小歐米伽的仙人掌
阿新 • • 發佈:2021-11-10
小歐米伽的仙人掌
題面:
對於一段區間 \([l,r]\) 如果 \([l,r]\) 不滿足題目所給條件,那麼 \([l+1,r]\) 一定不滿足題目所給條件。那麼我們容易想到用雙指標來解決這個問題。
但是我們發現,當左指標向右移動時,我們不好直接減去左指標所在位置的貢獻。這裡有一種常見的處理方法。我們維護兩個棧。假設當前左端點為 \(l\),右端點為 \(r\),\(mid\) 為 \([l,r]\) 中任意一個點,然後一個棧自棧頂到棧底維護的是 \([i,mid],i\in[l,mid]\) 這個區間的 dp 值。另一個棧自棧頂到棧底維護的是 \([mid+1,i],i\in[mid+1,r]\)
查詢的時候我們只要合併兩邊棧頂的 dp 值就行,複雜度為 \(O(m)\)。
然後每個位置的元素分別會被左邊那個棧和右邊那個棧壓入和彈出一次,每壓入一次的複雜度是 \(O(m)\)。
總時間複雜度為 \(O(m\times n)\)。
程式碼如下:
路漫漫其修遠兮,吾將上下而求索。#include<bits/stdc++.h> using namespace std; const int MAXN = 1e4+5; int n,W,K,A[MAXN],B[MAXN],ans,f[MAXN][5005],l,r; bool check(int x) { if(r==l) return f[x][W]<=K; for(int i=0;i<=W;++i) if(f[x][i]+f[r][W-i]<=K) return true; return false; } int main() { freopen("cactus.in","r",stdin); freopen("cactus.out","w",stdout); scanf("%d %d %d",&n,&W,&K); for(int i=1;i<=n;++i) scanf("%d %d",&A[i],&B[i]); memset(f,0x3f,sizeof f); for(int i=1;i<=n;++i) f[i][0]=0; l=1,r=1;ans=n+1; f[1][A[1]]=B[1]; for(int i=1;i<=n;++i) { while(!check(i)&&r+1<=n) { ++r; if(r==l+1) f[r][A[r]]=B[r]; else { for(int j=W;j>=0;--j) { f[r][j]=f[r-1][j]; if(j>=A[r]) f[r][j]=min(f[r][j],f[r-1][j-A[r]]+B[r]); } } } if(check(i)) ans=min(ans,r-i+1); if(i==l) { memset(f[r],0x3f,sizeof f[r]); f[r][0]=0;f[r][A[r]]=B[r]; for(int j=r-1;j>l;--j) { for(int k=W;k>=0;--k) { f[j][k]=f[j+1][k]; if(k>=A[j]) f[j][k]=min(f[j+1][k-A[j]]+B[j],f[j][k]); } } l=r; } } printf("%d\n",ans>n?-1:ans); return 0; }