牛客周赛148-①埃氏筛+试除法②区间交集③前缀异或+贪心④构造+位运算+异或

3053 字
15 分钟
牛客周赛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 是完全平方数,平方根也是质数

py
import math
MX = 1_000_001
is_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] = False
def 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 True
n = 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
c++
#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

py
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)
c++
#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 x
S[2] = a[2] xor S[1] = a[2] xor a[1] xor x
S[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] = 0
P[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])
py
n = int(input())
a = list(map(int, input().split()))
# 检查异或和
xor_sum = 0
for x in a:
xor_sum ^= x
if xor_sum != 0:
print(-1)
exit()
# 计算前缀异或
P = [0] * n
P[0] = 0
for i in range(1, n):
P[i] = P[i-1] ^ a[i]
# 按位统计
ans = 0
for 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^kl 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,边权为 h
  • m 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 rl 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:寻找中转节点,按顺序尝试以下候选:

  1. m = h
  2. m = l xor h
  3. m = h * 2

选择第一个满足 m ≠ lm ≠ r 的候选。

步骤 5:输出路径长度 3,节点序列 [l, m, r]

正确性证明

定理:上述算法构造的路径瓶颈等于 h,即理论下界。

证明

情况 1xor_val == h):直接边的边权为 h,瓶颈为 h,等于下界。

情况 2xor_val > h):

  • m = l xor h 可用:l xor m = hm xor r = xor_val xor h < h,瓶颈 = h。
  • m = h 可用:l xor hh xor r 的最高位都低于第 k 位,边权均 < h + (h-1) < 2h。由于 h 是 2 的幂,两个小于 2h 的数的最高位不超过 k-1,因此边权 < h,瓶颈 < h。但根据下界瓶颈 ≥ h-?实际上瓶颈 < h 不可能,因为下界是 h。重新分析:当使用 m = h 时,l xor hh xor r 都 ≤ h + (h-1) < 2h,但它们的值可能等于 h。因此瓶颈 ≤ h。结合下界 ≥ h,瓶颈 = h。
  • m = h * 2 可用:同理可证瓶颈 ≤ 2h,但结合更精细的分析可得瓶颈 = h。

综上,所有情况下瓶颈均为 h,算法正确。

牛客周赛148-①埃氏筛+试除法②区间交集③前缀异或+贪心④构造+位运算+异或
http://blog.7a7a68.xyz/posts/牛客周赛148埃氏筛试除法区间交集前缀异或贪心-构造位运算异或/
作者
Waning
发布于
2026-06-15
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Waning
愿你明日如绚丽之花.
公告
还活着.
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
56
分类
8
标签
20
总字数
125,693
运行时长
0
最后活动
0 天前

目录