P1070 [NOIP2009 普及组] 道路游戏
Problem
Solve
此题是求最优解,考虑贪心时会发现这个不满足局部最优->整体最优,故考虑DP
通过输入格式能受到启发,时间可以作为维度之一,所以定义为:
\(f_{i,j}\)第i秒末,机器人在j号工厂能获得的最大金币
因为机器存在时间有上限,所以推的时候枚举本次机器人到底走了多少步,然后从走之前推过来(如图1)
动态转移方程为$$f_{i,j}=max(f_{i-k,j-k}+\sum_{l=0}^{k-1} a_{i-l,j-l}) (k\le p)$$
当然这个仅仅是大部分情况下的版本,有时会从n->1,这种情况将后半部分拆成两个部分即可(如图2)
解为\(max(f_{m,i})(i\le n)\),时间复杂度\(O(nmp)\)
优化
不难发现,我们都在寻找当前时刻近p秒内的最优解,可以使用单调队列
我们给每个对角线建立单调队列,这样就能直接找出最优的位置了,前半部分可以O(1)解决
对于后半部分,我们给每个对角线维护前缀和即可
对角线的编号建议为为i-j,这样可以一一对应,十分方便,具体参照代码,时间复杂度\(O(nm)\)
理论上不加优化的解法是TLE90,但是在洛谷神机+常数优化+O2的情况下能将理论\(10^9\)的计算量压在500ms内,所以代码给出的是理论90+前缀和维护后半部分的写法
Code
#include<bits/stdc++.h> using namespace std; int n,m,p,a[1005][1005],b[1005]; int f[1005][1005],s[2005][1005],maxn[1005]; int fg(int j,int k){ if((j-k+1000*n)%n==0)return n; return (j-k+1000*n)%n; } int fs(int i,int j,int k){ int ret=0; if(j-k<0){ ret+=s[i-j+1000][i]; ret+=s[i-j-n+1000][i-j]-s[i-j-n+1000][i-k]; }else if(j==k){ ret+=s[i-j+1000][i]; }else ret=s[i-j+1000][i]-s[i-j+1000][i-k]; ret-=b[fg(j,k)]; return ret; } int main(){ memset(f,-0x3f,sizeof(f)); memset(maxn,-0x3f,sizeof(maxn)); scanf("%d%d%d",&n,&m,&p); for(int i=1;i<=n;i++)f[0][i]=0; maxn[0]=0; for(int i=2;i<=n;i++){ for(int j=1;j<=m;j++){ scanf("%d",&a[j][i]); } } for(int j=1;j<=m;j++){ scanf("%d",&a[j][1]); } for(int i=1;i<=n;i++){ scanf("%d",&b[i]); } for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ s[i-j+1000][i]=s[i-j+1000][i-1]+a[i][j]; } } for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ for(int k=1;k<=p;k++){ if(i-k<0)break; f[i][j]=max(f[i][j],maxn[i-k]+fs(i,j,k)); } maxn[i]=max(maxn[i],f[i][j]); } } int ans=0; for(int i=1;i<=n;i++){ ans=max(ans,f[m][i]); } cout<<ans; return 0; }
图一
图二
本文作者:一位XXS
本文链接:https://www.cnblogs.com/yiweixxs/p/18607119
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步