Watching Fireworks is Fun -單調佇列優化DP
阿新 • • 發佈:2018-12-16
- 題意:一個城鎮有n個區域,從左到右1-n,每個區域之間距離1個單位距離。節日中有m個煙火要放,給定放的地點a[ i ]、時間t[ i ] ,如果你當時在區域x,那麼你可以獲得b[ i ] - | a[ i ] - x |的happiness 。你每個單位時間可以移動不超過d個單位距離,你的初始位置是任意的,求你通過移動能獲取到的最大的happiness值。
- 思路: 首先設dp[i][ j ]為到放第i個煙花的時候站在j的位置可以獲得的最大happiness。那麼我們可以很容易寫出轉移方程:
- dp[ i ] [ j ] =max(dp[ i - 1] [ k ]) + b[ i ] - | a[ i ] - j | ,其中 max(1,j-t*d)<=k<=min(n,j+t*d) 。由於是求一段區間的最小值,我們可以想到用單調佇列維護,維護一個單調減的佇列,並且需要滾動陣列優化。單調佇列解決的是max(dp[ i - 1] [ k ])如何取出一個對當前j合法的k,所以現在是每個i也就是每個fire都有一個對應自己的佇列,並且位置不斷後移j可能合法的k也隨之往後移,如果新來的合法的比佇列中的happiness大了那麼佇列中原有的值就成為無用的了可以出隊了。但是如果新來的happine小也得入隊因為對於現在與後面的j未必能夠到達前面happin更大的k所以這個小的有可能是對於某個j來說合法的k中最大的。這也就是單調佇列特性,更優可以把之前的無效的踢掉但是不更優也有可能是答案必須入隊。去最值得時候直接從隊首訪問即可並且注意k的有效性。
-
#include<bits/stdc++.h> using namespace std; #define ll long long #define maxn 156666 struct node { ll a,b,t; } fire[333]; bool cmp(node c,node d) { return c.t<d.t; } ll n,m,d,pretime,gd,dt,k; ll dp[2][maxn],que[maxn]; int main() { scanf("%lld%lld%lld",&n,&m,&d); for(int i=0; i<m; i++) scanf("%lld%lld%lld",&fire[i].a,&fire[i].b,&fire[i].t); sort(fire,fire+m,cmp); memset(dp,0,sizeof(dp)); pretime=fire[0].t; for(int i=0; i<m; i++) { int head=1,tail=0; k=1; if(pretime==fire[i].t) for(int j=1; j<=n; j++) dp[gd][j]=dp[gd^1][j]+fire[i].b-abs(fire[i].a-j); else { dt=fire[i].t-pretime; pretime=fire[i].t; for(int j=1; j<=n; j++) { while(k<=n&&k<=j+d*dt) { while(head<=tail&&dp[gd^1][k]>dp[gd^1][que[tail]]) tail--; que[++tail]=k++; } while(head<=tail&&j-dt*d>que[head]) head++; dp[gd][j]=dp[gd^1][que[head]]+fire[i].b-abs(fire[i].a-j); } } gd^=1; } ll ans=-1e17; for(int i=1; i<=n; i++) ans=max(ans,dp[gd^1][i]); printf("%lld\n",ans); return 0; }