【arc067F】Yakiniku Restaurants
Solution
这题其实好像。。思维难度不算特别大==(众神犇:这不是弱智题吗?)但是我再一次在场上想到之后自己fou掉了正确的解法qwq真是全场最佳qwq
首先比较容易想到走的时候为了让减去的值尽量小,肯定是从前往后走的
然而这样想好像没有什么用== 这个时候又有一个很粗暴的想法:如果说我们已经钦定了走的是哪一段(假设是从\(l\)走到\(r\)),那么我们只要求出每张票在这段区间中的价值的最大值,然后加起来再减掉钦定的距离就是答案了
枚举\(l\)和\(r\)是\(n^2\)的,现在我们需要一个比较快速的求区间最大值之和的方法
最大值的话。。我们考虑固定左端点\(l\),然后移动\(r\),容易发现在这个过程中对于每一张票,它的最大值是单调的(不下降),我们考虑对于每一张票用一个单调栈来维护一下当前最大值
但是这样还不能够统计快速统计到当前区间的最大值之和
我们考虑维护一个数组\(val\),\(val[i]\)表示当左端点\(l\)固定的时候,右端点\(r\)为\(i\)时的最大值增量,这个东西可以在单调栈维护最大值的时候一并处理,具体一点的操作就是:对于每一个新加进栈里的元素(也就是当前的最大值),我们记这个元素的位置为\(pos\),在\(val[pos]\)处加上该元素的值,并在栈中前一个元素的位置减去当前元素的值(移到那里去的话就不包含当前元素了),然后在元素出栈的时候,对于当前出栈的元素,在它的位置上减去这个元素的值,再在它的栈中前一个元素的位置加上它的值(其实就是和上面的操作反过来,相当于消除这个元素的影响)
然后查询的时候,我们从大到小枚举\(l\)(单调栈需求),然后将当前行的每张票的值丢到其对应的单调栈里面,从小到大枚举\(r\),不断加上\(val\)这个增值,更新答案,在往后走的时候减去距离的代价即可
时间复杂度是\(O(n(n+m))\)的,然而好像有人写的是\(O(nmlogn)\)(还是\(logm\)忘记了qwq)也可以过qwq
最后吐槽一下自己的弱智操作:想到了这个做法之后非常开心,但是马上又觉得我要先把所有左端点对应的增值数组先算出来,然后最后统计的时候再累加然后就爆炸了==
代码大概长这个样子
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N=5010,TOP=10;
int a[N][N];
ll val[N];
int dis[N];
int n,m;
ll ans;
struct Stack{
int st[N],which,top;
void init(int _which){which=_which;}
void push(int x){
while (top&&a[st[top]][which]<=a[x][which]){
val[st[top]]-=a[st[top]][which];
if (top>1)
val[st[top-1]]+=a[st[top]][which];
--top;
}
st[++top]=x;
val[st[top]]+=a[st[top]][which];
if (top>1)
val[st[top-1]]-=a[st[top]][which];
}
}St[N];
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
ll now;
scanf("%d%d",&n,&m);
for (int i=1;i<n;++i) scanf("%d",dis+i);
memset(val,0,sizeof(val));
for (int i=1;i<=n;++i)
for (int j=1;j<=m;++j)
scanf("%d",&a[i][j]);
for (int i=1;i<=m;++i) St[i].init(i);
for (int l=n;l>=1;--l){
for (int i=1;i<=m;++i)
St[i].push(l);
now=0;
for (int r=l;r<=n;++r){
now+=val[r];
ans=max(ans,now);
now-=dis[r];
}
}
printf("%lld\n",ans);
}