首页 > 其他分享 >洛谷 P5618 堵塞的交通

洛谷 P5618 堵塞的交通

时间:2024-09-04 19:14:01浏览次数:4  
标签:堵塞 洛谷 int mid ID P5618 && id kid

洛谷 P5618 堵塞的交通

题意

有一个 \(2\times C\) 的网格图,需要维护 \(3\) 种操作。

  1. 连接相邻的两个格子。
  2. 将相邻的两个格子断开连接。
  3. 询问两个格子是否联通。

思路1

考虑分块。

连边时块内使用并查集维护,块与块之间用数组标记。

删边块内的边时暴力重构并查集,块之间的边清零标记。

查询时使用广搜。

从块的四个角开始广搜。

先使用并查集把起点能走到的角入队。

搜索时记录当前点编号和当前块的编号。

每个点可以走向相邻有标记的块的四角,

也可以走向当前块联通的四角(使用并查集判断)。

使用并查集判断终点所在块能被到达的角能否到达终点。

时间复杂度:\(O(n\sqrt n)\)。

代码1

#include <bits/stdc++.h>
#define ID(x,y) (((y)-1)*2+(x)) // x y 写反 
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
const int N = 1e6 + 5, M = 1005;
int n, L[M], R[M], id[N], fa[N];
int lu[M], ld[M], ru[M], rd[M];
int lkr[N], lkl[N];
int base, cnt;
vector <pii> E[M], temp;
char op[10];
struct node {int id, kid;};
queue <node> q;
queue <int> Q; 
bool can[N];
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
void merge(int x, int y) {
	int fx = find(x), fy = find(y);
	if (fx == fy) return ;
	fa[fx] = fy;
}
bool same(int x, int y) {return find(x) == find(y);}
void rebuild(int id, int x = 0, int y = 0) {
	lu[id] = ID(1, L[id]), ru[id] = ID(1, R[id]); // x y 写反 
	ld[id] = ID(2, L[id]), rd[id] = ID(2, R[id]);
	for (int i = L[id]; i <= R[id]; i ++)
		fa[ID(1, i)] = ID(1, i), fa[ID(2, i)] = ID(2, i);
	for (auto e : E[id]) {
		if (e.fi == x && e.se == y) continue;
		merge(e.fi, e.se);
		temp.push_back(e);
	}
	E[id].swap(temp); temp.clear();
}
void init() {
	base = sqrt(n);
	for (int i = 1; i <= n; i ++) {
		id[i] = (i - 1) / base + 1;
		if (!L[id[i]]) L[id[i]] = i;
		R[id[i]] = max(R[id[i]], i);
		cnt = max(cnt, id[i]);
	}
	for (int i = 1; i <= cnt; i ++) rebuild(i);
}
void link(int x_1, int y_1, int x_2, int y_2) {
	if (ID(x_1, y_1) > ID(x_2, y_2)) swap(x_1, x_2), swap(y_1, y_2);
	if (id[y_1] == id[y_2]) {
		merge(ID(x_1, y_1), ID(x_2, y_2));
		E[id[y_1]].push_back({ID(x_1, y_1), ID(x_2, y_2)});
		return ;
	}
	lkr[ID(x_1, y_1)] = 1;
	lkl[ID(x_2, y_2)] = 1;
}
void cut(int x_1, int y_1, int x_2, int y_2) {
	if (ID(x_1, y_1) > ID(x_2, y_2)) swap(x_1, x_2), swap(y_1, y_2);
	if (id[y_1] == id[y_2]) {
		rebuild(id[y_1], ID(x_1, y_1), ID(x_2, y_2));
		return ;
	}
	lkr[ID(x_1, y_1)] = 0;
	lkl[ID(x_2, y_2)] = 0;
}
int query(int x_1, int y_1, int x_2, int y_2) { // 思路错 
	if (same(ID(x_1, y_1), ID(x_2, y_2))) return 1; // 没判 
	if (same(ID(x_1, y_1), ld[id[y_1]])) q.push({ld[id[y_1]], id[y_1]});
	if (same(ID(x_1, y_1), rd[id[y_1]])) q.push({rd[id[y_1]], id[y_1]});
	if (same(ID(x_1, y_1), lu[id[y_1]])) q.push({lu[id[y_1]], id[y_1]});
	if (same(ID(x_1, y_1), ru[id[y_1]])) q.push({ru[id[y_1]], id[y_1]});
	while (!q.empty()) {
		node x = q.front(); q.pop();
		if (can[x.id]) continue;
		can[x.id] = 1; // 标记打错 
		Q.push(x.id);
		if (same(x.id, ld[x.kid])) q.push({ld[x.kid], x.kid});
		if (same(x.id, lu[x.kid])) q.push({lu[x.kid], x.kid});
		if (same(x.id, rd[x.kid])) q.push({rd[x.kid], x.kid});
		if (same(x.id, ru[x.kid])) q.push({ru[x.kid], x.kid});
		if (x.id == ld[x.kid])  // ld
			if (x.kid > 1 && lkl[x.id]) 
				q.push({rd[x.kid - 1], x.kid - 1});
		if (x.id == rd[x.kid])  // rd
			if (x.kid < cnt && lkr[x.id]) 
				q.push({ld[x.kid + 1], x.kid + 1});
		if (x.id == lu[x.kid])  // lu
			if (x.kid > 1 && lkl[x.id]) 
				q.push({ru[x.kid - 1], x.kid - 1});
		if (x.id == ru[x.kid])  // ru
			if (x.kid < cnt && lkr[x.id]) 
				q.push({lu[x.kid + 1], x.kid + 1});
	}
	int ok = 0;
	if (can[lu[id[y_2]]] && same(lu[id[y_2]], ID(x_2, y_2))) ok = 1;
	if (can[ru[id[y_2]]] && same(ru[id[y_2]], ID(x_2, y_2))) ok = 1;
	if (can[ld[id[y_2]]] && same(ld[id[y_2]], ID(x_2, y_2))) ok = 1;
	if (can[rd[id[y_2]]] && same(rd[id[y_2]], ID(x_2, y_2))) ok = 1;
	while (!Q.empty()) can[Q.front()] = 0, Q.pop();
	return ok;
}
int main() {
	scanf("%d", &n); 
	init();
	while (1) {
		scanf("%s", op);
		if (op[0] == 'E') break;
		if (op[0] == 'O') {
			int r1, c1, r2, c2;
			scanf("%d%d%d%d", &r1, &c1, &r2, &c2);
			link(r1, c1, r2, c2);
		}
		if (op[0] == 'A') {
			int r1, c1, r2, c2;
			scanf("%d%d%d%d", &r1, &c1, &r2, &c2);
			if (query(r1, c1, r2, c2)) puts("Y");
			else puts("N");
		}
		if (op[0] == 'C') {
			int r1, c1, r2, c2;
			scanf("%d%d%d%d", &r1, &c1, &r2, &c2);
			cut(r1, c1, r2, c2);
		}
	}
	return 0;
} 

思路2

使用线段树维护区间连通性。

维护左上角,右上角,左下角,右下角两两之间的连通性。

合并区间时分类讨论绕路的情况。

查询区间 \([l,r]\) 时,从线段树中查出 \([1,l],[l,r],[r,n]\) 三个区间,分类讨论绕路的情况。

修改竖着的边时,走到表示该位置的叶子,直接修改,更新即可。

修改横着 \([pos,pos+1]\) 的边时,只用走到第一个 \(mid=pos\) 的区间,更新该区间即可。

时间复杂度:\(O(n\log n)\)。

代码2

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
bool k[2][N];
struct segt{
	struct node {
		int Lt, Rt;
		bool u, d;
		bool l, r;
		bool x, y;
	} t[N << 2];
	friend node operator + (node a, node b) {
		node res;
		res.Lt = a.Lt, res.Rt = b.Rt;
		res.l = a.l || (a.u && k[0][a.Rt] && b.l && k[1][a.Rt] && a.d);
		res.r = b.r || (b.u && k[0][a.Rt] && a.r && k[1][a.Rt] && b.d);
		res.u = (a.u && k[0][a.Rt] && b.u) || (a.x && k[1][a.Rt] && b.y);
		res.d = (a.d && k[1][a.Rt] && b.d) || (a.y && k[0][a.Rt] && b.x);
		res.x = (a.u && k[0][a.Rt] && b.x) || (a.x && k[1][a.Rt] && b.d);
		res.y = (a.y && k[0][a.Rt] && b.u) || (a.d && k[1][a.Rt] && b.y);
		return res;
	}
	#define ls (p << 1)
	#define rs (p << 1 | 1)
	void build(int p, int l, int r) {
		t[p].Lt = l, t[p].Rt = r;
		if (l == r) {
			t[p].u = t[p].d = 1;
			t[p].l = t[p].r = 0;
			t[p].x = t[p].y = 0;
			return ;
		}
		int mid = (l + r) >> 1;
		build(ls, l, mid);
		build(rs, mid + 1, r);
		t[p] = t[ls] + t[rs];
	}
	void modify(int p, int id, bool c) {
		if (t[p].Lt == t[p].Rt) {
			t[p].l = t[p].r = c;
			t[p].x = t[p].y = c;
			t[p].u = t[p].d = 1;
			return ;
		}
		if (id <= t[ls].Rt) modify(ls, id, c);
		else modify(rs, id, c);
		t[p] = t[ls] + t[rs];
	}
	void Modify(int p, int id) {
		int mid = (t[p].Lt + t[p].Rt) >> 1;
		if (mid > id) Modify(ls, id);
		else if (mid < id) Modify(rs, id);
		t[p] = t[ls] + t[rs];
	}
	node query(int p, int l, int r) {
		if (l <= t[p].Lt && t[p].Rt <= r) return t[p];
		node res; res.Lt = -1;
		if (t[ls].Rt >= l) res = query(ls, l, r);
		if (t[rs].Lt <= r) {
			if (res.Lt == -1) res = query(rs, l, r);
			else res = res + query(rs, l, r);
		}
		return res;
	}
} T; 
int n; 
char op[10];
int main() {
	scanf("%d", &n);
	T.build(1, 1, n);
	while (1) {
		scanf("%s", op);
		if (op[0] == 'E') break;
		if (op[0] == 'O') {
			int x_1, y_1, x_2, y_2;
			scanf("%d%d%d%d", &x_1, &y_1, &x_2, &y_2);
			if (y_1 == y_2) T.modify(1, y_1, 1);
			else {
				if (y_1 > y_2) swap(y_1, y_2);
				k[x_1 - 1][y_1] = 1;
				T.Modify(1, y_1);	
			}
		}
		if (op[0] == 'C') {
			int x_1, y_1, x_2, y_2;
			scanf("%d%d%d%d", &x_1, &y_1, &x_2, &y_2);
			if (y_1 == y_2) T.modify(1, y_1, 0);
			else {
				if (y_1 > y_2) swap(y_1, y_2);
				k[x_1 - 1][y_1] = 0;
				T.Modify(1, y_1);	
			}
		}
		if (op[0] == 'A') {
			int x_1, y_1, x_2, y_2;
			scanf("%d%d%d%d", &x_1, &y_1, &x_2, &y_2); 
			if (y_1 > y_2) swap(x_1, x_2), swap(y_1, y_2); 
			segt::node mid = T.query(1, y_1, y_2);
			segt::node l = T.query(1, 1, y_1);
			segt::node r = T.query(1, y_2, n);
			bool ans = 0;
			if (x_1 == 1 && x_2 == 1) {
				ans |= mid.u, ans |= l.r && mid.y;
				ans |= mid.x && r.l, ans |= l.r && mid.d && r.l;
			} 
			if (x_1 == 2 && x_2 == 2) {
				ans |= mid.d, ans |= l.r && mid.x;
				ans |= l.r && mid.u && r.l, ans |= mid.y && r.l;
			}
			if (x_1 == 1 && x_2 == 2) {
				ans |= mid.x, ans |= l.r && mid.y && r.l;
				ans |= l.r && mid.d, ans |= mid.u && r.l;
			}
			if (x_1 == 2 && x_2 == 1) {
				ans |= mid.y, ans |= l.r && mid.u;
				ans |= mid.d && r.l, ans |= l.r && mid.x && r.l;
			}
			puts(ans ? "Y" : "N");
		}
	}
	return 0;
}

标签:堵塞,洛谷,int,mid,ID,P5618,&&,id,kid
From: https://www.cnblogs.com/maniubi/p/18397212

相关文章

  • 洛谷 B3645 数列前缀和 2 题解
    前缀知识:枚举,费马小定理,逆元,线性乘法逆元,线段树(?)。解法1:暴力如题。暴力枚举即可,30分。由于太简单,不给代码。解法2:前缀积+费马小定理+逆元由于涉及静态区间,可以想到前缀积。前缀积公式为\(q_r/q_{l-1}\),除法恰好可以用逆元来算。直接写即可。不会超时,因为时间为\(O(n\logp)\)......
  • 洛谷题单指南-常见优化技巧-P3143 [USACO16OPEN] Diamond Collector S
    原题链接:https://www.luogu.com.cn/problem/P3143题意解读:找到两个不相交的最长连续序列,使得序列最大值和最小值差不超过k,求两个最长的序列长度和。解题思路:先将所有数从小到大排序,记为a[]要找到两个不相交的最长连续序列,可以采用下面技巧:设b[i]表示i之前“差值在k之内的连续......
  • 洛谷题单指南-常见优化技巧-P4653 [CEOI2017] Sure Bet
    原题链接:https://www.luogu.com.cn/problem/P4653题意解读:选中的灯泡中,某一类较少的总权值减去灯泡数量所得到的收益最大值。解题思路:注意,此题关键是:要使得较少的收益最大化1、要最大化,意味着每次应该选择尽可能大权值的灯泡2、要使A、B类中较少的收益最大化,意味着每次应该优......
  • 洛谷题单指南-常见优化技巧-唯一的雪花 Unique Snowflakes
    原题链接:https://www.luogu.com.cn/problem/UVA11572题意解读:本质上是要计算最长连续不重复子序列的长度,典型的双指针应用。解题思路:通过双指针来枚举子序列,右指针指向的元素每次记录元素出现的次数,可以借助hash数组h[]如果枚举到的元素出现次数超过1,则表示左、右指针之间的子......
  • 洛谷题单指南-常见优化技巧-P2216 [HAOI2007] 理想的正方形
    原题链接:https://www.luogu.com.cn/problem/P2216题意解读:在矩阵中找n*n正方形里最大值和最小值差值的最小值。解题思路:1、枚举法直接枚举所有n*n的正方形的位置,然后在遍历求最大值、最小值,复杂度为O(n^4),显然不能通过。2、二维单调队列既然是求正方形范围内的最值,看起来是......
  • 洛谷题单指南-常见优化技巧-P2032 扫描
    原题链接:https://www.luogu.com.cn/problem/P2032题意解读:求滑动窗口内的最大值,典型的单调队列应用。解题思路:单调队列的三部曲:1、去头。已存入的元素个数超过k,则去头。注意队列里存的是元素下标,只需要用当前下标减去队头元素来判断即可。2、去尾。根据单调队列的单调性,如果......
  • 洛谷 P4819 杀人游戏
    洛谷P4819杀人游戏题意有\(n\)个人,他们之中有一个杀手。每个人都有可能是杀手,并且概率相等。你可以询问若干人。若询问的人是杀手,你会被干掉。若询问的人是平民,你会知道他认识的所有人的身份。给出一张有向图表示这\(n\)个人的关系。求出你活着知道杀手是谁的概率。......
  • 洛谷 P3225 矿场搭建
    洛谷P3225矿场搭建题意煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。请......
  • 洛谷 P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布
    题目大意小A和小B,要进行\(N\)次猜拳,每次按照一定周期出拳,胜负情况如下:求出小A和小B分别赢了几次。思路枚举\(N\)次猜拳,每次比较\(a[powera]\)与\(b[powerb]\)(poewra与powerb是a和b数组的索引,详见代码)。CODE#include<bits/stdc++.h>usingnamespacestd;in......
  • 题解:洛谷 P10996 【MX-J3-T3】Tuple
    原题链接介绍一种(也许是正解的)卡常做法先说总体思路:对于每个三元组\((x,y,z)\),若有一个\(w\)满足\((x,y,w),(x,z,w),(y,z,w)\)均存在,则找到了一个合法的四元组\((x,y,z,w)\)。\(20\\rm{Pts}\)做法如果暴力搜索,在遍历每一个三元组时,每一次都扫描所有的\(w\in[1,N]\)......