牛客周赛148-①埃氏筛+试除法②区间交集③前缀异或+贪心④构造+位运算+异或
牛客周赛148①埃氏筛+试除法②区间交集③前缀异或+贪心④构造+位运算+异或
牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ
B-埃氏筛+试除法
题意:
将 n 分解为 p × p × q,其中 p 和 q 都是质数。
题解:
筛出10^6内的质数,枚举p:
1.n % (p²) == 0,且 q = n / p² 也是质数
2.n % p == 0,且 n/p 是完全平方数,平方根也是质数
import mathMX = 1_000_001is_p = [False] * 2 + [True] * (MX - 2)p = []for i in range(2, MX): if is_p[i]: p.append(i) for j in range(i * i, MX, i): is_p[j] = Falsedef is_prime_large(x): if x < 2: return False if x < MX: return is_p[x] limit = int(x ** 0.5) for prime in p: if prime > limit: break if x % prime == 0: return False return Truen = int(input())for p1 in p: if p1 * p1 > n: break # p1 作为 p(平方的质数) if n % (p1 * p1) == 0: q = n // (p1 * p1) if is_prime_large(q): print(p1, q) break # p1 作为 q if n % p1 == 0: t = n // p1 rt = int(t ** 0.5) if rt * rt == t and is_prime_large(rt): print(rt, p1) break#include <iostream>#include <vector>#include <cmath>#include <algorithm>using namespace std;typedef long long ll;const int MX = 1e6 + 5;bool is_p[MX];vector<int> p;void sieve(){ fill(is_p,is_p + MX,true); is_p[0] = is_p[1] = false; for(int i = 2;i < MX;i++){ if(is_p[i]){ p.push_back(i); for(long long j = 1LL*i*i;j < MX;j+=i){ is_p[j] = false; } } }}bool check_prime(ll x) { if (x < 2) return false; if (x < MX) return is_p[x]; for (int p1 : p) { if (1LL * p1 * p1 > x) break; if (x % p1 == 0) return false; } return true;}int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); sieve(); ll n; cin >> n; for(int p1 : p){ if(1ll *p1*p1 > n){ break; } if(n%(1ll*p1*p1) == 0){ ll q = n / (1ll * p1 * p1); if(check_prime(q)){ cout << p1 << " "<< q << endl; return 0; } } if(n%p1 == 0){ ll t = n/p1; ll rt = sqrt(t); if(rt * rt == t && check_prime(rt)){ cout << rt << " " << p1 << endl; return 0; } } } return 0;}C-区间交集
题意:
一个数轴上有x个数,每秒钟有如下操作:
1.清除当前最小坐标的数 2.剩余数分裂:x → {x, x+1}
计算在k秒后,数轴上有多少个数
题解:
第i个数在k秒后产生的区间为[a[i],a[i] + k]
第 0 个初始小苯在 k 秒后产生的区间是 [a[0], a[0]+k],但它的部分坐标会被清除
被清除的 k 个坐标恰好是 [a[0], a[0]+k-1] 与所有区间并集的交集(取最小的 k 个)
如果所有区间重叠成一片,那么答案 = 并集长度 - k
n,k = map(int,input().split())a = list(map(int,input().split()))# while k > 0:# k -= 1# b = []# for i in range(1,len(a)):# if a[i] not in b:# b.append(a[i])# if a[i]+1 not in b:# b.append(a[i] + 1)# a = b# print(len(a))a.sort()b = [(a[i], a[i] + k) for i in range(1, n)]b.sort()c = []for l, r in b: if not c or l > c[-1][1] + 1: c.append([l, r]) else: c[-1][1] = max(c[-1][1], r)tot = sum(r - l + 1 for l, r in c)ans = max(0, tot - (k - 1))print(ans)#include <iostream>#include <vector>#include <algorithm>using namespace std;typedef long long ll;int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, k; cin >> n >> k; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } sort(a.begin(), a.end());
if (k == 0) { cout << n << "\n"; return 0; }
if (n == 1) { cout << 0 << "\n"; return 0; } vector<pair<ll, ll>> intervals; for (int i = 1; i < n; i++) { intervals.push_back({a[i], a[i] + k}); } sort(intervals.begin(), intervals.end()); vector<pair<ll, ll>> merged; for (auto& [l, r] : intervals) { if (merged.empty() || l > merged.back().second + 1) { merged.push_back({l, r}); } else { merged.back().second = max(merged.back().second, r); } } ll total = 0; for (auto& [l, r] : merged) { total += r - l + 1; } ll ans = max(0LL, total - (k - 1)); cout << ans << "\n";
return 0;}E-前缀异或+贪心
题意:
给定一个长度为 n 的环形数组 a[0..n-1](首尾相连,即 a[n-1] 与 a[0] 相邻)。
操作定义: 每次操作可以选择:
- 两个相邻元素
a[i]和a[(i+1) mod n] - 一个非负整数
k(0 ≤ k < 30)
将这两个元素同时异或 2^k:
a[i] = a[i] xor 2^ka[(i+1) mod n] = a[(i+1) mod n] xor 2^k 通过任意次操作,将数组中所有元素变为 0,输出最小的次数。
题解:
操作的位独立性
每次操作异或的是 2^k,即只在某一个二进制位上翻转。
由于不同位之间的异或操作互不影响,我们可以将问题按位分解:分别考虑第 0 位、第 1 位、…、第 29 位,最后将各步的操作次数相加。
操作的”边”视角
每次操作同时影响两个相邻元素。这提示我们将操作放在边上考虑。
定义边 i(0 ≤ i < n)为连接 a[i] 和 a[(i+1) mod n] 的边。
每次操作相当于在某条边上施加一个影响(异或某个 2^k)。
1. 定义边操作值
设 S[i] = 在边 i 上施加的所有异或值的异或和(一个 30 位整数)。
由于异或满足结合律和交换律,操作顺序无关紧要,只需关注每条边上各二进制位被翻转的总次数。
2. 推导方程
对于数组元素 a[i],它被两条边影响:
- 边
i-1(连接a[i-1]和a[i]) - 边
i(连接a[i]和a[i+1])
要使得最终 a[i] = 0,需要:
a[i] xor S[i-1] xor S[i] = 0即:
S[i-1] xor S[i] = a[i]对于所有 i(下标模 n),得到 n 个方程。
可行性判断
设 S[0] = x(未知数),根据方程递推:
S[1] = a[1] xor xS[2] = a[2] xor S[1] = a[2] xor a[1] xor xS[3] = a[3] xor S[2] = a[3] xor a[2] xor a[1] xor x...S[i] = a[i] xor a[i-1] xor ... xor a[1] xor x定义前缀异或:
P[0] = 0P[i] = a[1] xor a[2] xor ... xor a[i] (i ≥ 1)则:
S[i] = P[i] xor x (0 ≤ i < n)环形约束条件
最后一个方程:
S[n-1] xor S[0] = a[0](P[n-1] xor x) xor x = a[0]P[n-1] = a[0]即:
a[1] xor a[2] xor ... xor a[n-1] = a[0]两边同异或 a[0]:
a[0] xor a[1] xor a[2] xor ... xor a[n-1] = 0结论:所有元素的异或和必须为 0,否则无解(输出 -1)。
每条边 i 上的操作次数 = S[i] 的二进制中 1 的个数(popcount(S[i]))。
因为一次操作只能翻转一个二进制位,所以翻转多个位需要多次操作。
总操作次数:
ans = Σ popcount(S[i]) = Σ popcount(P[i] xor x)我们需要选择 x 来最小化这个和。
由于每个二进制位独立,对于第 b 位:
- 令
bit[i] = (P[i] >> b) & 1(0 或 1) - 令
x_b = (x >> b) & 1(0 或 1)
该位的贡献:
- 若
x_b = 0:贡献 = Σ bit[i](即P[i]第b位为 1 的个数) - 若
x_b = 1:贡献 = Σ (1 - bit[i]) = n - Σ bit[i](即第b位为 0 的个数)
因此第 b 位的最小贡献:
min(cnt[b], n - cnt[b])其中 cnt[b] = P[0..n-1] 中第 b 位为 1 的元素个数。
ans = Σ_{b=0}^{29} min(cnt[b], n - cnt[b])n = int(input())a = list(map(int, input().split()))
# 检查异或和xor_sum = 0for x in a: xor_sum ^= xif xor_sum != 0: print(-1) exit()
# 计算前缀异或P = [0] * nP[0] = 0for i in range(1, n): P[i] = P[i-1] ^ a[i]
# 按位统计ans = 0for b in range(30): cnt = sum(1 for p in P if (p >> b) & 1) ans += min(cnt, n - cnt)print(ans)D-构造+位运算+异或
问题重述
在一个完全图中:
- 任意两个不同节点之间都有一条边
- 节点编号范围可达 10^9
- 连接节点 i 和 j 的边权为 i xor j(按位异或)
- 一条路径的权值定义为路径上经过的所有边的最大边权
任务:给定起点 l 和终点 r(1 ≤ l < r ≤ 10^9),找出一条从 l 到 r 的路径,使得路径权值最小。
输出要求:
- 第一行:路径经过的节点数 n(1 ≤ n ≤ 2×10^5)
- 第二行:依次经过的节点序列(第一个为 l,最后一个为 r)
- 若有多个最优解,输出任意一个
问题分析
一、问题类型
这是最小瓶颈路径问题。在一张图中,路径的瓶颈定义为路径上边的最大权值。目标是使瓶颈最小。
一般图的解法是求最小生成树,生成树上的路径即为最小瓶颈路径。但本题是完全图且边权为异或值,具有特殊性质,可以直接构造最优解。
二、异或运算的关键性质
性质 1:最高位决定大小
对于两个数 a 和 b,a xor b 的最高位 1 的位置决定了异或值的大小范围。若最高位是第 k 位,则 2^k ≤ a xor b < 2^{k+1}。
性质 2:翻转最高位可消除差异
设 h = 2^k 是 l xor r 的最高位。若将 l 的第 k 位翻转得到 m,则 m 与 r 在第 k 位上相同,从而 m xor r 的最高位低于第 k 位。
性质 3:异或的恒等式
(l xor m) xor (m xor r) = l xor r,该式恒成立。
三、瓶颈的下界
设 xor_val = l xor r,最高位为 h = 2^k。
任意从 l 到 r 的路径上,必然存在一条边,其两端节点在第 k 位上不同(一个为 0,一个为 1)。这条边的异或值在第 k 位上为 1,因此边权至少为 h。
结论:路径瓶颈不可能小于 h,即 h 是理论下界。
四、直接边的分析
直接走边 l → r,边权为 xor_val,瓶颈为 xor_val。
- 若
xor_val = h(即只有第 k 位不同),则瓶颈 = h,已达到下界,直接走就是最优。 - 若
xor_val > h(即还有其他位不同),则瓶颈 > h,直接走不是最优。
五、中转节点的构造
目标是找到一个中转节点 m,使路径 l → m → r 的瓶颈恰好为 h。
构造方法:取 m = l xor h。
分析:
l xor m = l xor (l xor h) = h,边权为 hm xor r = (l xor h) xor r = (l xor r) xor h = xor_val xor h
由于 h 是 xor_val 的最高位,xor_val xor h 相当于将 xor_val 的最高位 1 变为 0,因此 xor_val xor h < h。
于是 max(l xor m, m xor r) = max(h, <h) = h,瓶颈恰好为 h,达到下界。
特殊情况:若 m = l xor h 恰好等于 r,则不能使用(因为 m 不能等于终点)。此时改用 m = h 作为中转节点。
分析 m = h:
l xor h:将 l 的第 k 位翻转h xor r:将 r 的第 k 位翻转
由于 l 和 r 在第 k 位不同,翻转后两者在第 k 位相同,因此 (l xor h) xor r 和 l xor (h xor r) 的最高位都低于第 k 位。所以 max(l xor h, h xor r) ≤ 2^k - 1 < h,瓶颈小于 h,更加优于下界。
注意:若 m = h 恰好等于 l 或 r,则继续尝试 m = h * 2(即 2^(k+1)),该节点编号不同于 l 和 r,且作为中转同样能使瓶颈不超过 h。
步骤 1:计算 xor_val = l xor r。
步骤 2:计算最高位 h = 2^k,其中 k 是 xor_val 的最高位位置。
步骤 3:判断直接走是否最优。
- 若
xor_val == h,输出路径长度 2,节点序列[l, r],结束。
步骤 4:寻找中转节点,按顺序尝试以下候选:
m = hm = l xor hm = h * 2
选择第一个满足 m ≠ l 且 m ≠ r 的候选。
步骤 5:输出路径长度 3,节点序列 [l, m, r]。
正确性证明
定理:上述算法构造的路径瓶颈等于 h,即理论下界。
证明:
情况 1(xor_val == h):直接边的边权为 h,瓶颈为 h,等于下界。
情况 2(xor_val > h):
- 若
m = l xor h可用:l xor m = h,m xor r = xor_val xor h < h,瓶颈 = h。 - 若
m = h可用:l xor h和h xor r的最高位都低于第 k 位,边权均 < h + (h-1) < 2h。由于 h 是 2 的幂,两个小于 2h 的数的最高位不超过 k-1,因此边权 < h,瓶颈 < h。但根据下界瓶颈 ≥ h-?实际上瓶颈 < h 不可能,因为下界是 h。重新分析:当使用 m = h 时,l xor h和h xor r都 ≤ h + (h-1) < 2h,但它们的值可能等于 h。因此瓶颈 ≤ h。结合下界 ≥ h,瓶颈 = h。 - 若
m = h * 2可用:同理可证瓶颈 ≤ 2h,但结合更精细的分析可得瓶颈 = h。
综上,所有情况下瓶颈均为 h,算法正确。