AT2289 [ARC067D] Yakiniku Restaurants
(本来是联系决策单调的,但似乎搞着搞着变成杂题了?)
思路
我们有一个朴素的 \(O(n^2m)\) 的做法,就是枚举左右端点,然后更新每种票贡献的最大值
我们要想方法将一个 \(n\) 或 \(m\) 给优化掉
考虑用扫描线的方法:
我们枚举右端点 \(r\),设 \(Mx[l]\) 为当左端点为 \(l\) 时的最大答案
当我们向右移动时,我们可以发现,对于每种票 \(i\),如果有 \(w[l][i]<w[r][i]\),那么代表 \(Mx[l]\) 一定可以被更新,而且这一定是连续一段的,即为连续一段区间 \([l,r]\),满足 \(w[l - 1][i]>w[r][i]\text{ and }\forall l\le x\le r,\ w[x][i]\le w[r][i]\)
但这样依旧可能被精心构造的数据卡成 \(O(n^2m)\)
但我们还可以发现,对于一段区间 \([x,y]\),如果 \(\forall x\le z < y\),有 \(w[z][i]\le w[y][i]\),那么说明在之前这段区间第 \(i\) 种票都已经被更新为 \(w[y][i]\) 了,那么我们对于这种区间就可以直接区间加(线段树实现)
因此我们只需要用单调栈记录这些关键点,如果一个点被访问到,那么这个点就会被弹出,因此单调栈的复杂度为 \(O(nm)\)
总复杂度就为 \(O(nm\log n)\)
#include<iostream>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#define LL long long
inline int reads()
{
int sign = 1, re = 0; char c = getchar();
while(c < '0' || c > '9'){if(c == '-') sign = -1; c = getchar();}
while('0' <= c && c <= '9'){re = re * 10 + (c - '0'); c = getchar();}
return sign * re;
}
int n, m, w[5005][205], p[5005][205]; LL ans, dis[5005];
namespace Seg_Tree
{
#define ls (now << 1)
#define rs ((now << 1) | 1)
LL tr[20005], tag[20005];
inline void down(int now, int l, int r)
{
if(tag[now])
{
tr[ls] += tag[now], tr[rs] += tag[now];
tag[ls] += tag[now], tag[rs] += tag[now];
tag[now] = 0;
}
}
void add(int now, int l, int r, int to)
{
if(l == r)
{
for(int i = 1; i <= m; i++) tr[now] += 1ll * w[to][i];
tr[now] += dis[to];
return;
}
int mid = (l + r) >> 1; down(now, l, r);
if(to <= mid) add(ls, l, mid, to);
else add(rs, mid + 1, r, to);
tr[now] = std::max(tr[ls], tr[rs]);
}
void modify(int now, int l, int r, int L, int R, LL add)
{
if(L <= l && r <= R)
{
tag[now] += add;
tr[now] += add;
return;
}
int mid = (l + r) >> 1; down(now, l, r);
if(L <= mid) modify(ls, l, mid, L, R, add);
if(mid < R) modify(rs, mid + 1, r, L, R, add);
tr[now] = std::max(tr[ls], tr[rs]);
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
n = reads(), m = reads();
for(int i = 2; i <= n; i++)
dis[i] = dis[i - 1] + reads();
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
w[i][j] = reads();
for(int i = 1; i <= n; i++)
{
Seg_Tree::add(1, 1, n, i);
for(int j = 1; j <= m; j++)
{
int now = i - 1;
while(now && w[now][j] <= w[i][j])
{
Seg_Tree::modify(1, 1, n, p[now][j], now, w[i][j] - w[now][j]);
now = p[now][j] - 1;
}
p[i][j] = now + 1;
}
ans = std::max(ans, Seg_Tree::tr[1] - dis[i]);
}
printf("%lld", ans);
return 0;
}