2025.8.18 模拟赛
T1
想象学竞赛网站 CodeFancy 举办了 \(m\) 场比赛。你在 CodeFancy 上关注了 \(n\) 个账号,编号为 \(1\sim n\)。你知道这 \(n\) 个账号分别参加了 \(m\) 场比赛中的哪些。但是你发现可能存在一个人使用多个账号的情况,你想知道这 \(n\) 个账号的使用者最少共有多少人。
具体地,账号和使用者的关系由两条规则限定:
- 一个人在一场比赛中至多使用一个账号。
- 一个账号的使用者只有恰好一个人。
\(1\le n\le 10^5,1\le m\le 4\)
一开始被吓住了。
后来发现每个账号的参赛情况是可以用一个 \(0\sim 15\) 的二进制数表示出来的,然后我们交将其分成尽可能少的集合,使每个集合内两两按位与为 \(0\)。
直接做 \(m=4\),其余是更简单的。
将所有的账号的按参赛情况丢到桶里。
根据 \(popcount\) 来分组。
\(=4\) 的直接走。
\(=3,=1\) (\(14\to 1,13\to 2,11\to 4,7\to 8\))的匹配肯定不劣。
然后是 \(3\),\(2-2\),\(2-1-1\),\(2-1\),\(2\),\(1-1-1-1\),\(1-1-1\),\(1-1\),\(1\)。
赛时代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, m, k, ans;
int a[N], st[16];
void f(int x) { ans += st[x], st[x] = 0; }
void f(int x, int y) {
int w = min(st[x], st[y]);
st[x] -= w, st[y] -= w, ans += w;
}
void f(int x, int y, int z) {
int w = min(st[x], min(st[y], st[z]));
st[x] -= w, st[y] -= w, st[z] -= w, ans += w;
}
void f(int x, int y, int z, int o) {
int w = min(min(st[x], st[o]), min(st[y], st[z]));
st[x] -= w, st[y] -= w, st[z] -= w, st[o] -= w, ans += w;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1, x, y; i <= m; ++i)
for (cin >> x; x; --x)
cin >> y, a[y] |= (1 << i - 1);
for (int i = 1; i <= n; ++i)
++st[a[i]];
if (m == 1)
cout << st[1] << '\n';
if (m == 2) {
if (st[0])
cout << max(1, max(st[1], st[2]) + st[3]) << '\n';
else
cout << max(st[1], st[2]) + st[3] << '\n';
}
if (m == 3) {
f(7);
f(6, 1), f(5, 2), f(3, 4);
f(6), f(5), f(3);
f(1, 2, 4);
f(1, 2), f(1, 4), f(2, 4);
f(2), f(1), f(4);
cout << max(ans, 1) << '\n';
}
if (m == 4) {
f(15);
f(14, 1), f(13, 2), f(11, 4), f(7, 8);
f(14), f(13), f(11), f(7);
f(9, 6), f(10, 5), f(12, 3);
f(9, 2, 4), f(10, 1, 4), f(12, 1, 2), f(6, 8, 1), f(5, 8, 2), f(3, 4, 8);
f(9, 2), f(9, 4), f(10, 1), f(10, 4), f(12, 1), f(12, 2), f(6, 8), f(6, 1), f(5, 8), f(5, 2), f(3, 8), f(3, 4);
f(9), f(6), f(10), f(5), f(12), f(3);
f(1, 2, 4, 8);
f(1, 2, 4), f(1, 2, 8), f(1, 4, 8), f(2, 4, 8);
f(1, 2), f(1, 4), f(1, 8), f(2, 4), f(2, 8), f(4, 8);
f(1), f(2), f(4), f(8);
cout << max(ans, 1) << '\n';
}
return 0;
}
T2
给定一棵 \(n\) 个点的树和整数 \(k\),边有边权。定义一个树上连通块的权值为其中边权之和,你需要求满足「至多包含一个度数 \(>k\) 的点」的树上连通块的权值最大值。
注意,条件中的度数指连通块中的度数,而非原树中的度数。
\(1\le k\le n\le 2\times 10^5\)
把连通块挂在深度最浅的点的统计答案。
每个点求一个 \(f_{u,0/1}\) 为 \(u\) 子树内无/有度数 \(\ge k\) 的点,\(u\) 度数 \(\le k-1\) 的最大权值。
之所以是 \(k-1\) 是为了方便与向父节点传递,向答案产生贡献就再求一个 \(u\) 度数为 \(k\) 的就行。
\(f_{u,0}\) 直接求子树中 \(f_{v,0}\) 前 \(k-1\) 大的和。
\(f_{u,1}\) 一种是 \(d_u\ge k\),这个直接将所有 \(f_{v,0}\) 加起来就行(反正 \(\ge k\) 了,多大都行)。一种是子树中的,所以选一个 \(f_{v,1}\) 并从其余 \(f_{v,0}\) 选前 \(k-2\) 大。
这些都是前缀和好维护的。注意 \(k=0\) 的特殊情况。
赛时代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5 + 5;
int n, k;
int head[N], tot;
ll f[N][2], sum[N], ans;
struct node {
ll w, v;
} a[N];
struct edge {
int v, w, nxt;
} e[N * 2];
void add(int u, int v, int w) {
e[++tot] = (edge){v, w, head[u]}, head[u] = tot;
e[++tot] = (edge){u, w, head[v]}, head[v] = tot;
}
void Max(ll &x, ll y) { x = max(x, y); }
void dfs(int u, int fa) {
for (int i = head[u]; i; i = e[i].nxt)
if (e[i].v != fa)
dfs(e[i].v, u);
int m = 0;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v, w = e[i].w;
if (v != fa)
a[++m] = (node){f[v][0] + w, f[v][1] + w};
}
sort(a + 1, a + m + 1, [](node x, node y) { return x.w > y.w; });
for (int i = 1; i <= m; ++i)
sum[i] = sum[i - 1] + a[i].w;
if (m < k) {
f[u][0] = sum[m];
for (int i = 1; i <= m; ++i)
Max(f[u][1], sum[m] - a[i].w + a[i].v);
Max(ans, f[u][0]), Max(ans, f[u][1]);
} else {
f[u][0] = sum[k - 1];
f[u][1] = sum[m];
if (k > 1) {
for (int i = 1; i <= m; ++i)
if (i <= k - 1)
Max(f[u][1], sum[k - 1] - a[i].w + a[i].v);
else
Max(f[u][1], sum[k - 2] + a[i].v);
}
for (int i = 1; i <= m; ++i)
if (i <= k)
Max(ans, sum[k] - a[i].w + a[i].v);
else
Max(ans, sum[k - 1] + a[i].v);
Max(ans, f[u][0]), Max(ans, f[u][1]);
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; ++i)
f[i][0] = f[i][1] = -1e9;
for (int i = 1, u, v, w; i < n; ++i)
cin >> u >> v >> w, add(u, v, w);
if (k == 0)
return cout << 0 << '\n', 0;
dfs(1, 0), cout << ans << '\n';
return 0;
}
T3
T4
对于一个元素互不相同的序列,可以若干次选择一组 \(a_{i-1}< a_i< a_{i+1}\) 由 $\dots a_{i-1},a_i,a_{i+1}\dots $ 改为 \(\dots a_{i+1},a_{i-1},a_i,\dots\)。
若一个序列能够由一个严格单调上升的序列通过有限多次操作得到则其实合法的。
给定一个长为 \(n\) 的排列 \(p\),会 \(q\) 次交换 \(p_x,p_y\),求出每次交换后最长的合法后缀的开头位置。60pts: \(n\le 5\times 10^3,q\le 10^5\)
100pts:\(n,q\le 10^5\)7s 512MB
打表,感性理解。。
一个序列合法的充要条件是其所有 \(a_i\) 后面 \(<a_i\) 的个数为偶数。必要性显然,充分性的话我们倒推,如果有 \(a_{i-1}>a_{i+1}>a_i\) 那么直接操作,如果 \(a_{i-1}>a_i>a_{i+1}\) 那么一定不满足我们的条件。
然后每次修改 \(O(n)\) 更新答案,就可以得到 60pts。
赛时 60pts
#include <bits/stdc++.h>
using namespace std;
const int N = 5005;
int n, m, a[N], p[N];
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
for (int j = 1; j < i; ++j)
if (a[j] > a[i]) p[j] ^= 1;
}while (m--) {
int x, y, ans; cin >> x >> y;
if (x > y)swap(x, y);
for (int i = x + 1; i < y; ++i) {
if (a[i] > a[y])p[i] ^= 1;
if (a[i] > a[x])p[i] ^= 1;
}swap(a[x], a[y]);swap(p[x], p[y]);
for (int i = x; i <= y - 1; ++i)
if (a[y] > a[i]) p[y] ^= 1;
for (int i = x + 1; i <= y; ++i)
if (a[x] > a[i]) p[x] ^= 1;
for (int i = n; i >= 1; --i) {
if (p[i]) break;ans = i;
} cout << ans << '\n';
}return 0;
}
考虑到这题像是某种恐怖的分块,所以赛时直接放弃了。
有点像 P5356由乃打扑克。
正解就是分块,我们要维护一个 \(f_i=(\sum_{j=i+1}^n[a_j>a_i])\bmod 2\),我们需要。
-
每次修改我们会对一段区间内 \(\ge a_x,a_y\) 的作异或 \(1\) 的操作。
-
查询区间 \(\le x\) 数的个数
-
查询最后一个 \(f_i=1\) 的位置。
赛后我的做法:
直接对每个块块内排序,并对每个块内开一棵线段树以完成值域区间异或。查询 \(\le x\) 的直接小块暴力,大块 lower_bound 即可。
每次需要查询块内 \(f\) 值时将这个块的线段树标记下放,然后若有修改就将线段树重构。
值得注意的是复杂度瓶颈主要为块内暴力排序,而每次我们只会修改块内一个值,所以我们只要考虑将这个值向前/后移动即可。
复杂度比较玄学,大概是 \(O(n\sqrt{n\log n})\) 级别,实际情况还是要调块长。
6k+ 的代码。
#pragma GCC optimize(3)
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mid (l + r >> 1)
using namespace std;
const int N = 1e5 + 5, B = 700, C = 1005;
namespace BIT {
int bit[N];
void upd(int x) {
while (x < N)
++bit[x], x += x & -x;
}
int ask(int x, int res = 0) {
while (x)
res += bit[x], x -= x & -x;
return res;
}
} // namespace BIT
using namespace BIT;
int n, m;
int a[N];
bool b[N], f[N], s[N * 2][2], t[N * 2];
int id[N], l[C], r[C], sum[C], rt[C];
int ls[N * 2], rs[N * 2], tot;
struct node {
int v, id;
bool operator<(const node x) const { return v < x.v; }
} c[N];
void push_up(int p) {
s[p][0] = s[ls[p]][0] | s[rs[p]][0];
s[p][1] = s[ls[p]][1] | s[rs[p]][1];
}
void push_down(int p) {
if (t[p]) {
t[ls[p]] ^= 1, t[rs[p]] ^= 1;
swap(s[ls[p]][0], s[ls[p]][1]);
swap(s[rs[p]][0], s[rs[p]][1]);
t[p] = 0;
}
}
void bd(int &p, int l, int r) {
p = ++tot;
if (l == r)
return s[p][f[c[l].id]] = 1, s[p][f[c[l].id] ^ 1] = 0, void();
bd(ls[p], l, mid), bd(rs[p], mid + 1, r), push_up(p);
}
void rbd(int p, int l, int r) {
t[p] = 0;
if (l == r)
return s[p][f[c[l].id]] = 1, s[p][f[c[l].id] ^ 1] = 0, void();
rbd(ls[p], l, mid), rbd(rs[p], mid + 1, r), push_up(p);
}
void upd(int p, int l, int r, int L, int R) {
if (L > R)
return;
if (L <= l & r <= R)
return t[p] ^= 1, swap(s[p][0], s[p][1]);
push_down(p);
if (L <= mid)
upd(ls[p], l, mid, L, R);
if (mid < R)
upd(rs[p], mid + 1, r, L, R);
push_up(p);
}
void down(int p, int l, int r) {
if (l == r)
return f[c[l].id] = (s[p][1] ? 1 : 0), void();
push_down(p), down(ls[p], l, mid), down(rs[p], mid + 1, r);
}
void update(int L, int R, int x, int y) {
for (int i = L; i <= R; ++i) {
int px = upper_bound(c + l[i], c + r[i] + 1, (node){x, 0}) - c;
int py = upper_bound(c + l[i], c + r[i] + 1, (node){y, 0}) - c;
upd(rt[i], l[i], r[i], px, r[i]), upd(rt[i], l[i], r[i], py, r[i]);
}
}
bool query(int L, int R, int x) {
bool sum = 0;
if (id[L] == id[R]) {
for (int i = L; i <= R; ++i)
sum ^= (a[i] < x);
return sum;
}
for (int i = L; i <= r[id[L]]; ++i)
sum ^= (a[i] < x);
for (int i = l[id[R]]; i <= R; ++i)
sum ^= (a[i] < x);
for (int i = id[L] + 1; i <= id[R] - 1; ++i)
sum ^= (((lower_bound(c + l[i], c + r[i] + 1, (node){x, 0}) - c - l[i])) & 1);
return sum;
}
void Sort(int l, int r, int x) {
while (x > l && c[x] < c[x - 1])
swap(c[x], c[x - 1]), --x;
while (x < r && c[x + 1] < c[x])
swap(c[x], c[x + 1]), ++x;
}
int main() {
freopen("ex_swap2.in", "r", stdin);
freopen("a.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i], id[i] = (i - 1) / B + 1;
if (id[i] != id[i - 1])
r[id[i - 1]] = i - 1, l[id[i]] = i;
}
r[id[n]] = n;
for (int i = n; i >= 1; --i)
f[i] = (ask(a[i]) & 1), upd(a[i]);
for (int i = 1; i <= id[n]; ++i) {
for (int j = l[i]; j <= r[i]; ++j)
c[j].v = a[j], c[j].id = j;
sort(c + l[i], c + r[i] + 1);
bd(rt[i], l[i], r[i]);
}
for (int t = 1, x, y; t <= m; ++t) {
cin >> x >> y;
if (x > y)
swap(x, y);
if (id[x] == id[y]) {
int i = id[x];
down(rt[i], l[i], r[i]);
swap(a[x], a[y]);
swap(f[x], f[y]);
for (int i = x + 1; i <= y - 1; ++i) {
if (a[i] > a[x])
f[i] ^= 1;
if (a[i] > a[y])
f[i] ^= 1;
}
for (int i = x; i <= y - 1; ++i)
if (a[y] > a[i])
f[y] ^= 1;
for (int i = x + 1; i <= y; ++i)
if (a[x] > a[i])
f[x] ^= 1;
for (int j = l[i]; j <= r[i]; ++j) {
if (c[j].v == a[x])
c[j].id = x;
if (c[j].v == a[y])
c[j].id = y;
}
rbd(rt[i], l[i], r[i]);
} else {
bool sx = query(x + 1, y - 1, a[x]);
bool sy = query(x + 1, y - 1, a[y]);
update(id[x] + 1, id[y] - 1, a[x], a[y]);
down(rt[id[x]], l[id[x]], r[id[x]]);
down(rt[id[y]], l[id[y]], r[id[y]]);
for (int i = x + 1; i <= r[id[x]]; ++i) {
if (a[i] > a[x])
f[i] ^= 1;
if (a[i] > a[y])
f[i] ^= 1;
}
for (int i = l[id[y]]; i <= y - 1; ++i) {
if (a[i] > a[x])
f[i] ^= 1;
if (a[i] > a[y])
f[i] ^= 1;
}
swap(a[x], a[y]), swap(f[x], f[y]);
f[x] ^= sy, f[y] ^= sx;
if (a[y] > a[x])
f[y] ^= 1;
if (a[x] > a[y])
f[x] ^= 1;
int i = id[x], p = 0;
for (int j = l[i]; j <= r[i]; ++j)
if (c[j].id == x)
c[j].v = a[x], p = j;
Sort(l[i], r[i], p);
rbd(rt[i], l[i], r[i]);
i = id[y];
for (int j = l[i]; j <= r[i]; ++j)
if (c[j].id == y)
c[j].v = a[y], p = j;
Sort(l[i], r[i], p);
rbd(rt[i], l[i], r[i]);
}
int ans = n;
for (int i = id[n]; i >= 1; --i) {
if (s[rt[i]][1]) {
down(rt[i], l[i], r[i]);
for (int j = r[i]; j >= l[i]; --j) {
if (f[j])
break;
ans = j;
}
break;
} else
ans = l[i];
}
cout << ans << '\n';
}
return 0;
}

浙公网安备 33010602011771号