比赛链接
C. 和天下
题目大意
给定 n n n 个结点的图以及整数 k k k,第 i i i 个结点的权值为 a i ( 1 ⩽ i ⩽ n ) a_i (1 \leqslant i \leqslant n) ai(1⩽i⩽n)
如果对于两个结点 u , v u, \ v u, v,满足 a u ⊙ a v ⩾ k a_u \odot a_v \geqslant k au⊙av⩾k,那么 u , v u, \ v u, v 之间就有一条无向边,否则没有边。
其中 ⊙ \odot ⊙ 表示按位与!!!!
求整张图中最大联通块的大小。
数据范围如下。
- 1 ⩽ n ⩽ 2 × 1 0 5 , 1 \leqslant n \leqslant 2 \times 10^5, 1⩽n⩽2×105,
- 0 ⩽ a i , k ⩽ 1 0 18 0 \leqslant a_i, \ k \leqslant 10^{18} 0⩽ai, k⩽1018.
Solution
若 k = 0 k = 0 k=0,则任意两个数与起来都是大等于 k k k 的,那么答案为 n n n。
若 k > 0 k > 0 k>0,我们考虑按位与的性质,即如果两个数 x , y x, \ y x, y 的某一位都是 1 1 1,那么 x ⊙ y x \odot y x⊙y 的这一位也是 1 1 1。
考虑 k k k 最高位的 1 1 1 在第 h i hi hi 位。
-
如果存在 a u , a v a_u, \ a_v au, av,使得 a u ⊙ a v a_u \odot a_v au⊙av 在 h i + 1 hi + 1 hi+1 或者更高位上有 1 1 1,那么这些满足条件的 ( u , v ) (u, \ v) (u, v) 就可以随便连边,因为高位满足,低位就无需考虑。
-
因此我们可以先按照第 b ∈ [ h i + 1 , 60 ) b \in [hi + 1, \ 60) b∈[hi+1, 60) 位上是否有 1 1 1 来进行连边操作,也就是在第 b b b 位上,所有满足这一位是 1 1 1 的 a i a_i ai,对应的下标 i i i 都属于同一个联通块。
接下来考虑第 b ∈ [ 0 , h i ] b \in [0, \ hi] b∈[0, hi] 位。
-
若 k k k 在第 b b b 位上为 1 1 1,那就要求 a i a_i ai 在这一位上也为 1 1 1,也就是说,对于那些在这一位上不为 1 1 1 的 a i ′ a_{i'} ai′,对应的下标 i ′ i' i′ 在之后的更低位都是禁止选择的(即在此之后 i ′ i' i′ 不可能再和别的结点连边了)。
-
若 k k k 在第 b b b 位上为 0 0 0,那 a i a_i ai 就没要求。而如果 a i a_i ai 在这一位上为 1 1 1,且 i i i 在此之前没有(因为上一段的原因)被 b a n ban ban 掉,那么 i i i 就是可以选择和其他结点连边的,将 i i i 加入当前第 b b b 位的可选集合。最后得到的可选集合内的下标都在同一个联通块内。
从第 h i hi hi 位操作到第 0 0 0 位后,我们能够得到最后仍然合法(即没被 b a n ban ban)的下标,这些下标可以在同一个联通块。
时间复杂度 O ( n log V ) O(n \log V) O(nlogV)
- V V V 为值域最大值。
C++ Code
#include <bits/stdc++.h>
using i64 = int64_t;
using u64 = uint64_t;
using f64 = double_t;
using i128 = __int128_t;
struct DSU {
std::vector<int> f, sz;
DSU() {}
DSU(int n) {
init(n);
}
void init(int n) {
f.resize(n);
std::iota(f.begin(), f.end(), 0);
sz.assign(n, 1);
}
int find(int x) {
while (x != f[x]) {
x = f[x] = f[f[x]];
}
return x;
}
int size(int x) {
return sz[find(x)];
}
bool merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) {
return false;
}
sz[x] += sz[y];
f[y] = x;
return true;
}
bool same(int x, int y) {
return find(x) == find(y);
}
};
void solve() {
int n;
i64 k;
std::cin >> n >> k;
std::vector<i64> a(n);
for (int i = 0; i < n; i++) {
std::cin >> a[i];
}
if (k == 0) {
std::cout << n << "\n";
return;
}
int hi = std::__lg(k);
DSU dsu(n);
auto work = [&](int &root, int i) {
if (root != -1) {
dsu.merge(root, i);
} else {
root = i;
}
};
for (int j = 59; j > hi; j--) {
int root = -1;
for (int i = 0; i < n; i++) {
if (a[i] >> j & 1) {
work(root, i);
}
}
}
std::vector<bool> valid(n, true);
for (int j = hi; j >= 0; j--) {
int root = -1;
for (int i = 0; i < n; i++) {
if (~k >> j & 1 and a[i] >> j & 1 and valid[i]) {
work(root, i);
} else if (k >> j & 1 and ~a[i] >> j & 1) {
valid[i] = false;
}
}
}
int root = -1;
for (int i = 0; i < n; i++) {
if (valid[i]) {
work(root, i);
}
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans = std::max(ans, dsu.size(i));
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout << std::fixed << std::setprecision(12);
int T = 1;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}
D. 搬家
题目大意
给定 n n n 个物品, m m m 个箱子,均排成一行。
每个箱子体积为 k k k,第 i i i 个物品体积为 a i ( 1 ⩽ i ⩽ n ) a_i (1 \leqslant i \leqslant n) ai(1⩽i⩽n)。
规定要从第 1 1 1 个箱子按顺序装到第 m m m 个箱子,一开始箱子什么都没装。
初始时指定第一个箱子,并选择一个起始位置 i i i,从第 i i i 个物品开始按顺序遍历它和它右侧的所有物品,对于每一个被遍历到的物品,我们的操作分为三步:
- 如果当前物品可以装入现在指定的收纳箱,则将其装入,
- 否则,只有其右侧存在收纳箱,才指定其右侧的第一个收纳箱,接着重复第一步,
- 若物品被遍历完,或者右侧不存在收纳箱,就停止整个过程。
求最多可以装入的物品个数。
数据范围如下。
- 1 ⩽ n , m ⩽ 1 0 5 , 1 \leqslant n, \ m \leqslant 10^5, 1⩽n, m⩽105,
- 1 ⩽ k ⩽ 1 0 9 , 1 \leqslant k \leqslant 10^9, 1⩽k⩽109,
- ∀ i ∈ [ 1 , n ] , 0 < a i ⩽ k . \forall i \in [1, \ n], \ 0 < a_i \leqslant k. ∀i∈[1, n], 0<ai⩽k.
Solution
由于从固定的 i i i 开始,往右跳到第一次停止过程,落点 r i r_i ri 是固定的,因此这种固定落点跳跃问题我们可以想到倍增优化。
我们称向右跳一步表示上述的从 i i i 到 r i r_i ri。
考虑 r [ i ] [ j ] r[i][j] r[i][j] 表示从 i i i 向右跳 2 j 2^j 2j 步能到达的下标, c n t [ i ] [ j ] cnt[i][j] cnt[i][j] 表示从 i i i 向右跳 2 j 2^j 2j 步能在所有箱子里装下的物品总数。
r [ i ] [ 0 ] r[i][0] r[i][0] 和 c n t [ i ] [ 0 ] cnt[i][0] cnt[i][0] 的计算可以用双指针,剩下 j > 0 j > 0 j>0 的部分可以用倍增求得。
接着我们枚举起点 i i i,然后模拟向右走的过程,具体如下(我的下标从 0 0 0 开始)。
设当前已经从 i i i 到了 j j j,还想向右走 2 b 2^b 2b 步,使用过(且无法再使用的)箱子总数为 t o t tot tot,所装下的物品总数为 r e s res res。
- 若
r
[
j
]
[
b
]
⩽
n
r[j][b] \leqslant n
r[j][b]⩽n 且
t
o
t
+
2
b
⩽
m
tot + 2^b \leqslant m
tot+2b⩽m,说明这
2
b
2^b
2b 步是可以走的,直接执行以下三步
- r e s = r e s + c n t [ j ] [ b ] , res = res + cnt[j][b], res=res+cnt[j][b],
- t o t = t o t + 2 b , tot = tot + 2^b, tot=tot+2b,
- j = r [ j ] [ b ] . j = r[j][b]. j=r[j][b].
- 令
b
=
b
−
1
b = b - 1
b=b−1,若满足以下三个条件的其中一个,就停止过程
- j = n , j = n, j=n,
- b < 0 , b < 0, b<0,
- t o t = m . tot = m. tot=m.
答案就是从每个 i i i 出发的答案的最大值。
时间复杂度 O ( n log n ) O(n \log n) O(nlogn)
C++ Code
#include <bits/stdc++.h>
using i64 = int64_t;
using u64 = uint64_t;
using f64 = double_t;
using i128 = __int128_t;
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector<int> a(n);
for (int i = 0; i < n; i++) {
std::cin >> a[i];
}
std::vector<std::array<int, 17>> r(n + 1);
std::vector<std::array<int, 17>> cnt(n + 1);
for (int i = 0, j = 0, sum = 0; i < n; i++) {
while (j < n and sum + a[j] <= k) {
sum += a[j++];
}
r[i][0] = j;
cnt[i][0] = j - i;
sum -= a[i];
}
r[n][0] = n;
for (int j = 1; j < 17; j++) {
for (int i = 0; i <= n; i++) {
r[i][j] = r[r[i][j - 1]][j - 1];
cnt[i][j] = cnt[i][j - 1] + cnt[r[i][j - 1]][j - 1];
}
}
i64 ans = 0;
for (int i = 0; i < n; i++) {
i64 res = 0;
for (int j = i, b = 16, tot = 0; j < n and b >= 0 and tot < m; b--) {
if (r[j][b] <= n and tot + (1 << b) <= m) {
res += cnt[j][b];
tot += 1 << b;
j = r[j][b];
}
}
ans = std::max(ans, res);
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout << std::fixed << std::setprecision(12);
int T = 1;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}
E. Alice and Bod(疑似打错)
题目大意
给定一个长度为 n n n 字符串 s s s,仅由小写字母组成。
现在给定 m m m,表示要做 m m m 次操作,每次操作有两种类型:
1 l r
:询问 s [ l . . . r ] s[l...r] s[l...r] 是否是回文串,是输出YES
,否则输出NO
。2 l r x
:将 s [ l . . . r ] s[l...r] s[l...r] 中每个字符都改为其第 x x x 个后继,比如 a a a 的第 2 2 2 个后继是 c c c, z z z 的第 1 1 1 个后继是 a a a, a a a 的第 26 26 26 个后继是 a a a。
数据范围如下。
- 1 ⩽ n , m ⩽ 1 0 5 , 1 \leqslant n, \ m \leqslant 10^5, 1⩽n, m⩽105,
- 0 ⩽ x ⩽ 1 0 9 . 0 \leqslant x \leqslant 10^9. 0⩽x⩽109.
Solution
做法是线段树 + 哈希。
首先,要判断一个字符串是否是回文串,哈希是很明显的。
但是这里有修改操作,因此不能直接上普通的字符串哈希。
这里的哈希方式是,对 26 26 26 个字母每个都建立一个 01 01 01 串,例子如下:
- 如 s = a a b c a s = aabca s=aabca,那么 a a a 对应的 01 01 01 串为 11001 11001 11001, b b b 对应的为 00100 00100 00100, c c c 对应的为 00010 00010 00010,其余 23 23 23 个字母都是全 0 0 0。
但是比较不能直接比这 26 26 26 个串,否则就是 O ( n ) O(n) O(n) 的比较了;所以我们需要对新生成的 01 01 01 串进行哈希,这里的底数和模数可以去搜一下,我选择的是 B = 29 , P = 1 ′ 00 0 ′ 00 0 ′ 007 B = 29, \ P = 1'000'000'007 B=29, P=1′000′000′007。
上面的对 01 01 01 串哈希的意思是,把这些 01 01 01 串看成(在 P P P 模数系下)的 B B B 进制数,例如 101 101 101 就表示 ( 1 ⋅ 2 9 2 + 0 ⋅ 2 9 1 + 1 ⋅ 2 9 0 ) % P (1 \cdot 29^2 + 0\cdot 29^1 + 1 \cdot 29^0) \ \% \ P (1⋅292+0⋅291+1⋅290) % P,这就是它的哈希值。
这样,我们判断一段子串是否是回文串,就只要正反两次哈希,然后比较这 26 26 26 个正反哈希值是否一致。
关键点是修改操作。
每次我们需要把字母调整为其第 x x x 个后继,其实就是让这个后继占了原字母的位置。再换句话说,其实就是把当前字母的状态,赋值给它的第 x x x 个后继。
因此修改字符这个操作,就变成了一个区间赋值操作,可以用带懒标记的线段树实现。
所以我们用线段树维护每个点的 26 26 26 个哈希值即可。
时间复杂度 O ( n log n ) O(n \log n) O(nlogn)
C++ Code
#include <bits/stdc++.h>
using i64 = int64_t;
using u64 = uint64_t;
using f64 = double_t;
using i128 = __int128_t;
template<class Info, class Tag>
struct LSGT {
int n;
std::vector<Info> info;
std::vector<Tag> tag;
LSGT() {}
LSGT(int _n, Info _v = Info()) {
init(std::vector(_n, _v));
}
template<class T>
LSGT(const std::vector<T> &a) {
init(a);
}
template<class T>
void init(const std::vector<T> &a) {
n = a.size();
info.assign(4 << std::__lg(n), Info());
tag.assign(4 << std::__lg(n), Tag());
auto build = [&](auto self, int p, int l, int r) -> void {
if (r - l == 1) {
info[p] = a[l];
return;
}
int m = l + r >> 1;
self(self, p * 2, l, m);
self(self, p * 2 + 1, m, r);
pull(p);
};
build(build, 1, 0, n);
}
void pull(int p) {
info[p] = info[p * 2] + info[p * 2 + 1];
}
void modify(int p, int l, int r, int x, const Info &v) {
if (r - l == 1) {
info[p] = v;
return;
}
int m = l + r >> 1;
if (x < m) {
modify(p * 2, l, m, x, v);
} else {
modify(p * 2 + 1, m, r, x, v);
}
pull(p);
}
void modify(int x, const Info &v) {
modify(1, 0, n, x, v);
}
void push(int p) {
apply(p * 2, tag[p]);
apply(p * 2 + 1, tag[p]);
tag[p] = Tag();
}
void apply(int p, const Tag &t) {
info[p].apply(t);
tag[p].apply(t);
}
Info query(int p, int l, int r, int x, int y) {
if (l >= y or r <= x) {
return Info();
}
if (l >= x and r <= y) {
return info[p];
}
push(p);
int m = l + r >> 1;
return query(p * 2, l, m, x, y) + query(p * 2 + 1, m, r, x, y);
}
Info query(int l, int r) {
return query(1, 0, n, l, r);
}
void Apply(int p, int l, int r, int x, int y, const Tag &t) {
if (l >= y or r <= x) {
return;
}
if (l >= x and r <= y) {
apply(p, t);
return;
}
push(p);
int m = l + r >> 1;
Apply(p * 2, l, m, x, y, t);
Apply(p * 2 + 1, m, r, x, y, t);
pull(p);
}
void Apply(int l, int r, const Tag &t) {
Apply(1, 0, n, l, r, t);
}
};
constexpr int A = 26;
constexpr int B = 29;
constexpr int P = int(1E9) + 7;
std::vector<i64> p;
struct Tag {
int x = 0;
void apply(const Tag &t) {
x = (x + t.x) % A;
}
};
struct Info {
int l = 0;
int r = 0;
std::array<i64, A> h{};
void apply(const Tag &t) {
if (t.x == 0) {
return;
}
auto h1 = h;
for (int x = 0; x < A; x++) {
h[(x + t.x) % A] = h1[x];
}
}
};
Info operator+(const Info &a, const Info &b) {
std::array<i64, A> h{};
for (int i = 0; i < A; i++) {
h[i] = (a.h[i] * p[b.r - b.l] + b.h[i]) % P;
}
return {a.l, std::max(a.r, b.r), h};
}
bool operator==(const Info &a, const Info &b) {
return a.h == b.h;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout << std::fixed << std::setprecision(12);
int n, m;
std::cin >> n >> m;
std::string s;
std::cin >> s;
p.assign(n + 1, 1);
for (int i = 0; i < n; i++) {
p[i + 1] = p[i] * B % P;
}
LSGT<Info, Tag> sgt(n);
LSGT<Info, Tag> rsgt(n);
std::array<i64, A> c{};
for (int i = 0; i < n; i++) {
c[s[i] - 'a'] = 1;
sgt.modify(i, {i, i + 1, c});
c[s[i] - 'a'] = 0;
}
for (int i = 0; i < n; i++) {
c[s[n - i - 1] - 'a'] = 1;
rsgt.modify(i, {i, i + 1, c});
c[s[n - i - 1] - 'a'] = 0;
}
while (m--) {
int o, l, r;
std::cin >> o >> l >> r;
l--;
if (o == 1) {
std::cout << (sgt.query(l, r) == rsgt.query(n - r, n - l) ? "YES\n": "NO\n");
} else {
int x;
std::cin >> x;
sgt.Apply(l, r, {x});
rsgt.Apply(n - r, n - l, {x});
}
}
return 0;
}