CF372C Watching Fireworks is Fun(dp+双端队列)

题意(转):有一个街道放烟花,街道分成 N 块(1 <= N <= 150000),相邻块距离为1, 你移动速度是 d 。烟花放 M 次 (1 <= M <= 300), 给出每次放烟花的时间地点,Happy 值得计算公式 b[i] - abs(a[i] - x) ,(b[i] 第i次放烟花的基准happy, a[i] 是放烟花的地点,x 是你所在的地点) 。求 Happy 值总和最大是多少。

http://codeforces.com/problemset/problem/372/C

dp[i][j]表示当前放到了第I支烟花并且放这支烟花的时候他站在j点看。推出状态转移方程:

dp[ i ] [ j ] =max(dp[ i - 1] [ k ]) + b[ i ] - | a[ i ] - j | ,其中  max(1,j-t*d)<=k<=min(n,j+t*d) 。不过这样会爆数组和时间。怎么办?

发现大神是用单调队列作辅助的。dp[ i ] [ j ] =?????+ b[ i ] - | a[ i ] - j | ,只要?????最大那么dp[i][j]就是最优解。由于相邻的j之间能取到的上一个状态k的范围[j-d,j+d]会有重叠部分,因此可以用双端队列进行优化。

#include<bits/stdc++.h>
#define mp make_pair
#define aa first
#define bb second
#define ll long long
#define pii pair<long long,long long>
using namespace std;
deque<pii> q;  
ll dp[2][150005];
int main(){
	ll m,n,k,a,b,t;
	cin>>n>>m>>k;
	ll tt=0;
	int s1=0,s2=1;
	for(int ii=1;ii<=m;++ii){
		q.clear();
		cin>>a>>b>>t;
		ll d=k*(t-tt); tt=t;
		ll i;
		for(i=1;abs(i-1)<=d&&i<=n;++i){
			while(!q.empty()&&q.back().aa<=dp[s1][i]) //维护的是从大到小的序列 
				q.pop_back(); 
			q.push_back(mp(dp[s1][i],i));
		}
		for(int j=1;j<=n;++j){
			while(abs(q.front().bb-j)>d)
				q.pop_front();
			dp[s2][j]=q.front().aa+b-abs(a-j);
			if(i<=n){
				while(!q.empty()&&q.back().aa<=dp[s1][i]) //维护的是从大到小的序列 
					q.pop_back(); 
				q.push_back(mp(dp[s1][i],i));
				i++;
			}
		}
		swap(s1,s2);
	}
	ll g=dp[s1][1];
	for(int i=2;i<=n;++i){
		if(dp[s1][i]>g)
			g=dp[s1][i];
	}
	cout<<g<<endl;
	return 0;
}


你可能感兴趣的:(CF372C Watching Fireworks is Fun(dp+双端队列))