Day 4
Day 4
早上
起的比较晚, 但是正常吃早餐.
理论上我一顿早餐吃的是两片面包 + 果酱 + 两小块蛋糕 + 一杯咖啡, 可起晚了, 只有一块小蛋糕了.
人总会因为一时的贪心而错过什么吧.
上午
数据结构 I T2
给你若干个数字和若干次操作, 每次操作让一个数乘上 \(k\) 并让你返回整个序列的 \(gcd\) .
很显然能想到开质数颗线段树, 每个线段树维护 \(n\) 个数字表示第 \(i\) 个数字有最大因子 \(p^j\) .
然后每次对 \(k\) 进行质因数分解就好了.
你要卡很多东西. 很烦.
\(\mathcal{O}(n\sqrt{n}\log n)\)
#include <bits/stdc++.h>
using namespace std;
namespace OI {
#define int long long
#define endl "\n"
constexpr int maxn = 2e5+5;
int n, q;
int a[maxn];
namespace SegT {
#define int int32_t
struct node {
int val;
int lson, rson;
} seg[maxn << 6];
int tot;
inline int create() {
return ++tot;
}
inline int lson(int x) {
if (seg[x].lson)
return seg[x].lson;
return seg[x].lson = create();
}
inline int rson(int x) {
if (seg[x].rson)
return seg[x].rson;
return seg[x].rson = create();
}
void pushUp(int x) {
seg[x].val = min(seg[lson(x)].val, seg[rson(x)].val);
}
void add(int x, int L, int R, int pos, int k) {
if (L == R) {
seg[x].val += k;
return;
}
int mid = L + R >> 1;
if (pos <= mid)
add(lson(x), L, mid, pos, k);
else
add(rson(x), mid + 1, R, pos, k);
pushUp(x);
}
int root[maxn];
#define int long long
}
inline void add(int id, int x, int k) {
if (!SegT::root[id])
SegT::root[id] = SegT::create();
SegT::add(SegT::root[id], 1, n, x, k);
}
inline int query(int id) {
return SegT::seg[SegT::root[id]].val;
}
vector<int> prime;
bool vis[maxn];
constexpr int mod = 1e9+7;
int fpow(int a, int b) {
int res = 1;
while (b) {
if (b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void main() {
cin >> n >> q;
for (int i = 2; i <= 2e5; i++) {
if (!vis[i])
prime.push_back(i);
for (int p : prime) {
if (i * p > 2e5)
break;
vis[i * p] = 1;
if (i % p == 0)
break;
}
}
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[i] = a[i];
}
map<int, int> open;
for (int i = 1; i <= n; i++) {
int x = a[i];
for (int j = 0; j < prime.size(); j++) {
int p = prime[j];
if (p * p > x)
break;
int l = 0;
while (x % p == 0)
x /= p,
l++;
if (l)
add(p, i, l),
open[p] = query(p);
}
if (x > 1)
add(x, i, 1),
open[x] = query(x);
}
int gcd = 1;
for (auto p : open)
gcd = gcd * fpow(p.first, p.second) % mod;
while (q--) {
open.clear();
int i, k;
cin >> i >> k;
int x = k;
for (int j = 0; j < prime.size(); j++) {
int p = prime[j];
int before = query(p);
if (p * p > x)
break;
int l = 0;
while (x % p == 0)
x /= p,
l++;
if (l)
add(p, i, l);
int end = query(p);
if (end - before)
open[p] += end - before;
}
if (x > 1) {
int before = query(x);
add(x, i, 1);
int end = query(x);
if (end - before)
open[x] += end - before;
}
for (auto p : open) {
gcd = gcd * fpow(p.first, p.second) % mod;
}
cout << gcd << endl;
}
}
#undef int
#undef endl
}
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
OI::main();
return 0;
}
数据结构 II T4
给你一个字符串, 每次操作会把中间的一段排序, 让你求最终的序列.
我们维护 \(26\) 颗线段树表示每个区间有多少个当前字母.
当进行操作的时候我们把区间中字母拿出来并按序区间覆盖堆回去就好了.
时间复杂度 \(\mathcal{O}(26n \log n)\) .
#include <bits/stdc++.h>
using namespace std;
namespace OI {
#define int long long
#define endl "\n"
constexpr int maxn = 1e5 + 5;
struct SegT {
struct node {
int val;
int lazyVal;
bool hasLazy;
} seg[maxn << 2];
inline void tagCover(int x, int L, int R, int k) {
seg[x].val = k * (R - L + 1);
seg[x].lazyVal = k;
seg[x].hasLazy = true;
}
void pushUp(int x) {
seg[x].val = seg[x << 1].val + seg[x << 1 | 1].val;
}
void pushDown(int x, int L, int R) {
if (seg[x].hasLazy) {
int mid = (L + R) >> 1;
tagCover(x << 1, L, mid, seg[x].lazyVal);
tagCover(x << 1 | 1, mid + 1, R, seg[x].lazyVal);
seg[x].hasLazy = false;
}
}
void modify(int x, int L, int R, int l, int r, int k) {
if (l <= L && R <= r) {
tagCover(x, L, R, k);
return;
}
pushDown(x, L, R);
int mid = (L + R) >> 1;
if (l <= mid)
modify(x << 1, L, mid, l, r, k);
if (r > mid)
modify(x << 1 | 1, mid + 1, R, l, r, k);
pushUp(x);
}
int query(int x, int L, int R, int l, int r) {
if (l <= L && R <= r)
return seg[x].val;
pushDown(x, L, R);
int mid = (L + R) >> 1;
int ans = 0;
if (l <= mid)
ans += query(x << 1, L, mid, l, r);
if (r > mid)
ans += query(x << 1 | 1, mid + 1, R, l, r);
return ans;
}
} seg[26];
int n, q;
void main() {
cin >> n >> q;
string str;
cin >> str;
str = ' ' + str;
for (int i = 1; i <= n; i++) {
seg[str[i] - 'a'].modify(1, 1, n, i, i, 1);
}
while (q--) {
int l, r, k;
cin >> l >> r >> k;
int beg, end, off;
if (k == 1)
beg = 0, end = 26, off = 1;
else
beg = 25, end = -1, off = -1;
int last = l;
for (int i = beg; i != end; i += off) {
int time = seg[i].query(1, 1, n, l, r);
seg[i].modify(1, 1, n, l, r, 0);
if (time) {
seg[i].modify(1, 1, n, last, last + time - 1, 1);
last = last + time;
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < 26; j++) {
if (seg[j].query(1, 1, n, i, i))
cout << (char)('a' + j);
}
}
cout << endl;
}
#undef int
#undef endl
}
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
OI::main();
return 0;
}
数据结构 II T1
我大抵是喝多了
我看这题特么像决策树
中午
去吃了个冷面, 凉的.
回机房打模拟退火玩, 还是 \(96pts\) .
顺手写了篇 文章 .
下午
??? 上来给我叫上去让我讲 T1 .
是不是因为我头像是势于吉.
??? 怎么 lyd 也被叫上去了.
在追杀 h3z 么
I T4
给你一个副对角线以上的矩形, 每次操作可以从对角线上沿着一个方向平推, 问每次推掉多少.
用两个线段树维护, 分别用 \(0 / 1\) 表示这一行 / 列是否被平推.
每次用二分查找最后一个 \(0\) 就可以知道推掉了多少.
我失去了手写数据结构的能力(其实是懒得写)就脑切了.
怎么还有 set 做法. 我这么喜欢用 set 的人都没想到.
I T5
给你一个未知的 \(01\) 串和若干信息, 每条信息告诉你 \([l, r]\) 是否存在 \(1\).
每次查询询问 \(a_i\) 是否可以推断一定是 \(1\) .
一个人一定有病, 那么一定他所在区间有病且其他人一定没病.
给也许有病的区间打个 \(tag\), 给必定没病的人打另一种 \(tag\). 如果在一个区间内 \(tag_A - tag_B = 1\) 那么那个人有病.
线段树维护.
依旧脑切不写.
突然发现那个一直 ak 的大佬是另一个人在冒名顶替. WTF
啊?
啥叫有人初二 Ag 了.
衣服上漏血了, 好诡异.
II T1
给你若干个有权线段, 让你选一个组能覆盖全集的线段保证线段权值极差最小.
按 \(w\) 排序, 然后双指针 \(l, r\) .
判定合不合法, 合法 \(l\) 右移不合法 \(r\) 右移动.
无脑线段树维护是否合法.
完事.
lyd 在么?
哇塞还在追!
?莫名其妙想到了小鸟。

乐嘉风起残云散!
zyq 来讲一下.
T3 是莫队我不会!
就差你了 lzy!
不是我衣服咋被染的越来越多了. 是冷面的问题么?
疼的要死, 先不写题了.
?怎么莫名其妙突然下课了.
\(啊啊啊啊啊啊啊啊啊啊啊\textcolor{red}{啊啊啊啊好疼好疼好疼}\)
晚饭
去买了个三明治, 然后喝了个咖啡.
然后进行欢乐的三国杀环节.


旁边有个哥们在写小说?我不清楚.
然后用大量时间调试 II T1 .
#include <bits/stdc++.h>
using namespace std;
namespace OI {
#define int long long
#define endl "\n"
constexpr int maxn = 1e6+5;
struct segment {
int l, r, w;
} segments[maxn];
int n, m;
struct node {
int mn;
int lazy;
} seg[maxn << 2];
void pushUp(int x) {
seg[x].mn = min(seg[x << 1].mn, seg[x << 1 | 1].mn);
}
inline void tagAdd(int x, int k) {
seg[x].mn += k;
seg[x].lazy += k;
}
void pushDown(int x) {
if (seg[x].lazy) {
tagAdd(x << 1, seg[x].lazy);
tagAdd(x << 1 | 1, seg[x].lazy);
seg[x].lazy = 0;
}
}
void add(int x, int L, int R, int l, int r, int k) {
if (l <= L && R <= r) {
tagAdd(x, k);
return;
}
pushDown(x);
int mid = (L + R) >> 1;
if (l <= mid)
add(x << 1, L, mid, l, r, k);
if (r > mid)
add(x << 1 | 1, mid + 1, R, l, r, k);
pushUp(x);
}
int query(int x, int L, int R, int l, int r) {
if (l <= L && R <= r)
return seg[x].mn;
pushDown(x);
int mid = (L + R) >> 1;
int ans = LONG_LONG_MAX;
if (l <= mid)
ans = min(ans, query(x << 1, L, mid, l, r));
if (r > mid)
ans = min(ans, query(x << 1 | 1, mid + 1, R, l, r));
return ans;
}
void main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int l, r, w;
cin >> l >> r >> w;
segments[i] = {l, r - 1, w};
}
sort(segments + 1, segments + 1 + n, [](segment a, segment b) {
return a.w < b.w;
});
int curL = 1, curR = 0, ans = LONG_LONG_MAX;
while (curR < n) {
while (curR < n && !query(1, 1, m, 1, m - 1))
curR++,
add(1, 1, m, segments[curR].l, segments[curR].r, 1);
while (curL <= curR && query(1, 1, m, 1, m - 1))
ans = min(ans, segments[curR].w - segments[curL].w),
add(1, 1, m, segments[curL].l, segments[curL].r, -1),
curL++;
}
cout << ans << endl;
}
#undef int
#undef endl
}
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
OI::main();
return 0;
}
好在过了.
晚自习
莫队算法
我们把整个区间分块, 然后对所有询问按下面规则离线排序:
- 第一关键字:左端点, 按所在块从小到大排序
- 第二关键字:若左端点所在块为奇数则按右端点所在块从小到大排序, 否则是从大到小排序
然后维护一个双指针, 对于每个查询, 我们要把两个指针移动到对应的查询区间并在每次进行一下 \(\mathcal{O}(1)\) 的更新.
复杂度 \(\mathcal{O}(n \sqrt{n})\)
伪代码:
// 分块
int B = sqrt(n);
#define block(i) (i / B + 1)
sort(queries.begin(), queries.end(), [](query a, query b) {
if (block(a.l) == block(b.l))
if (block(a.l) & 1)
return a.r < b.r;
else
return a.r > b.r;
return a.l < b.l;
});
int curL = 1, curR = 0;
for (query q : queries) {
while (curL > q.l)
add(--curL);
while (curR < q.r)
add(++curR);
while (curL < q.l)
remove(curL++);
while (curL > q.r)
remove(curR--);
}
II T3
莫队板子. 给你一个序列和若干个可离线询问, 问你在询问区间内异或和为 \(k\) 的区间有多少.
\(k\) 是一开始就给定的.
那还说啥了, 直接前缀和 + 莫队!
\(\mathcal{O}(n \sqrt n)\)
#include <bits/stdc++.h>
using namespace std;
namespace OI {
#define int long long
#define endl "\n"
constexpr int maxn = 2e6+5;
int a[maxn], cnt[maxn];
int ret[maxn], ans;
struct query {
int l, r, id;
} queries[maxn];
int n, m, k;
int B;
#define block(x) (x / B + 1)
void add(int i) {
ans += cnt[a[i] ^ k];
cnt[a[i]]++;
}
void remove(int i) {
cnt[a[i]]--;
ans -= cnt[a[i] ^ k];
}
void main() {
cin >> n >> m >> k;
B = sqrt(n);
for (int i = 1; i <= n; i++)
cin >> a[i],
a[i] = a[i] ^ a[i - 1];
for (int i = 1; i <= m; i++) {
cin >> queries[i].l >> queries[i].r;
queries[i].l--;
queries[i].id = i;
}
sort(queries + 1, queries + 1 + m, [](query a, query b) {
if (block(a.l) == block(b.l)) {
if (block(a.l) & 1)
return block(a.r) < block(b.r);
else
return block(a.r) > block(b.r);
}
return block(a.l) < block(b.l);
});
int curL = 1, curR = 0;
for (int i = 1; i <= m; i++) {
auto q = queries[i];
while (curL > q.l)
add(--curL);
while (curR < q.r)
add(++curR);
while (curL < q.l)
remove(curL++);
while (curR > q.r)
remove(curR--);
ret[q.id] = ans;
}
for (int i = 1; i <= m; i++)
cout << ret[i] << endl;
}
#undef int
#undef endl
}
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
OI::main();
return 0;
}
不知道为啥, 感觉做出来复杂度带根号的东西看起来就很帅.
II T2
题意 懒得写了.
神秘完了. 大概就是决定一个 \(\Delta\) 然后通过一些 \(\Delta\) 的性质判断.
因为这个操作的本质是对 \(\Delta\) 的一个子区间进行 \(+1, -1, +1, -1, \cdots\) 的操作.
如果 \(\sum \Delta \neq 0\) 则不可能成立, 如果 \(\exists S_\Delta > 0\) 则不可能成立, 否则答案是 \(|\min S_\Delta|\)
让我自己想我八百年想不出来啊. 这就是构造么, 畏惧了.
#include <bits/stdc++.h>
using namespace std;
namespace OI {
constexpr int maxn = 1e5+5;
int n, q;
int a[maxn], b[maxn], c[maxn];
int st[maxn][18];
int ts[maxn][18];
inline int qmax(int l, int r) {
int k = log2(r - l + 1);
return max(st[l][k], st[r - (1ll << k) + 1][k]);
}
inline int qmin(int l, int r) {
int k = log2(r - l + 1);
return min(ts[l][k], ts[r - (1ll << k) + 1][k]);
}
void main() {
cin >> n >> q;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
cin >> b[i];
for (int i = 1; i <= n; i++)
c[i] = a[i] - b[i];
for (int i = 1; i <= n; i++)
c[i] = c[i] + c[i - 1],
st[i][0] = c[i],
ts[i][0] = c[i];
for (int j = 1; j < 18; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++)
st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]),
ts[i][j] = min(ts[i][j - 1], ts[i + (1 << (j - 1))][j - 1]);
}
for (int i = 1; i <= q; i++) {
int l, r;
cin >> l >> r;
if (c[r] - c[l - 1] == 0 && qmax(l, r) - c[l - 1] <= 0)
cout << abs(qmin(l, r) - c[l - 1]) << endl;
else
cout << -1 << endl;
}
}
}
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
OI::main();
return 0;
}
心血来潮用了个 ST 表.
实则线段树写累了快力竭了.
突然 emo……?
突然回忆起 zyq 昨天的一句话
难道他们觉得我 D1 打 \(40pts\) 是应该的吗
……
这种“应该”我大抵经历很久了吧.
主要在文化课, 在生活, 在方方面面.
有的时候自己都习惯了.
讲真的, 这些东西写进日记里很奇怪, 我似乎忘了这东西是要发出去的了.
罢了,
有何不敢?!

晚上
emm
zyq 把房卡丢了. 补办回去了.
zyq 一直在输麻将, lyd 在推音游, lzy 在打东方.
而我, 手机没电了无事可做.
Damn it.
最后还是玩了把飞行棋然后被暴虐.
...
Damn it.
平常的晚上.

浙公网安备 33010602011771号