Ynoi Easy Round 做题笔记

Ynoi Easy Round 做题笔记

题单:https://www.luogu.com.cn/training/663143#problems

持续更新中……

[Ynoi Easy Round 2020] TEST_8

给出一个长度为 n01S(一个由 01 组成的序列,下标为 1n 的整数)。
支持以下几种操作:
操作1:给出 l,r,k,将 01 串下标为 lr 的一段重复 k 次并放回原位;
操作2:给出 l,r,k,将 01 串下标为 lr 的一段带翻转地重复 k 次(具体地说,第 i1ik)次重复时,若 i1 的二进制表示中有奇数个 1,则这次重复时要左右反转,否则不变),最后放回原位;
操作3:给出 l,r,将 01 串下标为 lr 的一段删除;
操作4:给出 k,求 01 串中从左到右第 k1 的位置,若 k 超过 01 串中 1 的个数,则输出 1
1n,m10501 串的长度始终不超过 108
时间限制 3.00s 内存限制 1.00GB

考虑区间反转,复制等操作,想到平衡树维护。

对于前两个操作,直接倍增复制即可。

通过 Leafy Tree 的 O(logszaszb) Merge,能将复制 k 份的复杂度降到 O(logn),总时间复杂度 O(nlogn)

ケロシの代码
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

给定一个长为 n 的序列 a,每个位置是一个线性变换 x=|xai|,每次查询给出一个区间 [l,r] 和一个值 v,依次令 ilr ,访问每个元素 ai,将 v 变为 |vai|,求结束后的 v 的值。
1n,m,ai,v105
时间限制 2.00s 内存限制 2.00GB

首先有一个分块的思路,块长设 n,预处理出每一块的函数复合。

然后对于每次查询,暴力算散块的函数,整块直接用预处理的值。

此处时间复杂度 O(nn)

考虑如何算一整块的函数复合。

正过来算比较困难,考虑倒过来,从后往前。

假设上一个复合为 Fi+1(x),那么这一步就是算新复合 Fi(x)=Fi+1(|xai|)

不难发现这一步就是对 Fi+1 进行复制和反转,使用平衡树完成这些操作即可。

时间复杂度 O(nlogn+nn)

ケロシの代码
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 做法。

对于区间询问不难想到线段树,所以考虑线段树套平衡树。

但是此时预处理的时间与空间复杂度巨大,考虑剪枝。

对于线段树上长度小于 lognlogV 的区间直接暴力扫。

对于线段树上长度大于 nlogn 的区间,继续递归至小区间。

这两个剪枝可使预处理时间与空间复杂度减小。

时间复杂度 O(nlognlogV),比上面的分块慢。

ケロシの代码
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

给定一棵 n 个节点的树,第 i 个点有一个权值 ai
对每个点 x,其的答案为其所在子树外的所有点中,选两个可以相同的点 i,jai 异或 aj 的最大值,如果选不出两个点,则认为 x 的答案是 0
1n5×1050ai1018
时间限制 3.00s 内存限制 512.00MB

考虑先用字典树找出全局的最优点对 (u,v),不难发现这个最优点对适用于不在 1u1v 路径上的所有点。

不难发现可以通过从顶到下插入字典树来求得 1u1v 路径上的所有答案。

时间复杂度 O(nlogV)

ケロシの代码
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

转转有一个操作序列(li,ri,vi)
现在,有 q 个询问 l,r
每次询问,你初始有一个长度为 m 的序列 c,初值全是 0
现在我们从 lr 执行这 rl+1 个操作。
每个操作是将 c[li]~c[ri] 赋值为 vi
询问所有操作结束后整个 c 的序列所有数的和。
询问之间互相独立。
$ 1 \le n,m,q \le 5 \times 10^5 1 \le l_i \le r_i \le m$
时间限制 2.50s 内存限制 512.00MB

考虑离线后丢进扫描线,从左往右扫描操作序列。

不难发现序列最多只有 O(n) 个颜色段,考虑颜色段均摊。

然后把贡献按照时刻丢进树状数组上即可,每次查询一个时间段里的和即可。

时间复杂度 O(nlogn)

ケロシの代码
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

给定一棵 n 个节点的树,第 i 个点有点权 ai
m 次操作:
1 x y:给出一个点 x ,将其所在的极大同色连通块中每个点的点权修改为 y
2 x:给出一个点 x,查询其所在的极大同色连通块的大小。
1n,m,ai,x,y106
时间限制 3.00s 内存限制 512.00MB

考虑每个连通块维护领域信息,但是只需要维护所有相邻儿子的信息。

每次把颜色相同的儿子合并,并且检查自己与父亲要不要合并,并更新自己在父亲中记录的颜色。

所以每个连通块开一个 map<int, list<int>> 维护每种颜色对应的儿子序列,list 是由链表实现,合并是 O(1) 的。

然后每次合并进行启发式合并即可。

时间复杂度 O(nlog2n)

ケロシの代码
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

给定一个长为 n 的序列 a,有 m 次操作。
每次有两种操作:
1 l r x:对于区间 [l,r] 内所有 i,将 ai 变成 gcd(ai,x)
2 l r:查询区间 [l,r] 的和,答案对 232 取模后输出。
1n2105,1m5105,所有数值为 [1,1018] 内的整数
时间限制 2.00s 内存限制 512.00MB

不难发现每次操作要么不变,要么最少除以 2 ,所以考虑势能线段树直接维护。

考虑线段树上不用向下修改的条件,即为 lcmi=lraix

所以同时维护区间和与区间 lcm 即可。

时间复杂度 O(nlognlogV)

ケロシの代码
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

给定一个长度 n 的序列 a1,,an
共有 m 次询问,每次询问给定 l,r,求区间 [l,r] 中有多少 子区间 [i,j] 满足 lijr,且在区间 [i,j] 内出现过的数的个数为奇数。
1n1061m1061ain
时间限制 3.00s 内存限制 512.00MB

像这种区间颜色数问题,考虑扫描线,然后点 i 会对 [prei+1,i] 产生 1 的贡献,因为需要计算颜色数为奇数的区间个数,所以每次直接将 [prei+1,i] 进行反转,值为 1 即为颜色数为奇数。

因为是子区间计数,所以考虑使用历史和线段树维护即可。

时间复杂度 O(nlogn)

ケロシの代码
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

给定序列 a1,,anm 次操作:
修改操作:给出 l,r,x,将 al,al+1,,ar 的值增加 x
查询操作:给出 l,r,x,问满足 lir,ai<xi 对应 ai 的历史最大值(即初值和每次修改操作后的值中最大的一个)。
1n,m5×105|ai|,|x|109
时间限制 40.00s 内存限制 512.00MB

由于有 ai<x 的询问限制,考虑分块,对于每个整块排序,然后查询整块就二分即可。

对于历史最大值,散块暴力修改查询,整块修改打标记即可,然后记录前缀最小值以及历史最小值即可。

时间复杂度 O(nnlogn)

ケロシの代码
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

给定平面上 n 个互不相同的点 (xi,yi)i=1n,每个点有点权,初始为 vi
m 次操作:
修改操作:给定 X,将满足 xi=X 的点的点权 vi 修改为 vi2
查询操作:给定 Y,求满足 yi=Y 的点的点权 vi 的和;
答案对 109+7 取模。
1n,m1.2×1061xi,yi,X,Yn0vi109+6
时间限制 12.00s 内存限制 512.00MB

考虑根号分治,对于每一个 X,若点的个数小于等于 B 则暴力修改。

若点的个数大于 B,则不难发现,每个询问中出现的这类点不超过 nB 个,暴力扫一遍即可。

使用预处理 O(P14) 和查询 O(4) 的光速幂,并把 Bn 即可。

时间复杂度 O(nn)

ケロシの代码
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;
}
posted @   KevinLikesCoding  阅读(70)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示