ZR22省选10连测day5

ZR22省选10连测day5

史莱姆A

题目

题目描述

史莱姆有一串长度为 \(n\) 的彩灯,其中第 \(i\) 盏彩灯的颜色为 \(a_i\)。史莱姆将这串彩灯剪成若干段并装饰在房间里,每一段彩灯的美丽度为这段彩灯的颜色形成的集合的 \(\operatorname{mex}\),即第一个未出现在集合内的非负整数,例如 \(\operatorname{mex}\{1,2,4\}=0,\operatorname{mex}\{0,1,2,4\}=3\)

由于彩灯之间的奇特相互作用,整个房间的美丽度为每段彩灯的美丽度的积。史莱姆想知道所有剪彩灯的方案的美丽度和是多少。若两个方案中存在一对相邻的彩灯间,一个方案剪断,一个方案未剪断则视为不同的方案。答案对 \(998244353\) 取模。

输入格式

第一行一个正整数 \(n\)

第二行 \(n\) 个非负整数,第 \(i\) 个数 \(a_i\) 代表第 \(i\) 盏彩灯的颜色。

输出格式

一行一个数,表示答案对 \(998244353\) 取模后的值。

输入输出样例

a.in

4
0 1 0 2

a.out

8

更多样例见下发文件。

数据范围

对于全部数据 \(1\le n\le 10^6,0\le a_i\le n\)

对于前 \(30\%\) 的数据 \(n\le 5000\)

对于前 \(60\%\) 的数据 \(a_i\le 5000\)

时空限制

2s,512MB

AC代码
#include <bits/stdc++.h>
const int MAXN = 1e6 + 10;
template<typename T>
inline T min(const T &x, const T &y) {
	return x < y ? x : y;
}
template<typename T>
inline T max(const T &x, const T &y) {
	return x > y ? x : y;
}
const int MOD = 998244353;
#define lc (p << 1)
#define rc (p << 1 | 1)
#define mid ((l + r) >> 1)
int t[MAXN << 2];
void modify(int p, int l, int r, int x, int v) {
	if (l == r) {
		t[p] = v;
		return;
	}
	x <= mid ? modify(lc, l, mid, x, v) : modify(rc, mid + 1, r, x, v);
	t[p] = min(t[lc], t[rc]);
}
int query(int p, int l, int r, int x) {
	if (l == r) { return r; }
	return t[lc] < x ? query(lc, l, mid, x) : query(rc, mid + 1, r, x);
}
auto Mod = [] (int x) -> int {
	if (x >= MOD) {
		return x - MOD;
	}
	else if (x < 0) {
		return x + MOD;
	}
	else {
		return x;
	}
};
int N, ans, a[MAXN], f[MAXN], lst[MAXN], L[MAXN], R[MAXN];
void ins(int v, int l, int r) {
	if (R[v]) {
		ans = Mod(ans - (long long) v * Mod(f[R[v]] - f[L[v] - 1]) % MOD);
	}
	L[v] = (r && R[v]) ? L[v] : l;
	R[v] = r;
	if (R[v]) {
		ans = (ans + (long long) v * Mod(f[R[v]] - f[L[v] - 1])) % MOD;
	}
	return;
}
int main() {
	scanf("%d", &N);
	f[1] = 1;
	for (int i = 1, l, r, v; i <= N; i++) {
		scanf("%d", a + i);
		modify(1, 0, N, a[i], lst[a[i]] = i);
		if (R[a[i]]) {
			l = L[a[i]];
			r = R[a[i]];
			ins(a[i], 0, 0);
			for (; l <= r;) {
				v = query(1, 0, N, r);
				ins(v, max(l, lst[v] + 1), r);
				r = max(lst[v], l - 1);
			}
		}
		ins((!a[i]) ? 1 : 0, i, i);
		f[i + 1] = Mod(f[i] + ans);
	}
	printf("%d\n", ans);
	return 0;
}

首先考虑一个暴力的dp,令 \(f_i\) 为做到第 \(i\) 个彩灯的答案,然后答案就是 \(f_n\),考虑怎么转移,首先应该有 \(f_0=1\)

\[f_i=\sum_{j=0}^{i-1} f_j \times \text{mex}(j+1,i) \]

容易观察到,对于 \(\text{mex}\) 来说,从左到右具有不增性。

于是考虑能不能维护后缀的 \(\text{mex}\) 来快速转移。

最开始想的是,用类似于线段树的数据结构来做,然后每次就暴力让一个区间的 \(\text{mex}\) 整体加一个,然后这个做法的复杂度是错误的。可以被 5000 0 1 2 ... 5000 0 1 2 3 ... 5000 ... 这种数据卡掉。

然后,我们考虑直接对于每个 \(j\) 维护 \(\text{mex}=j\) 的这段左端点的区间 \([l,r]\)\(f_i\) 的前缀和可以快速转移。

考虑在末尾添一个数字 \(a_i\),那么肯定只有是原来等于 \(a_i\) 的这一段的区间的 \(\text{mex}\) 会被增大,设这段区间是 \([l,r]\),考虑求出来 \([r,i]\)\(\text{mex}\)\(x\),这可以通过维护一个权值线段树,通过查找最小的时间戳小于 \(r\) 的位置来实现。

然后,对于 \([\max(l,lst_x+1),r]\) 这段区间来说,他的 \(\text{mex}\) 都被修改为了 \(x\),然后未确定区间就变成了 \([l,\max(l,lst_x+1)-1]\),这样循环做就行。

证明一下这个做法的复杂度,考虑 \(\text{mex}\) 的种类不超过 \(N\),然后每次相当于是删掉了一个种类,添加了若干个种类,那么不会添加超过 \(O(N)\) 次的种类。

复杂度为 \(O(N\log N)\)

史莱姆 B

题目

题目描述

史莱姆有一个集合 \(S\),初始是空集。现在有 \(n\) 次操作,每次操作为以下两种之一:

  1. 向集合 \(S\) 中插入一个数 \(w\),保证此时集合 \(S\) 中没有 \(w\)
  2. 选择区间 \([0,w]\) 中的一个数 \(x\) 和集合 \(S\) 中互不相同的两个数 \(i,j\),你需要最小化 \((x+i)\oplus(x+j)\) 并输出这个最小值。其中 \(\oplus\) 表示二进制下的异或,保证此时集合 \(S\) 中有至少两个数。

输入格式

第一行两个正整数 \(V,n\),其中 \(V\) 在数据范围中有介绍。

接下来 \(n\) 行,每行两个整数 \(op,w\),其中 \(op\) 表示第几个操作。

输出格式

对于每一个 \(2\) 操作输出一行一个整数表示答案。

输入输出样例

b.in
7 5
1 85
1 69
1 24
1 82
2 71
b.out
3

更多样例见下发文件。

样例解释

\(x=2,i=82,j=85\) 时异或值最小。

数据范围

对于所有数据保证 \(3\le n\le 10^5,1\le op\le 2,0\le w<2^V\)

子任务 \(V=\) 数据范围 特殊性质 分值
1 7 \(n\le 10^2\) 15
2 20 \(n\le 10^5\) 只有最后一次操作 \(op=2\) 20
3 20 \(n\le 10^5\) 30
4 40 \(n\le 10^5\) 35

时空限制

1s,512MB
AC代码
#include <bits/stdc++.h>
using std::cin;
using std::cout;
using std::map;
using std::set;
int V, N;
set<long long> S;
map<long long, long long> mp;
int main() {
	std::ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> V >> N;
	auto mfy = [&] (long long p, long long v) -> void {
		auto it = mp.upper_bound(p);
		if (it != mp.begin() && std::prev(it)->second <= v) {
			return;
		}
		while (it != mp.end() && it->second >= v) {
			mp.erase(it++);
		}
		mp[p] = v;
		return;
	};
	auto ins = [&] (long long x, long long y) -> void {
		mfy(0, x ^ y);
		for (int i = 0; i <= V; ++i) {
			long long z = (~x & ((1LL << i) - 1)) + 1;
			mfy(z, (x + z) ^ (y + z));
			z = (~y & ((1LL << i) - 1)) + 1;
			mfy(z, (x + z) ^ (y + z));
		}
		return;
	};
	while (N--) {
		int op;
		long long W;
		cin >> op >> W;
		if (op == 1) {
			auto it = S.insert(W).first;
			if (it != S.begin()) {
				ins(*std::prev(it), W);
			}
			if (std::next(it) != S.end()) {
				ins(W, *std::next(it));
			}
		}
		else {
			cout << std::prev(mp.upper_bound(W))->second << '\n';
		}
	}
	return 0;
}

观察三个性质

  • \(b\oplus a \ge b - a\)
  • \(a\le b \le c\),则必有 \(a\oplus c \ge \min (a \oplus b,b\oplus c)\)。证明:可以考虑 \(a \oplus c\) 的最高位的 \(1\),后面两个异或中必有一个这位不是 \(1\)
  • 对于一对相邻的数字 \((i,j)\),只保留有用的 \(x\),这样的 \(x\)\(O(V)\) 个的。且一定是让 \(i+x\) 或者 \(j+x\),末尾有 \(k\) 个零的最小的 \(x\)。证明就考虑,如果这样情况存在的话,\(x\) 变大之后答案如果 \(j+x\) 不向 \(k+1\) 进位的话,答案一定不会更好。

于是直接用 set 维护每个有用的 \(x\) 即可,时间复杂度为 \(NV\log (NV)\)

不过跑不满就是了。

史莱姆 C

题目

题目描述

史莱姆有一张无向图,最开始图仅有一个 \(0\) 号节点。现在有 \(n\) 次操作,每次操作为以下 \(5\) 种之一(不妨假设每次操作前这张图的节点编号区间为 \([l,r]\)):

  1. 删去 \(l\) 号节点,并删去 \(l\) 号节点连接的所有边。
  2. 删去 \(r\) 号节点,并删去 \(r\) 号节点连接的所有边。
  3. 增加 \(l-1\) 号节点,并连接 \(\min(k-1,r-l+1)\) 条边,第 \(i\) 条边连接 \((l-1,l-1+i)\),边有边权。
  4. 增加 \(r+1\) 号节点,并连接 \(\min(k-1,r-l+1)\) 条边,第 \(i\) 条边连接 \((r+1,r+1-i)\),边有边权。
  5. 对当前图询问最小生成树的边权和。

输入保证任意时刻 \(l\le r\)

输入格式

第一行三个正整数 \(seed,k,n\),其中 \(seed\) 表示随机数生成器的种子。

接下来 \(n\) 行,每行一个正整数 \(op\),表示第几个操作。当 \(op=3/4\) 时,为了减少输入,你需要调用 \(\min(k-1,r-l+1)\) 次随机数生成器来获得边权,第 \(i\) 次调用表示第 \(i\) 条边的边权。

随机数生成器:

namespace qwq{
    std::mt19937 eng;
    void init(int Seed){eng.seed(Seed);}
    int readW(){return uniform_int_distribution<int>(0,1000000000)(eng);}
}

当你输入了 \(seed\) 后需要调用 qwq::init(seed) 来初始化,获得边权时调用 qwq::readW()

输出格式

对于每一个 \(5\) 操作输出一行一个整数表示答案。

输入输出样例

c.in
20220220 4 4
4
4
4
5
c.out
1139655038

更多样例见下发文件。

样例解释

询问时有边:

(0,1,780392573)
(1,2,852196855)
(0,2,494487013)
(2,3,57484417)
(1,3,895195425)
(0,3,301778048)

数据范围

对于所有数据满足 \(2\le k\le 10,1\le n\le 5\cdot10^5,1\le op\le 5,1\le seed\le 10^9\)

子任务 数据范围 特殊性质 分值
1 \(n\le 10^3\) 15
2 \(n\le 10^5\) 没有操作 \(1,2\) 20
3 \(n\le 10^5\) 没有操作 \(1,3\) 20
4 \(n\le 10^5\) \(k\) 次操作均为 \(4\) 操作,且之后的任意时刻 \(l\le 0,k\le r\) 20
5 \(n\le 5\cdot10^5\) 25

时空限制

2s,1536MB
AC代码
#include <bits/stdc++.h>
using std::cin;
using std::cout;
using std::cerr;
using std::mt19937;
using std::uniform_int_distribution;
using std::sort;
int read() {
	int x = 0;
	char ch = getchar();
	while (!isdigit(ch)) {
		ch = getchar();
	}
	while (isdigit(ch)) {
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return x;
}
template<typename T>
inline bool in(const T &x, const T &l, const T &r) {
	return x <= r && x >= l;
}
template<typename T>
inline T min(const T &x, const T &y) {
	return x < y ? x : y;
}
template<typename T>
inline T max(const T &x, const T &y) {
	return x > y ? x : y;
}
#define O(x) cerr << (x) << " : " << #x << '\n'
namespace qwq {
	mt19937 eng;
	void init(int Seed) {
		eng.seed(Seed);
		return;
	}
	int readW() {
		return uniform_int_distribution<int> (0, 1000000000)(eng);
	}
};
const int MAXK = 10, MAXN = 1e6 + 200;
struct Edge {
	int U, V, W, lac;
	Edge(int u = 0, int v = 0, int w = 0, int lst = 0) {
		U = u;
		V = v;
		W = w;
		lac = lst;
	}
	bool operator < (const Edge &a) const {
		return W < a.W;
	}
} e[MAXK * 8];
int ecnt, H[MAXN * 2];
auto add_edge(int U, int V, int W) {
	e[ecnt] = Edge(U, V, W, H[U]);
	H[U] = ecnt++;
	e[ecnt] = Edge(V, U, W, H[V]);
	H[V] = ecnt++;
}
int K, L, R, mL, mR, oL, oR, flag1, flag2, Fa[MAXN], W[MAXN][MAXK * 2], treecnt[MAXN], ok[MAXN], ew[MAXN];
long long ANS, ans[MAXN];
Edge tree[MAXN][MAXK * 4], mtr[MAXK];
int mn[MAXN], sc;
int find(int x) {
	return x == Fa[x] ? x : Fa[x] = find(Fa[x]);
}
void dfs(int x, int lst, int qjy) {
	ok[x] = in(x, oL, oR) || in(x, mL, mR) ? x : 0;
	for (int i = H[x], v; ~i; i = e[i].lac) {
		v = e[i].V;
		if (v == lst) {
			continue;
		}
		dfs(v, x, qjy);
		if (!ok[v]) {
			ans[qjy] += e[i].W;
		}
		else if (!ok[x]) {
			ok[x] = ok[v];
			ans[qjy] += min(e[i].W, ew[v]);
			ew[x] = max(ew[v], e[i].W);
		}
		else {
			if (ok[x] != x) {
				tree[qjy][++treecnt[qjy]] = Edge(x, ok[x], ew[x]);
				ok[x] = x;
			}
			tree[qjy][++treecnt[qjy]] = Edge(x, ok[v], max(e[i].W, ew[v]));
			ans[qjy] += min(e[i].W, ew[v]);
		}
	}
	if (ok[x] == x) {
		ew[x] = 0;
	}
	H[x] = -1;
}
void ins(int x, int ty) {
	static Edge qjy;
	int y = x + ty;
	ans[x] = ans[y];
	treecnt[x] = ecnt = 0;
	for (int i = 1; i < K; ++i) {
		tree[x][++treecnt[x]] = Edge (x, x + i * ty, W[x][K + i * ty]);
	}
	sort(tree[x] + 1, tree[x] + K);
	for (int i = 0; i < K; ++i) {
		Fa[x + i * ty] = x + i * ty;
	}
	for (int i = 1; i <= treecnt[y]; ++i) {
		Fa[tree[y][i].U] = tree[y][i].U;
		Fa[tree[y][i].V] = tree[y][i].V;
	}
	for (int i = 1, j = 1; i <= treecnt[x] || j <= treecnt[y]; ) {
		if (j > treecnt[y] || (i <= treecnt[x] && tree[x][i] < tree[y][j])) {
			qjy = tree[x][i++];
		}
		else {
			qjy = tree[y][j++];
		}
		if (find(qjy.U) != find(qjy.V)) {
			add_edge(qjy.U, qjy.V, qjy.W);
			Fa[Fa[qjy.U]] = Fa[qjy.V];
		}
	}
	if (ty == 1) {
		oL = x;
		oR = x + K - 2;
	}
	else {
		oL = x - K + 2;
		oR = x;
	}
	dfs(x, treecnt[x] = 0, x);
	sort(tree[x] + 1, tree[x] + treecnt[x] + 1);
}
void rebuild() {
	flag2 = 1;
	if (R - L + 1 < K) {
		mL = L;
		mR = R;
	}
	else {
		mL = L + (R - L - K) / 2 + 1;
		mR = mL + K - 2;
	}
	treecnt[mL] = treecnt[mR] = ans[mL] = ans[mR] = 0;
	for (int i = mL - 1; i >= L; --i) {
		ins(i, 1);
	}
	for (int i = mR + 1; i <= R; ++i) {
		ins(i, -1);
	}
	return;
}
int main() {
	// freopen("1.in", "r", stdin);
	// freopen("1.out", "w", stdout);
	memset(H, -1, sizeof H);
	L = R = mL = mR = MAXN / 2;
	qwq::init(read());
	K = read();
	for (int Q = read(), opt; Q--; ) {
		opt = read();
		flag1 |= opt != 5;
		switch(opt) {
		case 1: {
			if (++L > mL) {
				rebuild();
			}
		} break;
		case 2: {
			if (--R < mR) {
				rebuild();
			}
		} break;
		case 3: {
			--L;
			for (int i = 1, mx = min(K - 1, R - L); i <= mx; ++i) {
				W[L][K + i] = W[L + i][K - i] = qwq::readW();
			}
			if (R - L + 1 < K) {
				rebuild();
			}
			else {
				ins(L, 1);
			}
		} break;
		case 4: {
			++R;
			for (int i = 1, mx = min(K - 1, R - L); i <= mx; ++i) {
				W[R][K - i] = W[R - i][K + i] = qwq::readW();
			}
			if (R - L + 1 < K) {
				rebuild();
			}
			else {
				ins(R, -1);
			}
		} break;
		case 5: {
			if (!flag1) {
				cout << ANS << '\n';
				break;
			}
			if (flag2) {
				sc = 0;
				for (int i = mL; i <= mR; ++i) {
					Fa[i] = mn[i] = 0;
				}
				for (int i = mL, o = mL, tmp, id; i < mR; ++i) {
					Fa[o] = 1;
					tmp = INT_MAX;
					for (int j = mL; j <= mR; ++j) {
						if (!Fa[j]) {
							if (!mn[j] || W[j][mn[j] - j + K] > W[j][o - j + K]) {
								mn[j] = o;
							}
							if (W[j][mn[j] - j + K] < tmp) {
								tmp = W[j][mn[j] - j + K];
								id = j;
							}
						}
					}
					o = id;
					mtr[++sc] = Edge(o, mn[o], W[o][mn[o] - o + K]);
				}
				sort(mtr + 1, mtr + sc + 1);
			}
			if (L == mL && R == mR) {
				ANS = 0;
				for (int i = 1; i <= sc; ++i) {
					ANS += mtr[i].W;
				}
			}
			else {
				ANS = ans[L] + ans[R];
				for (int i = 1; i <= treecnt[L]; ++i) {
					Fa[tree[L][i].U] = tree[L][i].U;
					Fa[tree[L][i].V] = tree[L][i].V;
				}
				for (int i = 1; i <= treecnt[R]; ++i) {
					Fa[tree[R][i].U] = tree[R][i].U;
					Fa[tree[R][i].V] = tree[R][i].V;
				}
				Edge qjy;
				for (int i = 1, j = 1, t = 1; i <= treecnt[L] || j <= treecnt[R] || t <= sc; ) {
					if (t <= sc && (i > treecnt[L] || mtr[t] < tree[L][i]) && (j > treecnt[R] || mtr[t] < tree[R][j])) {
						qjy = mtr[t++];
					}
					else if (i <= treecnt[L] && (j > treecnt[R] || tree[L][i] < tree[R][j])) {
						qjy = tree[L][i++];
					}
					else {
						qjy = tree[R][j++];
					}
					if (find(qjy.U) != find(qjy.V)) {
						Fa[Fa[qjy.U]] = Fa[qjy.V];
						ANS += qjy.W;
					}
				}
			}
			flag1 = flag2 = 0;
			printf("%lld\n", ANS);
		} break;
		}
	}
	
}

这道题很强啊。好久才明白 Orz starusc 小天使。

不过数据随的,不知道有没有啥更好的办法。

我们考虑能不能维护 \([l,0]\),然后 \([1,k-1]\)\([k,r]\) 这些位置的最小生成树,然后查询的时候就暴力把他们三个合并起来跑。

这样复杂度会爆炸来着。

不过我们发现,如果合并 \([l,0]\)\([1,k-1]\) 这两个生成树的话,只可能是 \([1,k-1]\) 这些点之间路径上最大的边才可能是被删去的边。

然后建立关于在 \([l,0]\) 这些最小生成树中建立 \([1,k-1]\) 这些点的虚树,但是发现不好加点了,于是要建立 \([1,k-1]\cup[l,l+k-2]\) 这些点的虚树,方便转移到下一个位置,虚树的边的权值就是路径上的最大值。

然后总点数就是 \(O(K)\) 的,于是我们可以把这些最小生成树的每个前缀暴力存起来,这样就能处理减去某个点。

然后,右边的是同理。

这样子可以做完 subtask4,因为 \([1,k]\) 可能会没。

这时候采取一种牛逼的方法,就是没得时候直接在中间位置将这个结构重构即可。

摊还分析一下,我们加入的一个位置,第 \(i\) 次重构的贡献是 \(\frac{k\log k}{2^{i-1}}\),那么每个加入的贡献不超过 \(2k\log k\)(无论多少次重构)。考虑最多插入 \(N\) 次,最多重构 \(N\) 次。

然后对于较小的点数的生成树,直接采用 \(\text{prim}\),由于常数较小,跑的很好。

总复杂度为 \(O(NK^2)\),但是实际的实现起来的瓶颈在于 \(O(NK\log K)\) 部分。

posted @ 2022-02-21 00:03  siriehn_nx  阅读(149)  评论(0编辑  收藏  举报