题目描述
-
老虎是一名旅游爱好者。
-
时至 7 7 7 月,正是前往海边度假的好时节。在海边享受阳光,沙滩和海风呼啸,真是虎生的一大享受。海边的居民有着自己的风俗习惯,具体来说,海边可以看做是一个 n × n n \times n n×n 的网格,左上角为 ( 1 , 1 ) (1, 1) (1,1),右下角为 ( n , n ) (n, n) (n,n)。在每个网格中,都生活着渔民一家。对渔民来说,售卖新鲜的贝类显然是谋生的一大工具,而在小镇中,只有网格 ( 1 , 1 ) (1, 1) (1,1) 中有海鲜市场。每天,渔民们会空手从自己的家离开,并通过向左或向上走的方式到达海鲜市场。渔民会在沿途中拾取贝类,并在海鲜市场出售。具体来说,如果一个渔民经过了点 ( i , j ) (i, j) (i,j),那么他能够拾取到价值为 a i , j a_{i, j} ai,j 的贝壳。
-
显然,每个渔民会选择一条收益最大的路径前进,现在老虎想要知道,所有渔民的最大收益和是多少。
-
由于自然环境的变化,渔民经过某个位置的代价可能会改变。但自然环境是动态平衡而稳定的,因此渔民经过某个位置时的收益变化不会超过 1 1 1。
-
同时,渔民显然不会做不利于自己的事,因此我们保证条件 a i , j ≥ 0 a_{i, j} \ge 0 ai,j≥0 始终满足。
-
对于 100 % 100\% 100% 的数据, 1 ≤ a i , j , n ≤ 2000 1 \le a_{i, j}, n \le 2000 1≤ai,j,n≤2000。
算法分析
- 首先我们可以有一个简单的 O ( n 3 ) \mathcal O(n^3) O(n3) 做法:令 f ( i , j ) f(i,j) f(i,j) 表示从 ( i , j ) → ( 1 , 1 ) (i,j)\to (1,1) (i,j)→(1,1) 的最大收益,然后就有 f ( i , j ) = max { f ( i − 1 , j ) , f ( i , j − 1 ) } + a i , j f(i,j)=\max\{f(i-1,j),f(i,j-1)\}+a_{i,j} f(i,j)=max{f(i−1,j),f(i,j−1)}+ai,j。
- 每次修改后暴力将 ( x , y ) (x,y) (x,y) 到 ( n , n ) (n,n) (n,n) 的 f f f 修改即可。
- 考虑每次被修改到的位置满足什么性质。
- 不难发现:
- 被修改的位置一定是在 ( x , y ) (x,y) (x,y) 到 ( n , n ) (n,n) (n,n) 之间,并且构成一个连通块。
- ( i , j − 1 ) 和 ( i − 1 , j ) 都被修改 ⇒ ( i , j ) 被修改 (i,j-1)\text{和}(i-1,j)\text{都被修改}\Rightarrow (i,j)\text{被修改} (i,j−1)和(i−1,j)都被修改⇒(i,j)被修改 。
- 不难证明,每一行被修改的恰好是连续的一段,并且这段的 l , r l,r l,r 都是随着行数增加而单调递增的。
- 因此我们可以得到一个算法,每次修改用两个指针 l , r l,r l,r 表示该行要修改 [ l , r ] [l,r] [l,r] 这个区间。
- 然后我们从上往下一行行扫过去,在此过程判断 l , r l,r l,r 能否向右移动。
- 显然对于每次操作,查询次数是 O ( n ) \mathcal O(n) O(n) 的。
- 然后我们要实现的就是区间加,单点询问,用 BIT \text{BIT} BIT 即可。
- 时间复杂度 O ( n 2 log n ) \mathcal O(n^2\log n) O(n2logn)。
#include <bits/stdc++.h>
template <class T>
inline void read(T &x)
{
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
inline bool read_opt()
{
static char ch;
while (ch = getchar(), ch != 'U' && ch != 'D');
return ch == 'U';
}
typedef long long s64;
const int MaxN = 2e3 + 5;
int n;
int a[MaxN][MaxN], f[MaxN][MaxN];
s64 ans;
s64 c[MaxN][MaxN];
inline void add(s64 *c, int x, int del)
{
for (; x <= n; x += x & -x)
c[x] += del;
}
inline s64 query(int i, int x)
{
s64 res = 0;
for (; x; x ^= x & -x)
res += c[i][x];
return res;
}
inline void modify(int i, int l, int r, int del)
{
add(c[i], l, del), add(c[i], r + 1, -del);
}
int main()
{
freopen("run.in", "r", stdin);
freopen("run.out", "w", stdout);
read(n);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
read(a[i][j]);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
ans += (f[i][j] = std::max(f[i - 1][j], f[i][j - 1]) + a[i][j]);
printf("%lld\n", ans);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
modify(i, j, j, f[i][j]);
for (int orzczk = 1; orzczk <= n; ++orzczk)
{
int opt = read_opt(), x, y;
read(x), read(y);
a[x][y] = opt ? a[x][y] + 1 : a[x][y] - 1;
for (int i = x, l = y, r = y; i <= n; ++i)
{
while (r < n && query(i, r + 1)
!= std::max(query(i, r) + (opt ? 1 : -1), query(i - 1, r + 1)) + a[i][r + 1])
++r;
while (l <= r && query(i, l)
== std::max(query(i, l - 1), query(i - 1, l)) + a[i][l])
++l;
// printf(":%d %d %d\n", i, l, r);
if (l > r) break;
ans += (opt ? 1 : -1) * (r - l + 1);
modify(i, l, r, opt ? 1 : -1);
}
printf("%lld\n", ans);
}
return 0;
}