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\)
容易观察到,对于 \(\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\) 次操作,每次操作为以下两种之一:
- 向集合 \(S\) 中插入一个数 \(w\),保证此时集合 \(S\) 中没有 \(w\)。
- 选择区间 \([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]\)):
- 删去 \(l\) 号节点,并删去 \(l\) 号节点连接的所有边。
- 删去 \(r\) 号节点,并删去 \(r\) 号节点连接的所有边。
- 增加 \(l-1\) 号节点,并连接 \(\min(k-1,r-l+1)\) 条边,第 \(i\) 条边连接 \((l-1,l-1+i)\),边有边权。
- 增加 \(r+1\) 号节点,并连接 \(\min(k-1,r-l+1)\) 条边,第 \(i\) 条边连接 \((r+1,r+1-i)\),边有边权。
- 对当前图询问最小生成树的边权和。
输入保证任意时刻 \(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)\) 部分。