【日常训练】跑步(BIT)

本文探讨了一种基于二维网格的动态调整与区间更新优化算法,适用于收益最大化的路径选择问题。通过使用线段树和差分技巧,实现了对特定区域内数值的高效更新,同时保持了全局最优解的准确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

  • 老虎是一名旅游爱好者。

  • 时至 777 月,正是前往海边度假的好时节。在海边享受阳光,沙滩和海风呼啸,真是虎生的一大享受。海边的居民有着自己的风俗习惯,具体来说,海边可以看做是一个 n×nn \times nn×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),那么他能够拾取到价值为 ai,ja_{i, j}ai,j 的贝壳。

  • 显然,每个渔民会选择一条收益最大的路径前进,现在老虎想要知道,所有渔民的最大收益和是多少。

  • 由于自然环境的变化,渔民经过某个位置的代价可能会改变。但自然环境是动态平衡而稳定的,因此渔民经过某个位置时的收益变化不会超过 111

  • 同时,渔民显然不会做不利于自己的事,因此我们保证条件 ai,j≥0a_{i, j} \ge 0ai,j0 始终满足。

  • 对于 100%100\%100% 的数据,1≤ai,j,n≤20001 \le a_{i, j}, n \le 20001ai,j,n2000

算法分析

  • 首先我们可以有一个简单的 O(n3)\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)}+ai,jf(i,j)=\max\{f(i-1,j),f(i,j-1)\}+a_{i,j}f(i,j)=max{f(i1,j),f(i,j1)}+ai,j
  • 每次修改后暴力将 (x,y)(x,y)(x,y)(n,n)(n,n)(n,n)fff 修改即可。
  • 考虑每次被修改到的位置满足什么性质。
  • 不难发现:
    • 被修改的位置一定是在 (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,j1)(i1,j)都被修改(i,j)被修改
  • 不难证明,每一行被修改的恰好是连续的一段,并且这段的 l,rl,rl,r 都是随着行数增加而单调递增的。
  • 因此我们可以得到一个算法,每次修改用两个指针 l,rl,rl,r 表示该行要修改 [l,r][l,r][l,r] 这个区间。
  • 然后我们从上往下一行行扫过去,在此过程判断 l,rl,rl,r 能否向右移动。
  • 显然对于每次操作,查询次数是 O(n)\mathcal O(n)O(n) 的。
  • 然后我们要实现的就是区间加,单点询问,用 BIT\text{BIT}BIT 即可。
  • 时间复杂度 O(n2log⁡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; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值