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;
}
图一
图二