【CodeForces 1066F】离散化 | 贪心 | 简化极角排序 | 动态规划 | N

                                1066F. Yet another 2D Walking

                                                                         time limit per test: 4 seconds

                                                                  memory limit per test: 256 megabytes

URL

http://codeforces.com/contest/1066/problem/F

Tags

离散化 + 贪心 + 简化极角排序 + DP

Introduction

平面坐标系xOy,给出n个整点,每个点 (x, y) 的层数定义为 max{x, y}

一个人从原点出发,并且按层数非递减顺序访问每个点。

求最短路径长度和(距离是曼哈顿距离,即竖直/水平折线的距离)。

Input

Codeforces老规矩,一组输入。第一行是点的个数(数据范围2e5,能猜到应该是nlogn的解法),接下来V行是每个点的 x y 坐标

Output

输出最短距离和

Sample Input

8
2 2
1 4
2 3
3 1
3 4
1 1
4 3
1 2

 

Sample Output

15

图解:

 

 

Analysis   &   AC Code

分析如下:

 

  • ① 离散化

    •  把层号离散化,因为坐标可能非常大,但是层数最多不超过点的个数。为了存下每层信息,需要离散化。这里要用到一次排序,时间复杂度 O(nlogn)

  • ② 贪心

    •  按层走,在每一层内部走的时候,最短路的走法一定是按顺时针走过本层内所有点(不回头)或者按逆时针走。并且由于是曼哈顿距离,如不这样走的话,最终解一定不会为最优解(一定会绕远)所以贪心算法适用。时间复杂度 Θ(n)

  • ③ 简化版极角排序

    • 对所有点排序,时间复杂度 O(nlogn) (当然这里的排序可以和用来离散化的排序在一起进行)(不在同一层的点,层数小的排在前面;在同一层的,x小的 或者 x相等但y大的 排在前面,这样就可以把同一层内的点按照顺时针排好)然后就可以:

    1. 对每一层进行完整的顺序遍历,进而求出每一层内部的路径和

    2. 为之后dp状态转移做好基础(把每一层最左上的点排在了vector的最前、最右下的点排在了vector的最后,便于dp状态转移时求层间距

  • ④ DP

    • 首先这里满足无后效性(对于之前层不同的走法,只要累计路径和相同,就可以认为这些不同的走法都是相同的,即选择任何一个都不会影响后续的最优解)和最优子结构性质(最终最优解的中途解也是对应的最优解,否则最终解还可以更小,那就不是最优解了),所以dp算法适用

    • 然后再分析具体如何dp。每一层都可能以顺时针方向通过,或者以逆时针方向通过,所以跟本层走法有关;并且跟上一层是顺时针还是逆时针也有关(决定着状态转移时层间距是多少)。很自然地就可以想到二维DP,第一维就是层编号,第二维表示这一层是顺时针通过的还是逆时针通过的。dp结束后取 min{ dp[level_count-1][R], dp[level_count-1][S] } 就是最终答案。时间复杂度 Θ(n)

    • dp 详细思路

      • 【定义】:dp[i][R] 表示走到第i层最后一个点,并且第i层是时针通过的;dp[i][S] 表示走到第i层最后一个点,并且第i层是时针通过的
      • 【初始化】:dp[0][R] = dp[0][S] = dist_sum[0](dist_sum[0]就是第0层内部的路径和,在本题第0层只有一个点(原点),所以其实就是0)
      • 【递推顺序】循环第一维 [1, level_count)(从第一层到最后一层dp最优路径和)
      • 【状态转移方程】:dist(↖↘) 表示上一层最左上的点和这一层最右下的点之间的曼哈顿距离,以此类推。
        • dp[i][R] = min
          • {
          •       dp[i-1][R] + dist(↘↖) + dist_sum[i]  (上层顺这层顺,层间路径是上层终点↘和这层起点↖的连接)
          •       dp[i-1][S] + dist(↖↖) + dist_sum[i]  (上层逆这层顺,层间路径是上层终点↖和这层起点↖的连接)
          • }
        • dp[i][S] = min
          • {
          •       dp[i-1][R] + dist(↘↘) + dist_sum[i]  (上层顺这层逆,层间路径是上层终点↘和这层起点↘的连接)
          •       dp[i-1][S] + dist(↖↘) + dist_sum[i]  (上层逆这层逆,层间路径是上层终点↖和这层起点↘的连接)
          • }
      • 【答案】min{ dp[level_count-1][R], dp[level_count-1][S] }

 

总时间复杂度 O(nlogn),空间复杂度 Θ(n)

 

代码:

#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>

#define LL long long
#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
#define PC putchar
#define MAX(a, b) (a>b?a:b)
#define MIN(a, b) (a<b?a:b)
#define DIST(p1, p2) ((p1.x>p2.x?p1.x-p2.x:p2.x-p1.x)+(p1.y>p2.y?p1.y-p2.y:p2.y-p1.y))
void PRT(const LL a){if(a<0){PC(45),PRT(-a);return;}if(a>=10)PRT(a/10);PC(a%10+48);}

constexpr int MN(200007);

enum RS_DIR
{
	R, S, RS_CNT
};

struct Point
{
	int x, y;
	int level;

	inline bool operator <(const Point &o) const
	{
		return level < o.level || level == o.level && (y > o.y || y == o.y && x < o.x);
	}
} points[MN];

LL dist_sum[MN];
LL dp[MN][RS_CNT];
std::vector<Point> level[MN];

int main()
{
	int n;
	sc(n)
	++n;

	points[0].x = points[0].y = points[0].level = 0;
	for (auto it=points+1, E=points+n; it!=E; ++it)
	{
		sc((it->x))
		sc((it->y))
		it->level = MAX(it->x, it->y);
	}
	std::sort(points, points+n);

	int level_index = 0;
	for (int i=0; i<n-1; ++i)
	{
		level[level_index].push_back(points[i]);

		if (points[i].level == points[i+1].level)
			dist_sum[level_index] += DIST(points[i], points[i+1]);
		else
			++level_index;
	}
	level[level_index].push_back(points[n-1]);
	int level_cnt = level_index + 1;

	dp[0][R] = dp[0][S] = 0;
	for (int i=1; i<level_cnt; ++i)
	{
		LL R_R = dp[i-1][R] + DIST(level[i-1].back(), level[i].front()) + dist_sum[i];
		LL S_R = dp[i-1][S] + DIST(level[i-1].front(), level[i].front()) + dist_sum[i];
		if (R_R < S_R)
			dp[i][R] = R_R;
		else
			dp[i][R] = S_R;

		LL R_S = dp[i-1][R] + DIST(level[i-1].back(), level[i].back()) + dist_sum[i];
		LL S_S = dp[i-1][S] + DIST(level[i-1].front(), level[i].back()) + dist_sum[i];
		if (R_S < S_S)
			dp[i][S] = R_S;
		else
			dp[i][S] = S_S;
	}

	PRT(MIN(dp[level_cnt-1][R], dp[level_cnt-1][S]));

	return 0;
}

 

(刚刚打比赛的时候这道题是1:59:54 AC 的qwq) 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值