单调队列DP->斜率DP

蒟蒻题单

瑰丽华尔兹

考虑到知识点是单调队列,考虑怎么使用单调队列
首先说明一点,小天使可以选择当前时刻钢琴是否移动(并非一次就要一段时间)
考虑DP方程,由于每次只能走一个方向,选择不了,其实就相当于一个一维的DP了
以往上(北)为例(t为第t段时间)

\[f[t][i][j]=max_{f[t-1][i'][j]+(i-i')} \]

\[变形为 \]

\[f[t][i][j]=max_{f[t-1][i'][j]-i'}+i \]

然后把\(f[t-1][i'][j]-i'\) 扔进单调队列里就可以
然后代码没事找事用了提前声明,不太好看

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
ll xx,yy,n,m,t,f[300][300],dx[5]={0,-1,1,0,0},dy[5]={0,0,0,-1,1},ans;
char c[300][300];

struct dequeue{
	ll pos,ff;
}q[100001];

void dp(ll x,ll y,ll d,ll len);

int main(){
	memset(f,-0x3f,sizeof f);
	scanf("%lld%lld%lld%lld%lld",&n,&m,&xx,&yy,&t);
	for(ll i=1;i<=n;i++){
		char dc=getchar();
		for(ll j=1;j<=m;j++){
			c[i][j]=getchar();
		}
	}
	f[xx][yy]=0;
	while(t--){
		ll T,S,D,L;
		scanf("%lld%lld%lld",&S,&T,&D);
		L=(T-S+1);
		if(D==1)for(ll i=1;i<=m;i++)dp(n,i,D,L);
		if(D==2)for(ll i=1;i<=m;i++)dp(1,i,D,L);
		if(D==3)for(ll i=1;i<=n;i++)dp(i,m,D,L);
		if(D==4)for(ll i=1;i<=n;i++)dp(i,1,D,L);
	}
	cout<<ans;
}

void dp(ll x,ll y,ll d,ll len){
	ll l=1,r=0;
	for(ll i=1;x<=n&&y<=m&&x>=1&&y>=1;x+=dx[d],y+=dy[d],i++){
		if(c[x][y]=='x')l=1,r=0;
		else {
			while(l<=r&&q[r].ff+i-q[r].pos<f[x][y])r--;
			q[++r]=dequeue{i,f[x][y]};
			while(i-q[l].pos>len)l++;
			f[x][y]=q[l].ff+i-q[l].pos;
			ans=max(ans,f[x][y]);
		}
	}
}

很好,咕了一年,让我们继续

火星藏宝图

题目
首先,\(n^2\)的做法不难想
暴力枚举现在的点,然后枚举决策点 \(f_i=min(f_j-dis_{ij}+w_{ij})\)
然后考虑优化
这里有个有意思的贪心:
art if abcd
考虑题意,\(A C\)点间距离为\(c^2=(a+b)^2+d^2\)稍作计算就会发现直接走\(A C\)不如顺便去一下\(B\)
所以对于每一列,最下面的点必定是最优的(不要忘了\(w\)大于零)
用一个\(st\)数组记录下每一列的最优点在第几排,设\(f_{ij}\)表示走到第\((i,j)\)点的最大收获

\[f[i][j]=min(f[st[k]][k]-dis(i,j,st[k],k)+w[i][j]) \]

时间复杂度变成了\(m^3\)
意识到可以用下单调队列优化一下
先无视掉第一维,设\(i\)为当前列
设:

\[dis_{j}=(i-st[j]) \]

\[f[i]=min(f[j]-dis[j]-(i-j)^2)+w[i] \]

若第\(j\)列优于第\(k\)

\[f[j->i]>f[k->i] \]

\[f[j]-dis[j]-i^2+2*ij-j^2>f[k]-dis[k]-i^2+2*ik-k^2 \]

\[f[j]-f[k]-dis[j]+dis[k]-j^2+k^2>2i*(k-j) \]

\[\frac{f[j]-f[k]-dis[j]+dis[k]-j^2+k^2}{2*(k-j)}>i \]

显然是斜率\(DP\)格式,后面就直接套"模板"

#include<iostream> 
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define ld long double 
using namespace std;
const ld eps=1e-6;
const ll M=2100;
ll n,m,f[M][M],st[M],w[M][M],q[M],l,r,dis[M];

ll disc(ll x,ll y){
	return (x-y)*(x-y);
}

ld K(ll x,ll y){
	if(x!=y)return (f[st[x]][x]-dis[x]-x*x-f[st[y]][y]+dis[y]+y*y)/(y-x)/2.0;
	return -1e9;//这点不要忘记
}

int main(){
	memset(f,-0x3f,sizeof(f));
	scanf("%lld%lld",&n,&m);
	for(ll i=1,a1,a2,a3;i<=n;i++){
		scanf("%lld%lld%lld",&a1,&a2,&a3);
		w[a1][a2]=a3;
	}
	f[1][1]=w[1][1],st[1]=1;
	w[1][1]=0;
	for(ll i=1;i<=m;i++){
		l=1,r=0;
		for(ll j=1;j<=m;j++){
			dis[j]=(st[j]!=0)*disc(st[j],i);
		}
		for(ll j=1;j<=m;j++){
			if(st[j]){
				while(l<r&&K(q[r-1],q[r])>K(q[r],j))r--;
				q[++r]=j;
			}
			if(w[i][j]){
				while(l<r&&K(q[l],q[l+1])<j)l++;
				f[i][j]=f[st[q[l]]][q[l]]-dis[q[l]]-disc(j,q[l])+w[i][j];
				st[j]=i,dis[j]=0;//不要忘记dis赋为零
				while(l<r&&K(q[r-1],q[r])>K(q[r],j))r--;
				q[++r]=j;
			}
		}
	}
	cout<<f[m][m];
}
posted @ 2020-09-28 16:57  蒟蒻丁  阅读(74)  评论(0编辑  收藏  举报