1. 程式人生 > 實用技巧 >【線段樹優化建圖】luogu_P4083 [USACO17DEC]A Pie for a Pie G

【線段樹優化建圖】luogu_P4083 [USACO17DEC]A Pie for a Pie G

題意

2個人各自有n個禮物。
每個人從對方收到禮物,會回送在自己眼中價值\(x\sim x+d\)的禮物,其中\(x\)是對方送的禮物在自己眼中的價值。
當有人收到對方的禮物在自己眼中價值為0,那麼送禮結束。
對於第1個人的每種禮物,求出從它開始送起之後最少送禮的次數使送禮結束。

思路

可以反過來思考這個問題,就是從價值0的禮物開始跑最短路。
那麼對於一個禮物,它是指向一個區間的,我們就用線段樹優化建圖。
兩邊排序初始化即可,詳見程式碼。

程式碼

#include <queue>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

struct node {
	int a, b, id;
}g[200001];
int n, d, tot;
int b[200001], ans[100001];
int ver[1000001], next[1000001], edge[1000001], head[1000001], dis[1000001], v[1000001];

int cmp1(node x, node y) {
	return x.b < y.b;
}

int cmp2(node x, node y) {
	return x.a < y.a;
}

void add(int u, int v, int w) {
	ver[++tot] = v;
	next[tot] = head[u];
	edge[tot] = w;
	head[u] = tot;
}

void build(int p, int l, int r) {
	if (l == r) {
		b[l] = p;
		return;
	}
	int mid = l + r >> 1;
	add(p, p << 1, 0);
	add(p, p << 1 | 1, 0);
	build(p << 1, l, mid);
	build(p << 1 | 1, mid + 1, r);
}

int find1(int val) {
	int l = n + 1, r = 2 * n;
	if (g[r].a < val) return -1;
	while (l < r) {
		int mid = l + r >> 1;
		if (val <= g[mid].a) r = mid;
		else l = mid + 1;
	}
	return l;
}

int find2(int val) {
	int l = 1, r = n;
	if (g[r].b < val) return -1;
	while (l < r) {
		int mid = l + r >> 1;
		if (val <= g[mid].b) r = mid;
		else l = mid + 1;
	}
	return l;
}

int find3(int val) {
	int l = n + 1, r = 2 * n;
	if (g[l].a > val) return -1;
	while (l < r) {
		int mid = l + r + 1 >> 1;
		if (g[mid].a <= val) l = mid;
		else r = mid - 1;
	}
	return l;
}

int find4(int val) {
	int l = 1, r = n;
	if (g[l].b > val) return -1;
	while (l < r) {
		int mid = l + r + 1 >> 1;
		if (g[mid].b <= val) l = mid;
		else r = mid - 1;
	}
	return l;
}

void modify(int p, int l, int r, int L, int R, int pos) {
	if (L <= l && r <= R) {
		add(pos, p, 1);//連向一個區間
		return;
	}
	int mid = l + r >> 1;
	if (L <= mid) modify(p << 1, l, mid, L, R, pos);
	if (R > mid) modify(p << 1 | 1, mid + 1, r, L, R, pos);
}

void spfa() {
	memset(dis, 127 / 3, sizeof(dis));
	queue<int> q;
	for (int i = 1; i <= n; i++)
		if (!g[i].b && !v[b[i]]) q.push(b[i]), dis[b[i]] = 1, v[b[i]] = 1;
	for (int i = n + 1; i <= 2 * n; i++)
		if (!g[i].a && !v[b[i]]) q.push(b[i]), dis[b[i]] = 1, v[b[i]] = 1;
	while (q.size()) {
		int u = q.front();
		q.pop();
		v[u] = 0;
		for (int i = head[u]; i; i = next[i])
			if (dis[ver[i]] > dis[u] + edge[i]) {
				dis[ver[i]] = dis[u] + edge[i];
				if (!v[ver[i]]) {
					v[ver[i]] = 1;
					q.push(ver[i]);
				}
			}
	}
	for (int i = 1; i <= n; i++)
		ans[g[i].id] = dis[b[i]];
}

int main() {
	scanf("%d %d", &n, &d);
	for (int i = 1; i <= 2 * n; i++)
		scanf("%d %d", &g[i].a, &g[i].b), g[i].id = i;
	sort(g + 1, g + n + 1, cmp1);
	sort(g + n + 1, g + 2 * n + 1, cmp2);
	build(1, 1, 2 * n);
	for (int i = 1; i <= n; i++) {//第一個人連向第二個人
		int l = find1(g[i].a - d), r = find3(g[i].a);
		if (l < 0 || r < 0) continue;
		modify(1, 1, 2 * n, l, r, b[i]);
	}
	for (int i = n + 1; i <= 2 * n; i++) {//反之
		int l = find2(g[i].b - d), r = find4(g[i].b);
		if (l < 0 || r < 0) continue;
		modify(1, 1, 2 * n, l, r, b[i]);
	}
	spfa();
	for (int i = 1; i <= n; i++)
		printf("%d\n", ans[i] == 707406378 ? -1 : ans[i]);
}