zoj (單點更新區間查詢:線段樹)
阿新 • • 發佈:2018-12-24
題意:有n天,每天都可以買西瓜,每個西瓜的價格是ai,每個西瓜能吃bi天。問這n天每天都有西瓜吃的最小的代價是多少?如果你在第i天買了一個西瓜,那麼之前買的西瓜就要全部扔掉,才能開始吃新的西瓜。
定義dp[i]為到i天為止,每天都有西瓜吃的最小代價,那麼狀態轉移方程就是:dp[i]=min(dp[i],dp[i-k-1]+a[i-k])。這樣時間複雜度會達到O(n^2),所以要優化。在遞推的過程中,我們達到第i-k天之後,去更新第i-k+1天到第i天的代價。如果我們能一次性更新這些範圍,就可以將複雜度降下來,優化的方法就是線段樹。
轉化的方法還是挺巧妙的。對於第i-k天,我們只去更新第i天這個點,然後在查詢的時候,我們查詢的是第i天到第n天裡的最小值,因為如果我們得到的是在第i天到 第n天的某一個最小值,那麼這個最小值一定是在第i天或第i-1天前更新到的。可以仔細想想。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #define N 50005 using namespace std; const long long Max=pow(10,10); int n; long long a[N]; long long b[N]; long long dp[N]; long long tree[N*4]; void built(int L,int R,int fa) { tree[fa]=Max; if(L==R) return ; int mid=(L+R)/2; built(L,mid,fa*2); built(mid+1,R,fa*2+1); } int idex; long long val; void uptate(int L,int R,int fa) { if(L==R) { tree[fa]=min(val,tree[fa]); return; } int mid=(L+R)/2; if(idex<=mid) uptate(L,mid,fa*2); else uptate(mid+1,R,fa*2+1); tree[fa]=min(tree[fa*2],tree[2*fa+1]); } int LL,RR; long long query(int L,int R,int fa) { if(LL<=L&&RR>=R) return tree[fa]; int mid=(R+L)/2; long long t1=Max; long long t2=Max; if(LL<=mid) t1=query(L,mid,fa*2); if(RR>mid) t2=query(mid+1,R,fa*2+1); return min(t1,t2); } int main() { while(scanf("%d",&n)!=EOF) { memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); for(int j=1;j<=n;j++) scanf("%lld",&b[j]); built(1,n,1); dp[0]=0; for(int i=1;i<=n;i++) { int last=i+b[i]-1; last=min(n,last); idex=last; val=dp[i-1]+a[i]; uptate(1,n,1); LL=i; RR=n; dp[i]=query(1,n,1); } printf("%lld\n",dp[n]); } return 0; }