【刷题日记】Adding Chords(ABC424 F)(异或哈希+树状数组)
题目
题目链接
中文题面
思路
先考虑一个更简单的问题:在圆上有 $ n $ 个等距离的点,编号从 $ 1 $ 到 $ n $ 。给定 $ A_1, B_1, A_2, B_2 $ ,已知四个数字为互不相等的 $ 1 \sim n $ 的整数,怎么判断 $ (A_1, B_1) $ 和 $ (A_2, B_2) $ 两条线段有没有相交?
可以看到, $ (A_2, B_2) $ 在圆上划出了两个区间: $ [A_2 + 1, B_2 - 1] $ 和 $ [B_2 + 1, A_2 - 1] $ 。如果 $ A_1, B_1 $ 两者分别落在两个区间中,则相交。如果两者落在同一区间,则不相交。
将刚才这个问题扩大到原题目中:对于 $ (A_i, B_i) $ ,要求 $ [A_i + 1, B_i - 1] $ 中不能只含有前面 $ i - 1 $ 条线段中任意一条的仅一个端点(要么两个都包含,要么都不包含),那么 $ (A_i, B_i) $ 可以被画下来。更形式化地说: $ \forall k \in [1, i - 1],\ k \in \mathbb{Z} $,满足 $ (A_k \in [A_i, B_i]) \oplus (B_k \in [A_i, B_i]) $ 不成立(即两条线段不满足“仅一个端点在区间内”的条件)。
已知“异或”运算的性质:$ a \oplus a = 0 $ 。如果我们给 $ A_i, B_i $ 分配一个独一无二的标志数字,那么只需要判断 $ [A_i, B_i] $ 区间内所有已出现标志的异或和:若异或和为 $ 0 $,则说明没有一条线段的端点“仅一个”分布在这个区间内(要么两个端点都在,异或抵消;要么两个都不在,无贡献)。
显然不能用 $ i $ 作为这个独特标志,例如 $ 1 \oplus 2 \oplus 3 = 0 $,若 $ A_1, A_2, A_3 $ 都在区间内,程序会误判为“没有端点独立分布”。因此,我们需要用随机整数作为标志。
问题转化为“单点修改”和“区间查询”,可用树状数组或线段树。这里选择树状数组,码量更小。
AC代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
//初始化随机种子
i64 n, q, a, b;
i64 s[1001000];	//树状数组维护的异或数组
i64 lowbit(i64 x){
	return x & -x;
}
void add(i64 x, i64 k){
	for(; x <= n; x += lowbit(x))
		s[x] ^= k;
	return;
}
i64 find(i64 x){
	i64 res = 0;
	for(; x; x -= lowbit(x))
		res ^= s[x];
	return res;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> q;
	while(q--){
		cin >> a >> b;
		//查询区间[A,B]的异或和:find(b) ^ find(a-1)(树状数组前缀和性质)
		if((find(a - 1) ^ find(b)) == 0){	//注意优先级,^优先级比==低!
			//区间异或和为0,说明没有相交,可绘制该线段
			cout << "Yes\n";
			i64 r = rnd();	//生成随机数作为a和b的“标志”
			add(a, r);		//给点a添加标志r
			add(b, r);		//给点b添加标志r(同一线段两端点标志相同)
		}
		else
			cout << "No\n";
	}
	return 0;
}
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号