Ynoi Easy Round 做题笔记
Ynoi Easy Round 做题笔记
题单:https://www.luogu.com.cn/training/663143#problems
持续更新中……
[Ynoi Easy Round 2020] TEST_8
给出一个长度为 的 串 (一个由 和 组成的序列,下标为 到 的整数)。
支持以下几种操作:
操作1:给出 ,将 串下标为 到 的一段重复 次并放回原位;
操作2:给出 ,将 串下标为 到 的一段带翻转地重复 次(具体地说,第 ()次重复时,若 的二进制表示中有奇数个 ,则这次重复时要左右反转,否则不变),最后放回原位;
操作3:给出 ,将 串下标为 到 的一段删除;
操作4:给出 ,求 串中从左到右第 个 的位置,若 超过 串中 的个数,则输出 。
, 串的长度始终不超过
时间限制 内存限制
考虑区间反转,复制等操作,想到平衡树维护。
对于前两个操作,直接倍增复制即可。
通过 Leafy Tree 的 Merge,能将复制 份的复杂度降到 ,总时间复杂度 。
ケロシの代码
const int N = 1e5 + 5;
const int M = 5e7 + 5;
int n, m;
string s;
int rt, tot, ls[M], rs[M], sz[M], F[M];
bool rev[M];
int f[32], g[32], b[32], tp;
int add() {
return ++ tot;
}
int add(int x) {
int u = add();
sz[u] = 1;
F[u] = x;
return u;
}
int cp(int u) {
int v = add();
ls[v] = ls[u];
rs[v] = rs[u];
sz[v] = sz[u];
F[v] = F[u];
rev[v] = rev[u];
return v;
}
void up(int u) {
sz[u] = sz[ls[u]] + sz[rs[u]];
F[u] = F[ls[u]] + F[rs[u]];
}
int up(int l, int r) {
int u = add();
ls[u] = l, rs[u] = r;
up(u);
return u;
}
int build(int l, int r) {
if(l == r) {
return add(s[l] - '0');
}
int mid = l + r >> 1;
return up(build(l, mid), build(mid + 1, r));
}
int push(int u) {
int v = cp(u);
swap(ls[v], rs[v]);
rev[v] ^= 1;
return v;
}
void down(int u) {
if(! rev[u] || ! ls[u]) return;
ls[u] = push(ls[u]);
rs[u] = push(rs[u]);
rev[u] = 0;
}
int merge(int u, int v) {
if(! u || ! v) return u | v;
if(sz[u] <= sz[v] * 4 && sz[v] <= sz[u] * 4) {
return up(u, v);
}
if(sz[u] >= sz[v]) {
down(u);
int l = ls[u], r = rs[u];
if(sz[l] * 4 > (sz[u] + sz[v])) return merge(l, merge(r, v));
down(r);
return merge(merge(l, ls[r]), merge(rs[r], v));
}
else {
down(v);
int l = ls[v], r = rs[v];
if(sz[r] * 4 > (sz[u] + sz[v])) return merge(merge(u, l), r);
down(l);
return merge(merge(u, ls[l]), merge(rs[l], r));
}
}
void split(int u, int p, int & x, int & y) {
if(! u || ! p) {
x = 0, y = u;
return;
}
if(sz[u] == p) {
x = u, y = 0;
return;
}
down(u);
if(p <= sz[ls[u]]) {
split(ls[u], p, x, y);
y = merge(y, rs[u]);
}
else {
split(rs[u], p - sz[ls[u]], x, y);
x = merge(ls[u], x);
}
}
int query(int u, int k) {
if(! ls[u]) {
return 1;
}
down(u);
if(k <= F[ls[u]]) return query(ls[u], k);
else return query(rs[u], k - F[ls[u]]) + sz[ls[u]];
}
void solve() {
cin >> n;
cin >> s; s = ' ' + s;
rt = build(1, n);
cin >> m;
REP(_, m) {
int opt; cin >> opt;
if(opt == 1) {
int l, r, k;
cin >> l >> r >> k;
int x, y, z;
split(rt, r, x, z);
split(x, l - 1, x, y);
int len = log2(k) + 1;
f[0] = y;
FOR(i, 1, len) f[i] = merge(f[i - 1], f[i - 1]);
y = 0;
FOR(i, 0, len) if(k >> i & 1)
y = merge(y, f[i]);
rt = merge(merge(x, y), z);
}
if(opt == 2) {
int l, r, k;
cin >> l >> r >> k;
int x, y, z;
split(rt, r, x, z);
split(x, l - 1, x, y);
int len = log2(k) + 1;
f[0] = y; g[0] = push(y);
FOR(i, 1, len) {
f[i] = merge(f[i - 1], g[i - 1]);
g[i] = merge(g[i - 1], f[i - 1]);
}
int o = 0; tp = 0;
ROF(i, len, 0) if(k >> i & 1) {
b[++ tp] = (o ? g[i] : f[i]);
o ^= 1;
}
y = 0;
ROF(i, tp, 1) y = merge(b[i], y);
rt = merge(merge(x, y), z);
}
if(opt == 3) {
int l, r;
cin >> l >> r;
int x, y, z;
split(rt, r, x, z);
split(x, l - 1, x, y);
rt = merge(x, z);
}
if(opt == 4) {
int k;
cin >> k;
if(F[rt] < k) cout << - 1 << endl;
else cout << query(rt, k) << endl;
}
}
}
[Ynoi Easy Round 2020] TEST_100
给定一个长为 的序列 ,每个位置是一个线性变换 ,每次查询给出一个区间 和一个值 ,依次令 从 到 ,访问每个元素 ,将 变为 ,求结束后的 的值。
时间限制 内存限制
首先有一个分块的思路,块长设 ,预处理出每一块的函数复合。
然后对于每次查询,暴力算散块的函数,整块直接用预处理的值。
此处时间复杂度 。
考虑如何算一整块的函数复合。
正过来算比较困难,考虑倒过来,从后往前。
假设上一个复合为 ,那么这一步就是算新复合 。
不难发现这一步就是对 进行复制和反转,使用平衡树完成这些操作即可。
时间复杂度 。
ケロシの代码
const int N = 1e5 + 5;
const int V = 1e5;
const int M = 3e7 + 5;
const int B = sqrt(N) * 2;
int n, m, sn, a[N];
int st[N], ed[N], p[N];
int f[N / B + 5][N];
int tot, ls[M], rs[M], sz[M];
int L[M], R[M], len[M];
bool rev[M];
int add() {
return ++ tot;
}
int add(int l, int r) {
int u = add();
sz[u] = 1;
L[u] = l;
R[u] = r;
len[u] = abs(r - l) + 1;
return u;
}
int cp(int u) {
int v = add();
ls[v] = ls[u];
rs[v] = rs[u];
sz[v] = sz[u];
L[v] = L[u];
R[v] = R[u];
len[v] = len[u];
rev[v] = rev[u];
return v;
}
void up(int u) {
sz[u] = sz[ls[u]] + sz[rs[u]];
len[u] = len[ls[u]] + len[rs[u]];
}
int up(int l, int r) {
int u = add();
ls[u] = l, rs[u] = r;
rev[u] = 0;
up(u);
return u;
}
int push(int u) {
int v = cp(u);
if(ls[u]) {
swap(ls[v], rs[v]);
rev[v] ^= 1;
}
else {
swap(L[v], R[v]);
}
return v;
}
void down(int u) {
if(! rev[u] || ! ls[u]) return;
ls[u] = push(ls[u]);
rs[u] = push(rs[u]);
rev[u] = 0;
}
int merge(int u, int v) {
if(! u || ! v) return u | v;
if(sz[u] <= sz[v] * 4 && sz[v] <= sz[u] * 4) {
return up(u, v);
}
if(sz[u] >= sz[v]) {
down(u);
int l = ls[u], r = rs[u];
if(sz[l] * 4 > (sz[u] + sz[v])) return merge(l, merge(r, v));
down(r);
return merge(merge(l, ls[r]), merge(rs[r], v));
}
else {
down(v);
int l = ls[v], r = rs[v];
if(sz[r] * 4 > (sz[u] + sz[v])) return merge(merge(u, l), r);
down(l);
return merge(merge(u, ls[l]), merge(rs[l], r));
}
}
void split(int u, int k, int & x, int & y) {
if(! u || ! k) {
x = 0, y = u;
return;
}
if(len[u] == k) {
x = u, y = 0;
return;
}
if(! ls[u]) {
if(L[u] <= R[u]) {
x = add(L[u], L[u] + k - 1);
y = add(L[u] + k, R[u]);
}
else {
x = add(L[u], L[u] - k + 1);
y = add(L[u] - k, R[u]);
}
return;
}
down(u);
if(k <= len[ls[u]]) {
split(ls[u], k, x, y);
y = merge(y, rs[u]);
}
else {
split(rs[u], k - len[ls[u]], x, y);
x = merge(ls[u], x);
}
}
int kth(int u, int k) {
if(! ls[u]) {
if(L[u] <= R[u]) return L[u] + k - 1;
else return L[u] - k + 1;
}
down(u);
if(k <= len[ls[u]]) return kth(ls[u], k);
else return kth(rs[u], k - len[ls[u]]);
}
int reverse(int rt, int i) {
int x, y, z;
split(rt, a[i] + 1, x, z);
split(rt, V - a[i] + 1, y, z);
split(y, 1, z, y);
return merge(push(x), y);
}
void print(int u, int l, int p) {
if(! ls[u]) {
if(L[u] <= R[u]) {
FOR(i, 1, len[u])
f[l][p + i - 1] = L[u] + i - 1;
}
else {
FOR(i, 1, len[u])
f[l][p + i - 1] = L[u] - i + 1;
}
return;
}
down(u);
print(ls[u], l, p);
print(rs[u], l, p + len[ls[u]]);
}
void build() {
for(int l = 1, r = B; l <= n; l += B, r += B) {
sn ++;
st[sn] = l;
ed[sn] = min(r, n);
}
FOR(i, 1, sn) {
FOR(j, st[i], ed[i]) p[j] = i;
int rt = add(0, V);
ROF(j, ed[i], st[i]) rt = reverse(rt, j);
print(rt, i, 0);
}
}
int query(int l, int r, int x) {
int pl = p[l], pr = p[r];
if(pl == pr) {
FOR(i, l, r) x = abs(x - a[i]);
return x;
}
FOR(i, l, ed[pl]) x = abs(x - a[i]);
FOR(i, pl + 1, pr - 1) x = f[i][x];
FOR(i, st[pr], r) x = abs(x - a[i]);
return x;
}
void solve() {
cin >> n >> m;
FOR(i, 1, n) cin >> a[i];
build();
int lst = 0;
REP(_, m) {
int l, r, x;
cin >> l >> r >> x;
l ^= lst, r ^= lst, x ^= lst;
cout << (lst = query(l, r, x)) << endl;
}
}
考虑 polylog 做法。
对于区间询问不难想到线段树,所以考虑线段树套平衡树。
但是此时预处理的时间与空间复杂度巨大,考虑剪枝。
对于线段树上长度小于 的区间直接暴力扫。
对于线段树上长度大于 的区间,继续递归至小区间。
这两个剪枝可使预处理时间与空间复杂度减小。
时间复杂度 ,比上面的分块慢。
ケロシの代码
const int N = 1e5 + 5;
const int V = 1e5;
const int B = log2(V) * log(N);
const int U = N / log2(N) / 3;
const int M = 3e7 + 5;
int n, m, a[N];
int rt0, tot, ls[M], rs[M], sz[M];
int L[M], R[M], len[M];
bool rev[M];
int add() {
return ++ tot;
}
int add(int l, int r) {
int u = add();
sz[u] = 1;
L[u] = l;
R[u] = r;
len[u] = abs(r - l) + 1;
return u;
}
int cp(int u) {
int v = add();
ls[v] = ls[u];
rs[v] = rs[u];
sz[v] = sz[u];
L[v] = L[u];
R[v] = R[u];
len[v] = len[u];
rev[v] = rev[u];
return v;
}
void up(int u) {
sz[u] = sz[ls[u]] + sz[rs[u]];
len[u] = len[ls[u]] + len[rs[u]];
}
int up(int l, int r) {
int u = add();
ls[u] = l, rs[u] = r;
rev[u] = 0;
up(u);
return u;
}
int push(int u) {
int v = cp(u);
if(ls[u]) {
swap(ls[v], rs[v]);
rev[v] ^= 1;
}
else {
swap(L[v], R[v]);
}
return v;
}
void down(int u) {
if(! rev[u] || ! ls[u]) return;
ls[u] = push(ls[u]);
rs[u] = push(rs[u]);
rev[u] = 0;
}
int merge(int u, int v) {
if(! u || ! v) return u | v;
if(sz[u] <= sz[v] * 4 && sz[v] <= sz[u] * 4) {
return up(u, v);
}
if(sz[u] >= sz[v]) {
down(u);
int l = ls[u], r = rs[u];
if(sz[l] * 4 > (sz[u] + sz[v])) return merge(l, merge(r, v));
down(r);
return merge(merge(l, ls[r]), merge(rs[r], v));
}
else {
down(v);
int l = ls[v], r = rs[v];
if(sz[r] * 4 > (sz[u] + sz[v])) return merge(merge(u, l), r);
down(l);
return merge(merge(u, ls[l]), merge(rs[l], r));
}
}
void split(int u, int k, int & x, int & y) {
if(! u || ! k) {
x = 0, y = u;
return;
}
if(len[u] == k) {
x = u, y = 0;
return;
}
if(! ls[u]) {
if(L[u] <= R[u]) {
x = add(L[u], L[u] + k - 1);
y = add(L[u] + k, R[u]);
}
else {
x = add(L[u], L[u] - k + 1);
y = add(L[u] - k, R[u]);
}
return;
}
down(u);
if(k <= len[ls[u]]) {
split(ls[u], k, x, y);
y = merge(y, rs[u]);
}
else {
split(rs[u], k - len[ls[u]], x, y);
x = merge(ls[u], x);
}
}
int kth(int u, int k) {
if(! ls[u]) {
if(L[u] <= R[u]) return L[u] + k - 1;
else return L[u] - k + 1;
}
down(u);
if(k <= len[ls[u]]) return kth(ls[u], k);
else return kth(rs[u], k - len[ls[u]]);
}
int reverse(int rt, int i) {
int x, y, z;
split(rt, a[i] + 1, x, z);
split(rt, V - a[i] + 1, y, z);
split(y, 1, z, y);
return merge(push(x), y);
}
struct SgT {
int le[N << 2], ri[N << 2];
int rt[N << 2];
void build(int u, int l, int r) {
le[u] = l, ri[u] = r;
if(l == r) {
return;
}
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
if(r - l + 1 <= B || r - l + 1 > U) return;
if(rt[u << 1 | 1]) {
rt[u] = rt[u << 1 | 1];
ROF(i, mid, l) rt[u] = reverse(rt[u], i);
}
else {
rt[u] = rt0;
ROF(i, r, l) rt[u] = reverse(rt[u], i);
}
}
int query(int u, int l, int r, int x) {
if(l <= le[u] && ri[u] <= r && ri[u] - le[u] + 1 <= B) {
FOR(i, le[u], ri[u]) x = abs(x - a[i]);
return x;
}
if(l <= le[u] && ri[u] <= r && rt[u]) {
return kth(rt[u], x + 1);
}
int mid = le[u] + ri[u] >> 1;
if(l <= mid) x = query(u << 1, l, r, x);
if(mid < r) x = query(u << 1 | 1, l, r, x);
return x;
}
} t;
void solve() {
cin >> n >> m;
FOR(i, 1, n) cin >> a[i];
rt0 = add(0, V);
t.build(1, 1, n);
int lst = 0;
REP(_, m) {
int l, r, x;
cin >> l >> r >> x;
l ^= lst, r ^= lst, x ^= lst;
cout << (lst = t.query(1, l, r, x)) << endl;
}
}
[Ynoi Easy Round 2021] TEST_68
给定一棵 个节点的树,第 个点有一个权值 。
对每个点 ,其的答案为其所在子树外的所有点中,选两个可以相同的点 , 异或 的最大值,如果选不出两个点,则认为 的答案是 。
,
时间限制 内存限制
考虑先用字典树找出全局的最优点对 ,不难发现这个最优点对适用于不在 和 路径上的所有点。
不难发现可以通过从顶到下插入字典树来求得 和 路径上的所有答案。
时间复杂度 。
ケロシの代码
const int N = 5e5 + 5;
const int M = 4e7 + 5;
int n, p[N], b[N], len;
ll a[N];
int fi[N], ne[N], to[N], ecnt;
int tr[M][2], id[M], idx = 1;
ll ans[N], val;
void add(int u, int v) {
ne[++ ecnt] = fi[u];
to[ecnt] = v;
fi[u] = ecnt;
}
void clear() {
FOR(i, 1, idx) tr[i][0] = tr[i][1] = id[i] = 0;
idx = 1;
}
void insert(int i) {
int u = 1;
ROF(j, 61, 0) {
int o = a[i] >> j & 1;
if(! tr[u][o]) tr[u][o] = ++ idx;
u = tr[u][o];
}
id[u] = i;
}
pair<ll, int> query(ll x) {
int u = 1;
ll res = 0;
ROF(j, 61, 0) {
int o = x >> j & 1;
if(tr[u][o ^ 1]) {
res |= (1ll << j);
u = tr[u][o ^ 1];
}
else {
u = tr[u][o];
}
}
return {res, id[u]};
}
void dfs(int u) {
insert(u);
chmax(val, FI(query(a[u])));
for(int i = fi[u] ; i; i = ne[i]) {
int v = to[i];
dfs(v);
}
}
void solve() {
cin >> n;
FOR(i, 2, n) cin >> p[i];
FOR(i, 1, n) cin >> a[i];
FOR(i, 2, n) add(p[i], i);
FOR(i, 1, n) ans[i] = - 1;
FOR(i, 1, n) insert(i);
ll res = 0; int pl = 0, pr = 0;
FOR(i, 1, n) {
auto h = query(a[i]);
if(chmax(res, FI(h)))
pl = SE(h), pr = i;
}
val = 0;
int pos = pl;
while(pos) {
b[++ len] = pos;
pos = p[pos];
}
clear();
ROF(i, len, 1) {
int u = b[i];
ans[u] = val;
for(int j = fi[u]; j; j = ne[j]) {
int v = to[j];
if(v == b[i - 1]) continue;
dfs(v);
}
insert(u);
chmax(val, FI(query(a[u])));
}
val = 0; len = 0; pos = pr;
while(pos) {
b[++ len] = pos;
pos = p[pos];
}
clear();
ROF(i, len, 1) {
int u = b[i];
ans[u] = val;
for(int j = fi[u]; j; j = ne[j]) {
int v = to[j];
if(v == b[i - 1]) continue;
dfs(v);
}
insert(u);
chmax(val, FI(query(a[u])));
}
FOR(i, 1, n) if(ans[i] == - 1) ans[i] = res;
FOR(i, 1, n) cout << ans[i] << endl;
}
[Ynoi Easy Round 2021] TEST_152
转转有一个操作序列。
现在,有 个询问 ,。
每次询问,你初始有一个长度为 的序列 ,初值全是 。
现在我们从 到 执行这 个操作。
每个操作是将 ~ 赋值为 。
询问所有操作结束后整个 的序列所有数的和。
询问之间互相独立。
$ 1 \le n,m,q \le 5 \times 10^5 1 \le l_i \le r_i \le m$
时间限制 内存限制
考虑离线后丢进扫描线,从左往右扫描操作序列。
不难发现序列最多只有 个颜色段,考虑颜色段均摊。
然后把贡献按照时刻丢进树状数组上即可,每次查询一个时间段里的和即可。
时间复杂度 。
ケロシの代码
const int N = 5e5 + 5;
int n, m, q;
struct Modify {
int l, r, w;
} a[N];
vector<PII> e[N];
ll ans[N];
struct fenwick {
ll c[N];
int lowbit(int x) {
return - x & x;
}
void modify(int u, ll x) {
for(int i = u; i <= m; i += lowbit(i))
c[i] += x;
}
ll query(int u) {
ll res = 0;
for(int i = u; i; i -= lowbit(i))
res += c[i];
return res;
}
ll query(int l, int r) {
return query(r) - query(l - 1);
}
} t;
struct Node {
int l, r, t, w;
bool operator < (const Node & A) const {
return l < A.l;
}
};
set<Node> S;
set<Node> :: iterator split(int p) {
auto it = S.lower_bound({p, 0, 0, 0});
if(it != S.end() && it ->l == p) return it;
it --;
int l = it -> l, r = it -> r, t = it -> t, w = it -> w;
S.erase(it);
S.insert({l, p - 1, t, w});
return FI(S.insert({p, r, t, w}));
}
void solve() {
cin >> m >> n >> q;
FOR(i, 1, m) cin >> a[i].l >> a[i].r >> a[i].w;
FOR(i, 1, q) {
int l, r;
cin >> l >> r;
e[r].push_back({l, i});
}
S.insert({1, n, 1, 0});
FOR(i, 1, m) {
auto itr = split(a[i].r + 1);
auto itl = split(a[i].l);
for(auto it = itl; it != itr; it ++)
t.modify(it -> t, - 1ll * (it -> r - it -> l + 1) * it -> w);
S.erase(itl, itr);
S.insert({a[i].l, a[i].r, i, a[i].w});
t.modify(i, 1ll * (a[i].r - a[i].l + 1) * a[i].w);
for(auto h : e[i])
ans[SE(h)] = t.query(FI(h), i);
}
FOR(i, 1, q) cout << ans[i] << endl;
}
[Ynoi Easy Round 2022] TEST_105
给定一棵 个节点的树,第 个点有点权 。
有 次操作:
1 x y
:给出一个点 ,将其所在的极大同色连通块中每个点的点权修改为 。
2 x
:给出一个点 ,查询其所在的极大同色连通块的大小。
。
时间限制 内存限制
考虑每个连通块维护领域信息,但是只需要维护所有相邻儿子的信息。
每次把颜色相同的儿子合并,并且检查自己与父亲要不要合并,并更新自己在父亲中记录的颜色。
所以每个连通块开一个 map<int, list<int>>
维护每种颜色对应的儿子序列,list
是由链表实现,合并是 的。
然后每次合并进行启发式合并即可。
时间复杂度 。
ケロシの代码
const int N = 1e6 + 5;
int n, m, p[N], a[N], f[N], sz[N];
int fi[N], ne[N], to[N], ecnt;
map<int, list<int>> mp[N];
void add(int u, int v) {
ne[++ ecnt] = fi[u];
to[ecnt] = v;
fi[u] = ecnt;
}
int find(int u) {
if(f[u] == u) return u;
return f[u] = find(f[u]);
}
void merge(int u, int v) {
f[v] = u;
sz[u] += sz[v];
if(SZ(mp[u]) < SZ(mp[v])) swap(mp[u], mp[v]);
for(auto h : mp[v])
mp[u][FI(h)].splice(mp[u][FI(h)].end(), SE(h));
mp[v].clear();
}
void dfs(int u) {
for(int i = fi[u]; i; i = ne[i]) {
int v = to[i];
if(a[u] == a[v])
merge(find(u), v);
else
mp[find(u)][a[v]].push_back(v);
dfs(v);
}
}
void solve() {
cin >> n >> m;
FOR(i, 2, n) cin >> p[i];
FOR(i, 1, n) cin >> a[i];
FOR(i, 2, n) add(p[i], i);
FOR(i, 1, n) f[i] = i, sz[i] = 1;
dfs(1);
REP(_, m) {
int opt;
cin >> opt;
if(opt == 1) {
int u, x; cin >> u >> x;
u = find(u);
if(a[u] == x) continue;
auto h = mp[u][x];
for(int v : h) if(a[v] == x && find(v) != u)
merge(u, v);
if(a[find(p[u])] == x)
merge(find(p[u]), u);
else if(p[u])
mp[find(p[u])][x].push_back(u);
mp[find(u)][x].clear();
a[u] = x;
}
else {
int u; cin >> u;
u = find(u);
cout << sz[u] << endl;
}
}
}
[Ynoi Easy Round 2023] TEST_69
给定一个长为 的序列 ,有 次操作。
每次有两种操作:
1 l r x
:对于区间 内所有 ,将 变成 。
2 l r
:查询区间 的和,答案对 取模后输出。
,所有数值为 内的整数
时间限制 内存限制
不难发现每次操作要么不变,要么最少除以 ,所以考虑势能线段树直接维护。
考虑线段树上不用向下修改的条件,即为 。
所以同时维护区间和与区间 即可。
时间复杂度 。
ケロシの代码
const int N = 2e5 + 5;
const ll LNF = 1e18 + 128;
int n, m;
ll a[N];
ll gcd(ll x, ll y) {
if(! y) return x;
return gcd(y, x % y);
}
ll lcm(ll x, ll y) {
int128 val = (int128) x * y / gcd(x, y);
return (val > LNF ? LNF : (ll) val);
}
struct SgT {
int le[N << 2], ri[N << 2];
ll F[N << 2]; uint S[N << 2];
void pushup(int u) {
S[u] = S[u << 1] + S[u << 1 | 1];
F[u] = lcm(F[u << 1], F[u << 1 | 1]);
}
void build(int u, int l, int r) {
le[u] = l, ri[u] = r;
if(l == r) {
F[u] = a[l];
S[u] = a[l] % (1ll << 32);
return;
}
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
uint query(int u, int l, int r) {
if(l <= le[u] && ri[u] <= r) {
return S[u];
}
int mid = le[u] + ri[u] >> 1;
if(r <= mid) return query(u << 1, l, r);
if(mid < l) return query(u << 1 | 1, l, r);
return query(u << 1, l, r) + query(u << 1 | 1, l, r);
}
void modify(int u, int l, int r, ll x) {
if(F[u] != LNF && x % F[u] == 0) return;
if(le[u] == ri[u]) {
F[u] = gcd(F[u], x);
S[u] = F[u] % (1ll << 32);
return;
}
int mid = le[u] + ri[u] >> 1;
if(l <= mid) modify(u << 1, l, r, x);
if(mid < r) modify(u << 1 | 1, l, r, x);
pushup(u);
}
} t;
void solve() {
cin >> n >> m;
FOR(i, 1, n) cin >> a[i];
t.build(1, 1, n);
REP(_, m) {
int opt; cin >> opt;
if(opt == 1) {
int l, r; ll x;
cin >> l >> r >> x;
t.modify(1, l, r, x);
}
else {
int l, r;
cin >> l >> r;
cout << t.query(1, l, r) << endl;
}
}
}
[Ynoi Easy Round 2023] TEST_90
给定一个长度 的序列 。
共有 次询问,每次询问给定 ,求区间 中有多少 子区间 满足 ,且在区间 内出现过的数的个数为奇数。
,,
时间限制 内存限制
像这种区间颜色数问题,考虑扫描线,然后点 会对 产生 的贡献,因为需要计算颜色数为奇数的区间个数,所以每次直接将 进行反转,值为 即为颜色数为奇数。
因为是子区间计数,所以考虑使用历史和线段树维护即可。
时间复杂度 。
ケロシの代码
const int N = 1e6 + 5;
int n, m;
int a[N], pre[N], lst[N];
vector<PII> e[N];
ll ans[N];
struct SgT {
int le[N << 2], ri[N << 2], len[N << 2];
int F[N << 2]; ll H[N << 2];
int T[N << 2], C0[N << 2], C1[N << 2];
void pushup(int u) {
F[u] = F[u << 1] + F[u << 1 | 1];
H[u] = H[u << 1] + H[u << 1 | 1];
}
void push_rev(int u) {
F[u] = len[u] - F[u];
T[u] ^= 1;
swap(C0[u], C1[u]);
}
void push(int u, int c0, int c1) {
H[u] += 1ll * F[u] * c0;
H[u] += 1ll * (len[u] - F[u]) * c1;
C0[u] += c0;
C1[u] += c1;
}
void pushdown(int u) {
if(T[u]) {
push_rev(u << 1);
push_rev(u << 1 | 1);
T[u] = 0;
}
if(C0[u] || C1[u]) {
push(u << 1, C0[u], C1[u]);
push(u << 1 | 1, C0[u], C1[u]);
C0[u] = C1[u] = 0;
}
}
void build(int u, int l, int r) {
le[u] = l, ri[u] = r, len[u] = r - l + 1;
if(l == r) {
return;
}
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
void modify(int u, int l, int r) {
if(l <= le[u] && ri[u] <= r) {
push_rev(u);
return;
}
pushdown(u);
int mid = le[u] + ri[u] >> 1;
if(l <= mid) modify(u << 1, l, r);
if(mid < r) modify(u << 1 | 1, l, r);
pushup(u);
}
ll query(int u, int l, int r) {
if(l <= le[u] && ri[u] <= r) {
return H[u];
}
pushdown(u);
int mid = le[u] + ri[u] >> 1;
if(r <= mid) return query(u << 1, l, r);
if(mid < l) return query(u << 1 | 1, l, r);
return query(u << 1, l, r) + query(u << 1 | 1, l, r);
}
} t;
void solve() {
cin >> n;
FOR(i, 1, n) cin >> a[i];
FOR(i, 1, n) {
pre[i] = lst[a[i]];
lst[a[i]] = i;
}
cin >> m;
FOR(i, 1, m) {
int l, r;
cin >> l >> r;
e[r].push_back({l, i});
}
t.build(1, 1, n);
FOR(i, 1, n) {
t.modify(1, pre[i] + 1, i);
t.push(1, 1, 0);
for(auto h : e[i])
ans[SE(h)] = t.query(1, FI(h), i);
}
FOR(i, 1, m) cout << ans[i] << endl;
}
[Ynoi Easy Round 2024] TEST_133
给定序列 , 次操作:
修改操作:给出 ,将 的值增加 ;
查询操作:给出 ,问满足 的 对应 的历史最大值(即初值和每次修改操作后的值中最大的一个)。
,
时间限制 内存限制
由于有 的询问限制,考虑分块,对于每个整块排序,然后查询整块就二分即可。
对于历史最大值,散块暴力修改查询,整块修改打标记即可,然后记录前缀最小值以及历史最小值即可。
时间复杂度 。
ケロシの代码
const int N = 5e5 + 5;
const int B = sqrt(N);
const ll LNF = 1e18 + 128;
int n, m;
int sn, st[N], ed[N], p[N], id[N];
ll a[N], b[N], h[N], t[N], dp1[N], dp2[N];
void rebuild(int u) {
sort(id + st[u], id + ed[u] + 1, [&] (int x, int y) {
return a[x] < a[y];
});
dp1[st[u]] = a[id[st[u]]];
dp2[st[u]] = h[id[st[u]]];
FOR(i, st[u] + 1, ed[u]) dp1[i] = max(dp1[i - 1], a[id[i]]);
FOR(i, st[u] + 1, ed[u]) dp2[i] = max(dp2[i - 1], h[id[i]]);
}
void push(int u) {
FOR(i, st[u], ed[u]) chmax(h[i], a[i] + t[u]);
FOR(i, st[u], ed[u]) a[i] += b[u];
b[u] = t[u] = 0;
}
void build() {
for(int l = 1, r = B; l <= n; l += B, r += B) {
sn ++;
st[sn] = l;
ed[sn] = min(r, n);
}
FOR(i, 1, sn) {
FOR(j, st[i], ed[i]) p[j] = i;
FOR(j, st[i], ed[i]) id[j] = j;
rebuild(i);
}
}
void modify(int l, int r, ll x) {
int pl = p[l], pr = p[r];
if(pl == pr) {
push(pl);
FOR(i, l, r) a[i] += x;
FOR(i, l, r) chmax(h[i], a[i]);
rebuild(pl);
return;
}
push(pl); push(pr);
FOR(i, l, ed[pl]) a[i] += x;
FOR(i, l, ed[pl]) chmax(h[i], a[i]);
FOR(i, st[pr], r) a[i] += x;
FOR(i, st[pr], r) chmax(h[i], a[i]);
FOR(i, pl + 1, pr - 1) b[i] += x;
FOR(i, pl + 1, pr - 1) chmax(t[i], b[i]);
rebuild(pl); rebuild(pr);
}
ll query(int l, int r, ll x) {
int pl = p[l], pr = p[r];
ll res = - LNF;
if(pl == pr) {
FOR(i, l, r) if(a[i] + b[pl] < x) {
chmax(res, a[i] + t[pl]);
chmax(res, h[i]);
}
return res;
}
FOR(i, l, ed[pl]) if(a[i] + b[pl] < x) {
chmax(res, a[i] + t[pl]);
chmax(res, h[i]);
}
FOR(i, st[pr], r) if(a[i] + b[pr] < x) {
chmax(res, a[i] + t[pr]);
chmax(res, h[i]);
}
FOR(i, pl + 1, pr - 1) {
int L = st[i], R = ed[i], pos = - 1;
while(L <= R) {
int mid = L + R >> 1;
if(a[id[mid]] + b[i] < x) {
pos = mid;
L = mid + 1;
}
else {
R = mid - 1;
}
}
if(pos == - 1) continue;
chmax(res, dp1[pos] + t[i]);
chmax(res, dp2[pos]);
}
return res;
}
void solve() {
cin >> n >> m;
FOR(i, 1, n) cin >> a[i];
FOR(i, 1, n) h[i] = a[i];
build();
REP(_, m) {
int opt, l, r; ll x;
cin >> opt >> l >> r >> x;
if(opt == 1) {
modify(l, r, x);
}
else {
ll val = query(l, r, x);
if(val == - LNF) cout << "-inf" << endl;
else cout << val << endl;
}
}
}
[Ynoi Easy Round 2024] TEST_132
给定平面上 个互不相同的点 ,每个点有点权,初始为 ;
共 次操作:
修改操作:给定 ,将满足 的点的点权 修改为 ;
查询操作:给定 ,求满足 的点的点权 的和;
答案对 取模。
,,
时间限制 内存限制
考虑根号分治,对于每一个 ,若点的个数小于等于 则暴力修改。
若点的个数大于 ,则不难发现,每个询问中出现的这类点不超过 个,暴力扫一遍即可。
使用预处理 和查询 的光速幂,并把 取 即可。
时间复杂度 。
ケロシの代码
const int N = 1.2e6 + 5;
const int P = 1e9 + 7;
const int B = 2e3;
const int M = 260;
inline int add(int x, int y) { return (x + y < P ? x + y : x + y - P); }
inline void Add(int & x, int y) { x = (x + y < P ? x + y : x + y - P); }
inline int sub(int x, int y) { return (x < y ? x - y + P : x - y); }
inline void Sub(int & x, int y) { x = (x < y ? x - y + P : x - y); }
inline int mul(int x, int y) { return (1ll * x * y) % P; }
inline void Mul(int & x, int y) { x = (1ll * x * y) % P; }
int n, m;
struct Point {
int x, y, w;
} a[N];
struct Query {
int o, x;
} q[N];
vector<int> ex[N], qy[N];
int t[N], f[N], ans[N];
int f0[M], f1[M], f2[M], f3[M];
void init(int x) {
f0[0] = f1[0] = f2[0] = f3[0] = 1;
FOR(i, 1, 256) f0[i] = mul(f0[i - 1], x);
FOR(i, 1, 256) f1[i] = mul(f1[i - 1], f0[256]);
FOR(i, 1, 256) f2[i] = mul(f2[i - 1], f1[256]);
FOR(i, 1, 256) f3[i] = mul(f3[i - 1], f2[256]);
}
int fp(int y) {
return mul(
mul(f0[y & 255], f1[(y >> 8) & 255]),
mul(f2[(y >> 16) & 255], f3[(y >> 24) & 255])
);
}
void solve() {
cin >> n >> m;
FOR(i, 1, n) cin >> a[i].x >> a[i].y >> a[i].w;
FOR(i, 1, m) cin >> q[i].o >> q[i].x;
FOR(i, 1, m) if(q[i].o == 2) qy[q[i].x].push_back(i);
FOR(i, 1, n) ex[a[i].x].push_back(i);
FOR(i, 1, n) if(SZ(ex[i]) <= B)
for(auto u : ex[i])
Add(f[a[u].y], a[u].w);
FOR(i, 1, m) {
if(q[i].o == 1) {
if(SZ(ex[q[i].x]) <= B) {
for(auto u : ex[q[i].x]) {
Sub(f[a[u].y], a[u].w);
a[u].w = mul(a[u].w, a[u].w);
Add(f[a[u].y], a[u].w);
}
}
}
else {
ans[i] = f[q[i].x];
}
}
t[0] = 1;
FOR(i, 1, n) if(SZ(ex[i]) > B) {
FOR(j, 1, m) {
t[j] = t[j - 1];
if(q[j].o == 1 && q[j].x == i)
t[j] = (t[j] << 1) % (P - 1);
}
for(auto u : ex[i]) {
init(a[u].w);
for(auto p : qy[a[u].y])
Add(ans[p], fp(t[p]));
}
}
FOR(i, 1, m) if(q[i].o == 2) cout << ans[i] << endl;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App