luogu P4056 [JSOI2009]火星藏宝图
题面传送门
显然这道题有定义\(dp_i\)表示到\(i\)点的最大价值,\(dp\)式\(dp_{i}=\max\limits_{j=1}^{i-1}{dp_j-(x_i-x_j)^2-(y_i-y_j)^2+w_i}\)这样的\(dp\)是\(O(n^2)\)的
考虑怎么优化,显然一列中只有最下面的列转移更优,这样复杂度变成\(O(nm)\)
设\(g_i\)为当前\(i\)列能转移的最优答案。\(now\)为当前行,\(dis(i,j)=(i-j)^2\)则有\(dp_i=dp_{g_j}+(i-j)^2+dis(now,g_j)+w_i\)
看上去可以斜优。
设\(k<j\)且\(k\)劣于\(j\),有\(dp_{g_j}-(i-j)^2-dis(now,g_j)>dp_{g_k}-(i-k)^2-dis(now,g_k)\)
化简,得到\(dp_{g_j}-j^2+2ij-dis(now,g_j)>dp_{g_k}-k^2+2ik-dis(now,g_k)\)
\(dp_{g_j}-dp_{g_k}-j^2+k^2-dis(now,g_j)+dis(now,g_k)>2i(k-j)\)
则\(\frac{dp_{g_j}-dp_{g_k}-j^2+k^2-dis(now,g_j)+dis(now,g_k)}{k-j}<2i\)
就可以维护单调上升的队列了。
代码实现:
#include<cstdio>
#define X(a) (dp[g[a]][a]-a*a-dis(z,g[a]))
#define Y(a) (a)
using namespace std;
int n,m,k,x,y,z,w[1039][1039],dp[1039][1039],g[1039],q[1039],head,tail;
inline int dis(int x,int y){return (x-y)*(x-y);}
inline double slope(int x,int y,int z){return (Y(y)-Y(x)==0)?-1e9:(X(x)-X(y))*1.0/(Y(y)-Y(x));}
int main(){
//freopen("1.in","r",stdin);
register int i,j,k;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
scanf("%d%d%d",&x,&y,&z);w[x][y]=z;
}
dp[1][1]=w[1][1];g[1]=1;w[1][1]=0;
for(i=1;i<=m;i++){
head=1;tail=0;
for(j=1;j<=m;j++){
if(g[j]){
while(head<tail&&slope(q[tail-1],q[tail],i)>slope(q[tail],j,i))tail--;
q[++tail]=j;
}
if(w[i][j]){
while(head<tail&&slope(q[head],q[head+1],i)<2*j) head++;
k=q[head];dp[i][j]=dp[g[k]][k]-dis(j,k)-dis(i,g[k])+w[i][j];g[j]=i;
while(head<tail&&slope(q[tail-1],q[tail],i)>slope(q[tail],j,i))tail--;
q[++tail]=j;
}
// printf("%d ",dp[i][j]);
}
// putchar('\n');
}
printf("%d\n",dp[m][m]);
}