1. 程式人生 > 其它 >【ybtoj】【單調佇列】燃放煙火

【ybtoj】【單調佇列】燃放煙火

題意

前言

學習一個新的演算法,總是要仔細考慮一些深入的問題

題解

首先清楚單調佇列是一個主要用來優化 dp 的演算法,所以先想出 dp 轉移式子,再通過單調佇列優化掉一維的複雜度才是正常的開啟方式
迴歸本題,很容易想到設計\(dp(i,j)\)表示前\(i\)個煙花的時候在第\(j\)個位置能獲得的最大幸福值
轉移也比較顯然,再列舉一維\(k\)表示第\(i-1\)個煙火的時候在什麼位置,就有\(dp(i,j)=max \{dp(i-1,k)\} +b_i-abs(a_i-j);\)
因為是一個區間單調地向右移動同時維護最大值,所以\(k\)這一維就可以單調佇列優化了
需要注意的是,\(j\)

的迴圈需要順序和倒序分別列舉一次,因為要考慮到從\(j\)的左邊和右邊走到\(j\)的兩種情況
由於有負值,dp 陣列需要稍微特殊處理一下

程式碼

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 1.5e5+10,M = 305;
inline ll read()
{
	ll ret=0;char ch=' ',c=getchar();
	while(!(c>='0'&&c<='9')) ch=c,c=getchar();
	while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
	return ch=='-'?-ret:ret;
}
struct node
{
	int a,b,t;
	inline bool operator < (const node& oth) const {return t<oth.t;}
}f[M];
int dp[M][N];
int n,m,d,q[N];
int main()
{
	n=read(),m=read(),d=read();
	for(int i=1;i<=m;i++) f[i].a=read(),f[i].b=read(),f[i].t=read();
	//sort(f+1,f+m+1); 輸入保證t不下降 
	//memset(dp,-0x3f,sizeof(dp));
	//for(int i=1;i<=n;i++) dp[m][i]=-INF;
	for(int i=1;i<=m;i++)
	{
		int l=1,r=0;	
		for(int j=1;j<=n;j++)	
		{
			while(r>=l&&j-q[l]>(f[i].t-f[i-1].t)*d) l++;
			while(r>=l&&dp[i-1][q[r]]<=dp[i-1][j]) r--;
			q[++r]=j;
			if(r>=l) dp[i][j]=dp[i-1][q[l]]+f[i].b-abs(f[i].a-j);
			//printf("dp:%d update\n",dp[i-1][q[l]]+f[i].b-abs(f[i].a-j));
		}
		l=1,r=0;
		for(int j=n;j>=1;j--)	
		{
			while(r>=l&&q[l]-j>(f[i].t-f[i-1].t)*d) l++;
			while(r>=l&&dp[i-1][q[r]]<=dp[i-1][j]) r--;
			q[++r]=j;
			if(r>=l) dp[i][j]=max(dp[i][j],dp[i-1][q[l]]+f[i].b-abs(f[i].a-j));
			//printf("dp:%d update\n",dp[i-1][q[l]]+f[i].b-abs(f[i].a-j));
		}
		//printf("#%d:(%d,%d)\n",i,l,r);
	}
	int res=-INF;
	for(int i=1;i<=n;i++) 
		res=max(res,dp[m][i]);
	printf("%d",res);
	return 0;
}