题解 P3800 【Power收集】

囧仙

2020-04-11 23:59:34

Solution

东方Project相关试题(Easy-Normal) [3 / 30] - 题目大意 给出一个 $n \times m$ 的棋盘,有一些格子上面有权值。一开始站在第一行,每次下移一行,可以向两边走 $t$ 步以内,求权值和最大的路径 - 解法 这题和 dp 经典入门题,P1216 十分相似,只是每次行走的方案多了很多 所以很容易就可以想到,设 $dp_{i,j}$ 表示走到第 $i$ 行,第 $j$ 列的最大权值和,每次的转移是: $$dp_{i,j} = max(dp_{i - 1,k}) + c_{i,j}(j - t \le k \le j + t)$$ 如果你暴力去枚举的话,是 $O(nmt)$ 的,显然不能通过 但是我们枚举的区间实际上是很有规律的,它的长度相同,并且每次只会在左边少一个数,右边多一个数 显然,这个是单调队列的经典例题,滑动窗口 所以每次用单调队列去维护最大值就可以了,时间复杂度 $O(nm)$ 代码: ```cpp #include <cstdio> #include <algorithm> using namespace std; int n,m,k,t; int c[4005][4005]; int dp[4005][4005]; int h = 1,r = 1; struct node{ int id,val; }que[4005]; void pop(int id){ while(h != r){ if(que[h].id < id){ h++; }else{ return; } } } void push(int id,int val){ while(h != r){ if(que[r - 1].val <= val){ r--; }else{ break; } } que[r++] = {id,val}; } int main(){ scanf("%d%d%d%d",&n,&m,&k,&t); for(int i = 1;i <= k;i++){ int x,y,val; scanf("%d%d%d",&x,&y,&val); c[x][y] += val; } for(int i = 1;i <= n;i++){ h = r = 1; for(int j = 1;j <= t;j++){ push(j,dp[i - 1][j]); } for(int j = 1;j <= m;j++){ pop(j - t); if(j + t <= m) push(j + t,dp[i - 1][j + t]); dp[i][j] = que[h].val + c[i][j]; } } int ans = 0; for(int j = 1;j <= m;j++){ ans = max(ans,dp[n][j]); } printf("%d\n",ans); return 0; } ```