2025 暑期 mx 集训 7.14
https://hydro.ac/d/pigsyy/p 来自 Pigsyy 的补题网址。
T1
https://www.mxoj.net/problem/P100313?contestId=29
题意
有 \(3\) 个杯子,只能进行一种操作:把一个杯子里的水倒到另一个杯子里去,直到另一个杯子满了,或这个被子空了就停止。
他们的容积分别是:\(A,B,C\) 满足 \(A\leq B \leq C \leq 10^5\),他们的初始水量是:\(a, b, c\)。
不可以加新的水进来。对于 \(k = [0, C]\) 输出最少多少次操作可以使得任意一个杯子里的水恰好 \(= k\)。
Solution
考虑没有新的水添加,那么由于每次操作必须有一个会倒满或倒空,那么其中一个为满 / 空的状态后,剩下两个的其中一个的状态也是唯一的,那最后一个的状态不确定。
所以总状态数 \(O(C)\),直接 bfs 就行。
tips:我这只能是感性理解,也可能不太对,但反正是能过的。
Code
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int, pair<int, int>>
const int N = 3e5 + 10, inf = 0x3f3f3f3f;
int a[5], b[5], res[N];
map<pii, bool> mp;
pair<int, int> f[] = {{1, 2}, {1, 3}, {2, 1}, {2, 3}, {3, 1}, {3, 2}};
struct node { int a[5], d; };
int main()
{
cin.tie(0)->ios::sync_with_stdio(false);
freopen("juice.in", "r", stdin);
freopen("juice.out", "w", stdout);
cin >> b[1] >> b[2] >> b[3] >> a[1] >> a[2] >> a[3];
memset(res, inf, sizeof res);
res[a[1]] = res[a[2]] = res[a[3]] = 0;
queue<node> q; q.push({{0, a[1], a[2], a[3]}, 0});
auto get = [&](int a, int b, int c) -> pii {
return make_pair(a, make_pair(b, c));
};
mp[get(a[1], a[2], a[3])] = 1;
while (!q.empty()) {
auto u = q.front(); q.pop();
for (int i = 0; i < 6; i++) {
int x = f[i].first, y = f[i].second;
int o = min(u.a[x], b[y] - u.a[y]);
if (o == 0) continue;
u.a[x] -= o, u.a[y] += o;
bool ok = (u.a[1] == 0 || u.a[1] == b[1] || u.a[2] == 0 || u.a[2] == b[2] || u.a[3] == 0 || u.a[3] == b[3]);
if (!ok && !(u.a[1] == a[1] && u.a[2] == a[2] && u.a[3] == b[3])) continue;
if (mp.find(get(u.a[1], u.a[2], u.a[3])) == mp.end()) {
mp[get(u.a[1], u.a[2], u.a[3])] = 1;
res[u.a[1]] = min(res[u.a[1]], u.d + 1);
res[u.a[2]] = min(res[u.a[2]], u.d + 1);
res[u.a[3]] = min(res[u.a[3]], u.d + 1);
q.push({{0, u.a[1], u.a[2], u.a[3]}, u.d + 1});
}
u.a[x] += o, u.a[y] -= o;
}
}
for (int i = 0; i <= b[3]; i++) cout << (res[i] == inf ? -1 : res[i]) << " \n"[i == b[3]];
return 0;
}
T2
https://www.mxoj.net/problem/P100314?contestId=29
题意
你有 \(n\) 个物品,重为 \(w_i\),你有一个体积为 \(m\) 的背包,\(q\) 次询问从第 \(l\) 到第 \(r\) 个物品中,恰好重量恰好为 \(x\) 的方案数。对 \(10^9 + 7\) 取模。
\(n \leq 3\times 10^4, q \leq 3\times 10^5, m \leq 500\)。
Solution
猫树板子。
但是可以用 cdq 分治做。
考虑先把询问挂到分治上。设当前询问区间为 \(l, r\),中点为 \(mid\)。
询问为 \(ql, qr, x\)。
首先我们只需要处理跨过 \(mid\) 的询问:
- \(qr < mid\) 那把他扔到左区间。
- \(ql > mid\) 扔到右区间。
- 否则在当前这个区间处理。
对于当前这个区间,设 \(f_{i,j}\) 表示到了 \(i\),用了 \([i, mid]\) 这个后缀,此时背包容量为 \(j\) 的方案数。
同理,\(g_{i,j}\) 表示到了 \(i\),\([mid, i]\) 这段前缀,背包容量为 \(j\) 的方案数。
然后枚举询问合并答案即可。具体可看代码。
时间复杂度:\(O(nm \log n + qm)\)。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 3e4 + 10, Q = 3e5 + 10, P = 1e9 + 7;
int n, m, t, a[N];
int f[N][510], g[N][510], ans[Q];
struct qry { int l, r, x, id; };
void cdq(int l, int r, vector<qry> q)
{
if (!q.size()) return;
if (l == r) {
for (auto u : q) ans[u.id] = (a[l] == u.x);
return;
}
int mid = (l + r) / 2;
vector<qry> q1, q2, q3;
for (auto u : q) {
if (u.r <= mid) q1.push_back(u);
else if (u.l > mid) q2.push_back(u);
else q3.push_back(u);
}
for (int i = mid + 1; i >= l; i--) for (int j = 0; j <= m; j++) f[i][j] = 0;
for (int i = mid; i <= r; i++) for (int j = 0; j <= m; j++) g[i][j] = 0;
f[mid + 1][0] = 1, g[mid][0] = 1;
for (int i = mid; i >= l; i--) for (int j = 0; j <= m; j++) {
f[i][j] = f[i + 1][j];
if (j >= a[i]) (f[i][j] += f[i + 1][j - a[i]]) %= P;
}
for (int i = mid + 1; i <= r; i++) for (int j = 0; j <= m; j++) {
g[i][j] = g[i - 1][j];
if (j >= a[i]) (g[i][j] += g[i - 1][j - a[i]]) %= P;
}
for (auto u : q3) {
int x = u.x;
for (int i = 0; i <= x; i++) (ans[u.id] += 1ll * f[u.l][i] * g[u.r][x - i] % P) %= P;
}
cdq(l, mid, q1), cdq(mid + 1, r, q2);
}
int main()
{
cin.tie(0)->ios::sync_with_stdio(false);
freopen("seg.in", "r", stdin);
freopen("seg.out", "w", stdout);
cin >> n >> m >> t;
for (int i = 1; i <= n; i++) cin >> a[i];
vector<qry> q;
for (int i = 1; i <= t; i++) {
int l, r, x; cin >> l >> r >> x;
q.push_back({l, r, x, i});
}
cdq(1, n, q);
for (int i = 1; i <= t; i++) cout << ans[i] << "\n";
return 0;
}
T3
https://www.mxoj.net/problem/P100315?contestId=29
题意
给你 \(n\) 个数 \(a_i\),定义一个函数 \(f(b)\)。
假设你有一个序列 \(b\) 你可以对 \(b\) 进行以下操作:
选择一个 \(i\),然后删除 \(b_i\) 和 \(b_{i + 1}\) 并添加一个 \(\gcd(b_i, b_{i + 1})\)。
最终把 \(b\) 序列里的元素全变成同一元素的最小操作次数就是 \(f(b)\) 的值。
给你 \(q\) 次询问,每次询问求 \(f(a_{[l,r]})\)。
\(n\leq 10^5, q\leq 3\times 10^5, a_i \leq 10^5\)。
Solution
首先考虑最终所有数都会变成啥。
那就是变成这个区间的 \(\gcd\)。
然后考虑不同的 \(\gcd\) 只有 \(n \log n\) 个。
因为你从每个 \(i\) 往后取 \(\gcd\) 只有 \(\log\) 个。所以总共 \(n\log n\) 个。
然后考虑设 \(z_{i,j,k}\) 表示从 \(i\) 开始,跳 \(2^j\) 步,在 \(\gcd = k\) 的情况下能最近能跳到哪。
为啥最近?因为相当于把这一段分成 \(2^j\) 块,块和块之间不需要合并,所以肯定想让块的大小尽可能小。
然后初始化就是从后往前扫,同时维护 \(\gcd\) 具体可看代码。
倍增因为你不确定都有哪些 \(\gcd\),所以要根据前面的来维护。
查询的话有一个 \(+1\),是因为你上一个块弄完了,所以你要跳到下一个块里去。同时防止 \(l = r\) 的块。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, inf = 0x3f3f3f3f;
int n, q, a[N], st[N][18], lg[N];
unordered_map<int, int> z[N][18];
int qry(int l, int r)
{
int k = lg[r - l + 1];
return __gcd(st[l][k], st[r - (1 << k) + 1][k]);
}
int main()
{
cin.tie(0)->ios::sync_with_stdio(false);
freopen("gcd.in", "r", stdin);
freopen("gcd.out", "w", stdout);
cin >> n >> q;
lg[0] = -1;
for (int i = 1; i <= n; i++) {
cin >> a[i];
st[i][0] = a[i];
lg[i] = lg[i / 2] + 1;
}
for (int j = 1; j <= 17; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++)
st[i][j] = __gcd(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
for (int i = n; i >= 1; i--) {
for (auto u : z[i + 1][0]) {
int g = __gcd(u.first, a[i]);
if (z[i][0][g]) z[i][0][g] = min(z[i][0][g], u.second);
else z[i][0][g] = u.second;
}
z[i][0][a[i]] = i;
}
for (int j = 1; j <= 17; j++) {
for (int i = 1; i <= n; i++) {
for (auto u : z[i][j - 1]) {
if (!u.second || !z[u.second + 1][j - 1][u.first]) continue;
z[i][j][u.first] = z[u.second + 1][j - 1][u.first];
}
}
}
for (int i = 1; i <= q; i++) {
int l, r; cin >> l >> r;
int g = qry(l, r), ans = r - l + 1, x = l;
for (int j = 17; j >= 0; j--) {
if (z[x][j][g] && z[x][j][g] <= r) {
x = z[x][j][g] + 1;
ans -= (1 << j);
}
}
cout << ans << "\n";
}
return 0;
}
T4
https://www.luogu.com.cn/problem/P5806
大模拟,扔了。
T5
https://www.luogu.com.cn/problem/CF1569F
T6
hdu7200

浙公网安备 33010602011771号