这是道十分神奇的dp...
我们以 表示第
时刻所获的最大金币数,不难想到,可以采用刷表法,尝试在每一个
时买一个新的,枚举它的起始位置和步数来更新后面的
值,但那样的复杂度就是
的,理论上过不了。
于是我们考虑优化,要删掉枚举步数的那一维,但首先我们需要改变更新的方式,改为由前面推后面的模式。于是有:
其中 是购买机器人的花费,
表示此时购买的机器人的终点,
表示它走的步数。
关键是表示价值的前缀和,表示‘过
的那一条\形对角线的在当前点的前缀和’,计算方式比较怪(可以继续往下看):
inline int back(int x,int t){
return ((x-t)%n+n)%n;
}
for(int i=0;i<n;i++){
for(int j=1;j<=m;j++)cin>>g[i][j];
}
for(int j=2;j<=m;j++){//注意!顺序不能变
for(int i=0;i<n;i++)
g[i][j]+=g[back(i,1)][j-1];
}
由于走一步时刻和位置都会增加1,机器人一段行程在时间-位置的二维坐标里是几条45度倾斜的\形斜线(因为有环),根据对角线,斜过来计算 刚好可以覆盖每一段行程。而且这样解决了环的问题:更新时斜线碰到下边界时会自动换到最上面,每个
都表示唯一的状态,调用时运用back就可以一步到位了。
ps:这里顺序如果行在外面,本该因为环形连起来的对角线会连不起来导致wa(上图左下角'3'和右侧'2 4 6',同位置的前缀和应如中图'3 5 9 15',下图为‘3 2 6 12’):

铺垫了这么多,我们终于可以开始优化了:把转移方程中带k的提出来,设:
则:
由于 的范围是
,可以把它丢到n个单调队列里,每条独立的对角线开一个,就可以通过了。
还有些细节看代码:
#include<bits/stdc++.h>
#define N 1009
#define M 50009
#define INF 0x3f3f3f3f
#define mod 998244353
using namespace std;
typedef long long ll;
typedef long double ldb;
typedef pair<int,int> pii;
int n,m,p;
int dp[N],g[N][N],a[N];
struct node{
int q[N],head=1,tail=0,id[N];
}Q[N];
inline int back(int x,int t){
return ((x-t)%n+n)%n;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>p;
for(int i=0;i<n;i++){
for(int j=1;j<=m;j++)cin>>g[i][j];
}
for(int j=2;j<=m;j++){
for(int i=0;i<n;i++)g[i][j]+=g[back(i,1)][j-1];
}
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n;i++){
Q[back(i,-1)].q[++Q[back(i,-1)].tail]=-a[i];//j=0,设置每个时间和位置对应的单调队列,此时i对应i+1号单调队列
Q[back(i,-1)].id[Q[back(i,-1)].tail]=0;//初始化,开始走之前买好机器人
}
for(int i=1;i<=m;i++){
dp[i]=-INF;
for(int j=0;j<n;j++){//一组(i,j),相当于i+1走了j步,所以倒退回去找属于他们的单调队列
dp[i]=max(dp[i],Q[back(j,i-1)].q[Q[back(j,i-1)].head]+g[back(j,1)][i]);
}
for(int j=0;j<n;j++){
while(Q[back(j,i-1)].head<=Q[back(j,i-1)].tail&&Q[back(j,i-1)].id[Q[back(j,i-1)].head]<=i-p)Q[back(j,i-1)].head++;//弹出过时元素
while(Q[back(j,i-1)].head<=Q[back(j,i-1)].tail&&dp[i]-g[back(j,1)][i]-a[j]>=Q[back(j,i-1)].q[Q[back(j,i-1)].tail])Q[back(j,i-1)].tail--;//弹出不优元素
Q[back(j,i-1)].q[++Q[back(j,i-1)].tail]=dp[i]-g[back(j,1)][i]-a[j];
Q[back(j,i-1)].id[Q[back(j,i-1)].tail]=i;
}
}
cout<<dp[m];
return 0;
}

810

被折叠的 条评论
为什么被折叠?



