Codeforces 677D 二维线段树 + 重要技巧

链接:http://codeforces.com/problemset/problem/677/D

到处看题解,用两种方法A了这道题

方法一:

我们到了点(x0,y0) 取得了这一点的钥匙a    可以拿着这个点的最短距离去更新同一行,手上有钥匙a的时候的最短距离

当我们需要求取得(x1, y1)处的钥匙a+1的最短距离时, 可以用与这个点同列的有钥匙a状态的点来更新

概括的说将巧妙地哈曼顿距离拆成与x,y轴分别平行的两段

这样的复杂度是 (n+m)*(nm)   



有个细节:

如果我没有看过题解代码的话,在求具有钥匙a的距离时

①     dis[n*m][n][m]的数组      显然MLE, 而且初始化是 8e9的 memset(dis, 0x3f, sizeof dis); TLE

②     开dis[n][m]                    每一次钥匙值改变都memset一下           同样TLE

考虑300 * 300的地图 并且p == 90000

这个做法的话每次需要用到的空间是300个int, 每次都要memset显然时间浪费巨大

做法:    用mark[300][300]保存 上一次更新到这个点时手中有的钥匙   

如果与这次更新的钥匙值相同    距离取小值, 否则距离换成新的, mark[i][j]随之改变


方法二:

以下公式算抄别人的……

if (key[i][j] + 1 == key[x][y]) dp[x][y] = min(dp[x][y], dp[i][j] + abs(x-i) + abs(y-j));

如果i, j在x, y左上方    dp[x][y] = min(dp[x][y], dp[i][j] - i - j + x + y);

同理右上 左下 右下,将整个地图裂成了4块

有一种似曾相识的赶脚……这不是单调队列优化dp的套路吗

用四棵二维线段树维护红色部分  

处理dp[x][y]时,以左上举例:

线段树维护 dp[i][j] - i - j 的最小值 query出 (1, 1) 至 (x,y) 的最小值 minv

minv + x + y 就是暂时的最小值

分别求出四块的得出dp[x][y]

递推公式求最值的题目,没有想法的话一定要试试能不能把公式分解啊!!


本来还没有写方法一的时候,   难到每次都要把树全部重新建一遍吗? TLE啊

后来发现 打个mark就行了……

玩了下class…好像不是很成功唉

<span style="font-size:14px;">#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath> 
#include <vector>
using namespace std;
const int maxn = 3e2 + 4;
int ans[maxn][maxn], dis[maxn][maxn], mark[maxn][maxn];
vector<int> node[maxn*maxn];
int n, m, p, x, y;
int main(){
	ios::sync_with_stdio(false);
	int i, j, k, kase;
	memset(mark, -1, sizeof mark);
	memset(ans, 63, sizeof ans);
	memset(dis, 0, sizeof dis);
	cin >> n >> m >> p;
	for (i = 0; i <= p; ++i) node[i].clear();
	for (i = 0; i < n; ++i)
		for (j = 0; j < m; ++j){
			cin >> x;
			node[x].push_back(i*m + j);
		}
	node[0].push_back(0);
	for (i = 0; i <= p; ++i){
		for (j = 0; j < int(node[i].size()); ++j){
			x = node[i][j] / m;
			y = node[i][j] % m;
//			cout << x << ' ' << y << endl;
			if (x == 0 && y == 0 && i != 0) ans[x][y] = 0x3f3f3f3f;
			for (k = 0; k < n; ++k)
				if (mark[k][y] == i - 1) ans[x][y] = min(ans[x][y], dis[k][y] + int(abs(k - x)));
//				cout << ans[x][y] << endl;
		}
		
		for (j = 0; j < int(node[i].size()); ++j){
			x = node[i][j] / m;
			y = node[i][j] % m;
//			cout << x << ' ' << y << ' ' << ans[x][y] << endl;
			for (k = 0; k < m; ++k)
				if (mark[x][k] != i){
					mark[x][k] = i;
					dis[x][k] = ans[x][y] + abs(y - k);
				}
				else dis[x][k] = min(dis[x][k], ans[x][y] + int(abs(y - k)));
		}
//		cout << endl;
	}
//	cout << x << ' ' << y << endl;
	cout << ans[x][y] << endl;
	return 0;
}</span>


/*
玩了发纯虚函数, 一开始还以为会有用的
训练了二维线段树 
1.通过给点做标记 而不用每一次大循环都把树重新清空一下
2.求(x-i) + (y-j)的最大值的方法             dp的双端队列优化也有这一点有趣啊 
*/ 
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using  namespace std;
#define lft pos << 1
#define rght (pos << 1) + 1 
const int maxn = 3e2 + 4;
const int inf = 0x3f3f3f3f;
int l[maxn << 2], r[maxn << 2], u[maxn << 2], e[maxn << 2];
void build(int pos, int L, int R){
	l[pos] = L, r[pos] = R;
	if (L == R) return;
	build(lft, L, (L + R) / 2);
	build(rght, (L + R) / 2 + 1, R);
	return;
}
void build2(int pos, int up, int down){
	u[pos] = up, e[pos] = down;
	if (up == down) return;
	build2(lft, up, (up + down) / 2);
	build2(rght, (up + down) / 2 + 1, down);
	return;
}
//好像不能通过循环求出最大值啊 伤心 
class tree{
	protected:
		int minv[maxn << 2][maxn << 2];
		int mark[maxn << 2][maxn << 2];
	public:
		virtual void update(int a, int num, int pos, int x, int y) = 0;
		virtual void query(int a, int pos, int L, int R, int down, int up) = 0;
		tree(){
			memset(minv, 0, sizeof minv);
			memset(mark, -1, sizeof mark);
		}
};
int ans, n, m, p;
class Tree: public tree{
	public:
		void update2(int a, int num, int rows, int pos, int y){
			if (mark[rows][pos] != a){
				mark[rows][pos] = a;
				minv[rows][pos] = num;
			}
			minv[rows][pos] = min(minv[rows][pos], num);
			if (u[pos] == e[pos]) return;
			if (e[lft] >= y) update2(a, num, rows, lft, y);
			else update2(a, num, rows, rght, y); 
			
		}
		void update(int a, int num, int pos, int x, int y){
			update2(a, num, pos, 1, y);
			if (l[pos] == r[pos]) return;
			if (r[lft] >= x) update(a, num, lft, x, y);
			else update(a, num, rght, x, y);
		}
		void query2(int a, int rows, int pos, int down, int up){// up < down
			if (mark[rows][pos] != a || ans <= minv[rows][pos]) return;
//			cout << "query2" << ' ' << pos << ' ' << u[pos] << ' ' << e[pos] << endl; 
			if (u[pos] >= up && e[pos] <= down){
				ans = minv[rows][pos];
//				cout << "successful\n";
				return;
			}
			if (u[pos] > down || e[pos] < up) return;
			query2(a, rows, lft, down, up);
			query2(a, rows, rght, down, up);
		}
		void query(int a, int pos, int L, int R, int down, int up){
//			cout << pos << endl;
//			cout << L << ' ' << l[pos] << "   " << R << ' ' << r[pos] << endl ;
			if (l[pos] >= L && r[pos] <= R){
				query2(a, pos, 1, down, up);
				return;
			}
			if (l[pos] > R || r[pos] < L) return;
			query(a, lft, L, R, down, up);
			query(a, rght, L, R, down, up);
		}
		Tree():tree(){
		}
}T[4];
int d[maxn][maxn], x, y;
vector<int> node[maxn*maxn];
int main(){
	ios::sync_with_stdio(false);
	int i, j, k, kase;
	cin >> n >> m >> p;
	build(1, 1, n);
	build2(1, 1, m);
	for (i = 0; i <= p; ++i) node[i].clear();
	for (i = 1; i <= n; ++i)
		for (j = 1; j <= m; ++j){
			cin >> x;
			node[x].push_back(i*m + j);
		}
	node[0].push_back(m+1);
	for (i = 0; i <= p; ++i){
		for (j = 0; j < int(node[i].size()); ++j){
			x = (node[i][j] - 1) / m;
			y = (node[i][j] - 1) % m + 1;
			{
				ans = inf;
//				cout << "lft :=" << 1 << "     rght :=" << x << endl; 
				T[0].query(i-1, 1, 1, x, y, 1);
//				cout << x << ' ' << y <<  ' ' << i << ' ' << ans << endl << endl;
				
//				getchar();getchar();getchar();
				d[x][y] = ans + x + y;
				ans = inf;
				T[1].query(i-1, 1, x, n, y, 1);
				d[x][y] = min(d[x][y], ans + y - x);
				ans = inf;
				T[2].query(i-1, 1, 1, x, m, y);
				d[x][y] = min(d[x][y], ans + x - y);
				ans = inf;
				T[3].query(i-1, 1, x, n, m, y);
				d[x][y] = min(d[x][y], ans - y - x);
			} 
			if (i == 0) d[x][y] = 0;
//			cout << "attention!!!!  " << d[x][y] << endl;
		}
		
		for (j = 0; j < int(node[i].size()); ++j){
			x = (node[i][j] - 1) / m;
			y = (node[i][j] - 1) % m + 1;
			{
				T[0].update(i, d[x][y] - x - y, 1, x, y);
				T[1].update(i, d[x][y] + x - y, 1, x, y);
				T[2].update(i, d[x][y] - x + y, 1, x, y);
				T[3].update(i, d[x][y] + x + y, 1, x, y);
			}
		}
	}
//	cout << x << ' ' << y << endl; 
	cout << d[x][y] << endl;
	return 0;
} 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值