AMPPZ 2011 B Bytean Road Race

翻了翻Claris的博客,果然发现Claris的博客里写满了题解 orz Claris
存个链接 https://www.cnblogs.com/clrs97/p/8861303.html
不过这个题我的代码是写的O(n)的那个方法。
在昨天那个题交了38发之后,今天一发就过了!平均每题只交了不到二十发…
以及在昨天被卡空间之后,今天写题之前测了下空间能不能开的下。
摸索出了新的卡空间技巧,图和点都开成局部变量,用完就扔。
虽然倍增我不会,但是写过O(n)的就不想再写O(nlogn)的了(逃

题意

一张网格图中一些线段被标出,跑步选手可以从左上到右下选择路线跑步。多次询问两个节点,判断是否有能经过这两个节点的跑步路线。

解法:O(nlogn)

先把网格向旋转135度,现在它变成了一个根在下面的DAG。

定义左路径是从一个点出发尽可能向左走的路径,右路径是从一个点出发尽可能向右走的路径。

如果p和q之间有路径,那么做一条经过点q的水平线,则:

从p出发的左路径与水平线的交点要么是q,要么在q左侧;从p出发的右路径和水平线的交点要么是q,要么在q右侧。

左右路径的结构:倍增

构造二维数组left,对于每个v和i <=log2n,left[i,j] 表示从v出发沿着左路径走2^i步能到达的点的标号。同理构造right。然后我们可以在O(nlogn)时间内计算出left和right的值。

left[u,i]:=left[left[u,i-1],i-1].

然后,就可以计算出left和right与水平线的交点,进而在log时间内回答每次询问。

引导问题:树上相互的位置

给一颗二叉树,询问某两个点的位置关系:高低左右

(高低指是否是直系亲属)

解法

深度优先搜索,记录每个点第一次被访问的时间 I 和访问完其所有子节点后离开它的时间O。

a 高于 b 当且仅当 Ia < Ib 且 Ob < Oa

a 在 b 的左边当且仅当 Oa < Ib

解法:O(n)

将所有左路径建成一颗树,将所有右路径也建成一棵树。注意,此时所有的边都汇入终点,因此它的根就是最后一个交叉点。在翻转的图形中,正好形成了一个有根树。

经过观察,我们发现在左树中如果p在q的右侧,则从p出发的做路径与水平线的交点在q的右侧。反之亦然。于是便可以在常数时间内回答每次询问。

总的时间复杂度为 O(n)。

代码

#include <iostream>
#include <vector>

const int maxn = 100000+7;

int n, m, k;
int u, v;
std::pair<int, int> tl[maxn], tr[maxn];
int inl[maxn], inr[maxn], outl[maxn], outr[maxn];

void inti() {
	std::cin >> n >> m >> k;
	std::pair<int, int> p[n+1];
	std::vector<int> g[n+1];
	for (int i = 1; i <= n; i++) {
		std::cin >> p[i].first >> p[i].second;
	}
	for (int i = 1; i <= m; i++) {
		std::cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	int nextl, nextr;
	for (int i = 1; i <= n; i++) {
		tl[i] = tr[i] = std::make_pair(0, 0);
	}
	for (int i = 1; i <= n; i++) {
		nextl = nextr = 0;
		for (auto it: g[i]) {
			if (p[it].first > p[i].first) {
				nextl = it;
			}
			if (p[it].second < p[i].second) {
				nextr = it;
			}
		}
		if (nextr == 0) {
			nextr = nextl;
		}
		if (nextl == 0) {
			nextl = nextr;
		}
 		if (p[nextl].second == p[i].second) {
			tl[nextl].first = i;
		} else {
			tl[nextl].second = i;
		}
		if (p[nextr].second == p[i].second) {
			tr[nextr].first = i;
		} else {
			tr[nextr].second = i;
		}
	}
}

int cur;

void dfsl(int now) {
	cur++;
	inl[now] = cur;
	if (tl[now].first != 0) {
		dfsl(tl[now].first);
	}
	if (tl[now].second != 0) {
		dfsl(tl[now].second);
	}
	cur++;
	outl[now] = cur;
}

void dfsr(int now) {
	cur++;
	inr[now] = cur;
	if (tr[now].first != 0) {
		dfsr(tr[now].first);
	}
	if (tr[now].second != 0) {
		dfsr(tr[now].second);
	}
	cur++;
	outr[now] = cur;
}

int main(int argc, char *argv[]) {  
	std::ios::sync_with_stdio(false);
	std::cin.tie(0); 
	inti();
	cur = 0;
	dfsl(n);
	cur = 0;
	dfsr(n);	
	for (int i = 1; i <= k; i++) {
		std::cin >> u >> v;
		bool yes = 0;
		if (inl[v] < inl[u] && outl[v] > outl[u]) {
			yes = 1;
		}
		if (inr[v] < inr[u] && outr[v] > outr[u]) {
			yes = 1;
		}
		if (inl[u] < inl[v] && outl[u] > outl[v]) {
			yes = 1;
		}
		if (inr[u] < inr[v] && outr[u] > outr[v]) {
			yes = 1;
		}
		if (outl[v] < inl[u] && outr[u] < inr[v]) {
			yes = 1;
		}
		if (outl[u] < inl[v] && outr[v] < inr[u]) {
			yes = 1;
		}
		if (yes) {
			std::cout << "TAK\n";
		} else {
			std::cout << "NIE\n";
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值