做题记录 5 / 世间所有路都将与你相逢
Priestess_SLG · · 个人记录
注:黑+ 上面还有 彩- / 彩 / 彩+ 三个等级()
应某人要求,跳过了一些敏感的编号。
2026 超新星战队 B & B+ 队暑假集训模拟赛 day5
A
:::info[Description] Link :::
:::info[Solution]{open} 考虑一个元素一个元素的归位。
设当前要处理的位置是
考虑优化操作次数。容易想到折半,每次对
| 因此总操作次数为 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int a[N], pos[N];
inline void gao(int l, int r) {
for (int i = l; i <= (l + r) / 2; ++i) {
swap(a[i], a[i + (r - l + 1) / 2]);
swap(pos[a[i]], pos[a[i + (r - l + 1) / 2]]);
}
}
inline void sol([[maybe_unused]]int __testcase_id) {
int n; cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i], pos[a[i]] = i;
vector<pair<int, int>> op;
for (int i = 1; i <= n; ++i) {
while (pos[i] != i) {
int l = i, r = pos[i];
if ((r - l + 1) & 1) {
op.emplace_back(r - 1, r);
op.emplace_back(l, r - 1);
gao(r - 1, r), gao(l, r - 1);
} else {
op.emplace_back(l, r);
gao(l, r);
}
}
} cout << op.size() << '\n';
for (auto &[i, j] : op) cout << i << ' ' << j << '\n';
}
} // namespace lowspeed_song
:::
难度:绿
鲜活指数:8
B
:::info[Description] Link :::
:::info[Solution]{open}
:::
C
D
E
:::info[Description] Link :::
:::info[Solution]{open}
为了方便,这里把
设在某一时刻当前还没有被弹出的
如果某一时刻 - 操作之前堆中有 - 操作弹出的元素必然属于
考虑维护当前时刻的
具体的:设
初始条件显然就是
然后考虑如果当前遇到了一个 + 操作,则先设前 + 操作和 - 操作,则当前
考虑如果插入非
如果当前插入
如果当前插入的值恰好不是
然后考虑如果当前遇到了一个 - 操作,则此时因为弹出的元素必须属于
同样考虑分类讨论:
如果当前弹出的元素不是
- 如果当前
o=0 ,则A_j 根本就不在堆里,当然不会弹出A_j 元素。 - 否则,如果
o=1\land k>1 则堆里除了A_j 以外还有其他属于A 集合内的元素,她们全都小于A_j 因此小根堆里肯定会先弹出其他元素。
总结一下,如果当前
如果当前弹出的值是 - 操作一定必须把
考虑之前一共有
| 直接按照上述 dp 转移时间时间复杂度看起来都是 |
|---|
:::success[Code]
namespace lowspeed_song {
int fac[N], inv[N], ifac[N];
inline void init() {
fac[0] = inv[0] = ifac[0] = 1;
fac[1] = inv[1] = ifac[1] = 1;
for (int i = 2; i < N; ++i) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = mod - inv[mod % i] * (mod / i) % mod;
ifac[i] = ifac[i - 1] * inv[i] % mod;
}
}
inline int binom(int a, int b) {
if (b < 0 || a < b) return 0;
return fac[a] * ifac[b] % mod * ifac[a - b] % mod;
}
char s[610];
int f[2][310][310][2], a[310];
inline void sol([[maybe_unused]]int __testcase_id) {
int n, m, q = 0; cin >> n >> m;
scanf("%s", s);
for (int i = 0; i < m; ++i) cin >> a[i];
sort(a, a + m, greater<>());
f[1][0][0][0] = 1;
for (int i = 0; i < n + m; ++i) {
memset(f[i & 1], 0, sizeof f[i & 1]);
for (int j = 0; j <= m; ++j)
for (int k = 0; k <= m; ++k)
for (int o : {0, 1}) {
if (s[i] == '+') {
if (n - a[j] - j - i + q + q + k > 0) f[i & 1][j][k][o] = (f[i & 1][j][k][o] + (n - a[j] - j - i + q + q + k) * f[~i & 1][j][k][o]) % mod;
if (j < m && !o && m - q - k > 0) f[i & 1][j][k + 1][1] = (f[i & 1][j][k + 1][1] + f[~i & 1][j][k][o]) % mod;
if (m - q - k + o > 1) f[i & 1][j][k + 1][o] = (f[i & 1][j][k + 1][o] + f[~i & 1][j][k][o]) % mod;
} else if (k) {
if (k != 1 || o != 1) f[i & 1][j][k - 1][o] = (f[i & 1][j][k - 1][o] + f[~i & 1][j][k][o]) % mod;
else for (int p = 0; p <= min(q - j, m - j - 1); ++p) f[i & 1][j + p + 1][0][0] = (f[i & 1][j + p + 1][0][0] + f[~i & 1][j][k][o] * fac[q - j] % mod * ifac[q - j - p] % mod) % mod;
}
}
if (s[i] == '-') ++q;
} cout << f[~(n + m) & 1][m][0][0] << '\n';
}
} // namespace lowspeed_song
:::
难度:黑
鲜活指数:9
2026 超新星战队 B & B+ 队暑假集训模拟赛 day6
E
:::info[Description]
有一个序列
每次可以选择一个前缀或者后缀增加
- 选择前缀
[1,i] :把a_1,a_2,\ldots,a_i 的值都增加1 ,代价为p_i 。 - 选择后缀
[i,n] :把a_i,a_{i+1},\ldots,a_n 的值都增加1 ,代价为s_i 。
| 希望进行若干次操作使得最后对每个 |
|---|
:::info[Solution]{open}
记
考虑相邻两个位置,有
先考虑
然后在考虑
同理,
总结一下,问题可以被转化为下面的图论模型:
- 对每个结点
i ,由0 号结点向i 号结点连一条流量为x_i 费用为p_i 的边。 - 对每个结点
i ,由i 号结点向i-1 号结点连一条流量为w_i 费用为0 的边。 - 对每个结点
i ,由i-1 号结点向n 号结点连一条流量为y_i 费用为s_i 的边。
记源点
但是直接建边时间复杂度爆炸了,考虑挖掘图的性质来优化。维护最优费用流每个结点
然后对势能做差分,记
与此同时费用流的最优费用还可以被表示为
此时考虑把限制变为单调的。具体来说:
对前缀限制而言,后面更小的
然后考虑求出
因为费用流是凸函数,因此
问题在于如何快速的计算
| 因此从当前的 |
|---|
:::success[Code]
:::
难度:黑+
鲜活指数:6
nfls CSP2026模拟赛6
A
:::info[Description] 请勿将OJ的题目搬到其他地方,如有发现封号处理。在博客写题解的同学,请不要写题目描述。 :::
:::info[Solution]{open} 直接双向搜索即可。 :::
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int n, s, a[20], res;
map<int, int> mp;
inline void dfs(int now, int End, int prod) {
if (now == End + 1) {
++mp[prod];
return;
} int inner = a[now];
for (int i = 1; prod + inner <= s; ++i) {
dfs(now + 1, End, prod + inner);
inner *= a[now];
}
}
inline void dfs2(int now, int End, int prod) {
if (now == End + 1) {
auto it = mp.upper_bound(s - prod);
if (it != mp.begin()) res += prev(it)->second;
return;
} int inner = a[now];
for (int i = 1; prod + inner <= s; ++i) {
dfs2(now + 1, End, prod + inner);
inner *= a[now];
}
}
inline void sol([[maybe_unused]]int __testcase_id) {
cin >> n >> s;
for (int i = 1; i <= n; ++i) cin >> a[i];
dfs(1, n / 2, 0);
int las = 0;
for (auto &[x, y] : mp) y += las, las = y;
dfs2(n / 2 + 1, n, 0);
cout << res << '\n';
}
} // namespace lowspeed_song
:::
难度:绿
鲜活指数:0
B
:::info[Description] 请勿将OJ的题目搬到其他地方,如有发现封号处理。在博客写题解的同学,请不要写题目描述。 :::
:::info[Solution]{open}
先把树上的距离拆开,得到
考虑先求
然后再求
| 最后询问的时候直接拆 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int a[N], b[N], E[N], E_lca[N], pre[N];
inline void sol([[maybe_unused]]int __testcase_id) {
int n, q; cin >> n >> q;
for (int i = 1; i <= n; ++i) cin >> a[i];
for (int i = 1; i <= n; ++i) cin >> b[i];
E[1] = E_lca[1] = 0, pre[1] = a[1];
int sum_E = a[1] * b[1] % mod, sum_Elca = 0;
for (int i = 2; i <= n; ++i) {
E[i] = (b[i] + sum_E * inversion(pre[i - 1]) % mod) % mod;
pre[i] = (pre[i - 1] + a[i]) % mod;
E_lca[i] = (a[i] * E[i] % mod + sum_Elca) % mod * inversion(pre[i]) % mod;
sum_E = (sum_E + a[i] * (E[i] + b[i]) % mod) % mod;
sum_Elca = (sum_Elca + a[i] * E_lca[i] % mod) % mod;
}
while (q--) {
int u, v; cin >> u >> v;
if (u == v) cout << 0 << '\n';
else cout << (E[u] + E[v] - 2 * E_lca[min(u, v)] % mod + mod) % mod << '\n';
}
}
} // namespace lowspeed_song
:::
难度:蓝
鲜活指数:7
C
:::info[Description] 请勿将OJ的题目搬到其他地方,如有发现封号处理。在博客写题解的同学,请不要写题目描述。 :::
:::info[Solution]{open}
:::
:::success[Code]
:::
难度:
鲜活指数:
D
:::info[Description] 请勿将OJ的题目搬到其他地方,如有发现封号处理。在博客写题解的同学,请不要写题目描述。 :::
:::info[Solution]{open}
考虑 dp。先不求恰好第几次连通,设
:::success[Code]
:::
难度:
鲜活指数:
Misc
107. P10992 [蓝桥杯 2023 国 Python A] 最长同类子串
:::info[Description] Link. :::
:::info[Solution]{open}
感觉比较套路。考虑二分答案,设当前二分的答案长度为
| 总时间复杂度为 |
|---|
:::success[Code]
namespace lowspeed_song {
constexpr u64 base = 1333331;
u64 pwr[N];
char s[N], t[N];
int n, m, ne[N], ne2[N], pre[N], pre2[N], buc[N];
inline void init() {
pwr[0] = 1;
for (int i = 1; i < N; ++i) pwr[i] = pwr[i - 1] * base;
}
inline int chk(int x) {
set<u64> se;
u64 Hash = 0;
for (int i = 1; i <= x; ++i)
if (pre[i]) Hash += (i - pre[i]) * pwr[x - i];
se.emplace(Hash);
// cerr << "awa " << Hash << ' ' << x << '\n';
for (int l = 2, r = x + 1; r <= n; ++l, ++r) {
if (ne[l - 1] <= r - 1) Hash -= (ne[l - 1] - l + 1) * pwr[r - ne[l - 1] - 1];
Hash = Hash * base;
if (pre[r] >= l) Hash += r - pre[r];
se.emplace(Hash);
// cerr << "qwq " << l << ' ' << r << " : " << Hash << '\n';
}
Hash = 0;
for (int i = 1; i <= x; ++i)
if (pre2[i]) Hash += (i - pre2[i]) * pwr[x - i];
// cerr << "Hash = " << Hash << ' ' << x << '\n';
if (se.count(Hash)) return 1;
for (int l = 2, r = x + 1; r <= m; ++l, ++r) {
if (ne2[l - 1] <= r - 1) Hash -= (ne2[l - 1] - l + 1) * pwr[r - ne2[l - 1] - 1];
Hash *= base;
if (pre2[r] >= l) Hash += r - pre2[r];
if (se.count(Hash)) return 1;
} return 0;
}
inline void sol() {
scanf("%s%s", s + 1, t + 1);
n = strlen(s + 1), m = strlen(t + 1);
for (int i = 1; i <= n; ++i) pre[i] = buc[s[i] - 'a'], buc[s[i] - 'a'] = i;
memset(buc, 0, sizeof buc);
for (int i = n; i; --i) ne[i] = buc[s[i] - 'a'], buc[s[i] - 'a'] = i;
memset(buc, 0, sizeof buc);
for (int i = 1; i <= m; ++i) pre2[i] = buc[t[i] - 'a'], buc[t[i] - 'a'] = i;
memset(buc, 0, sizeof buc);
for (int i = m; i; --i) ne2[i] = buc[t[i] - 'a'], buc[t[i] - 'a'] = i;
for (int i = 1; i <= n; ++i)
if (!ne[i]) ne[i] = n + 1;
for (int i = 1; i <= m; ++i)
if (!ne2[i]) ne2[i] = m + 1;
int l = 1, r = min(n, m), best = 0;
while (l <= r) {
int mid = l + r >> 1;
chk(mid) ? (best = mid, l = mid + 1) : (r = mid - 1);
} cout << best << '\n';
}
} // namespace lowspeed_song
:::
难度:紫-
鲜活指数:4
108. P9523 [JOIST 2022] 复制粘贴 3 / Copy and Paste 3
:::info[Description] Link. :::
:::info[Solution]{open}
考虑区间 dp。设
-
-
- 如果
[x,y]\subseteq[i,j] 且[x,y] 在[i,j] 中无交的出现了k 次,则有转移f_{i,j}=f_{x,y}+(j-i+1-k(y-x+1))A+B+kC 。
考虑先优化一下转移的形式。钦定转移时
现在考虑怎么对区间
对当前区间
| 因此问题解决,总时间复杂度为 |
|---|
:::success[Code]
namespace lowspeed_song {
char s[N];
int lcp[3010][3010], f[3010][3010], pre[3010][3010];
inline void init() {
}
inline void sol() {
int n; cin >> n;
scanf("%s", s + 1);
int A, B, C; cin >> A >> B >> C;
for (int i = n; i; --i)
for (int j = n; j > i; --j)
if (s[i] == s[j]) lcp[i][j] = lcp[i + 1][j + 1] + 1;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j < i; ++j) pre[i][min(lcp[j][i], i - j)] = max(pre[i][min(lcp[j][i], i - j)], j);
for (int j = n - 1; j; --j) pre[i][j] = max(pre[i][j], pre[i][j + 1]);
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n - i + 1; ++j) f[j][j + i - 1] = A * i;
for (int len = 1; len <= n; ++len)
for (int i = 1, j = len; j <= n; ++i, ++j) {
if (len > 1) f[i][j] = min({f[i][j], f[i][j - 1] + A, f[i + 1][j] + A});
int pos = i, k = 1;
while (pre[pos][len]) {
pos = pre[pos][len], ++k;
f[pos][j] = min(f[pos][j], f[i][j] + B + C * k + A * (j - pos + 1 - k * len));
}
}
cout << f[1][n] << '\n';
}
} // namespace lowspeed_song
:::
难度:紫
鲜活指数:10
109. P7361 「JZOI-1」拜神
:::info[Description] Link. :::
:::info[Solution]{open} 回顾一下后缀数组的一些定义和性质:
-
-
-
-
height_{rk_i}\ge height_{rk_{i-1}}-1 -
\operatorname{lcp}(sa_i,sa_j)=\min\limits_{k=i+1}^jheight_k - 序列中不同子串的数量为
\frac{n(n+1)}2-\sum\limits_{i=2}^nheight_i 。
问题可以被转化为每次询问区间
套路的求出后缀数组
对原串的每个位置
考虑用数据结构来维护这个结构。对 set 维护其中的原串位置,每次合并直接启发式合并两个 set 更新其中受影响位置的
最后二分
| 总时间复杂度为 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
char s[N];
int root[N];
int lc[M], rc[M], tot;
u16 mi[M];
namespace Suffix_Array {
int sa[N], rk[N], buc[N], height[N], n, idx;
struct Node {
int x[2], buc;
inline Node() { x[0] = x[1] = buc = 0; }
inline Node(int a, int b, int c) { x[0] = b, x[1] = a, buc = c; }
} a[N], b[N];
inline void sort(int up) {
for (int i : {0, 1}) {
memset(buc, 0, sizeof buc);
for (int j = 1; j <= n; ++j) ++buc[a[j].x[i]];
for (int j = 1; j <= up; ++j) buc[j] += buc[j - 1];
for (int j = n; j; --j) b[buc[a[j].x[i]]--] = a[j];
for (int j = 1; j <= n; ++j) a[j] = b[j];
}
int idx = 1;
rk[a[1].buc] = 1;
for (int i = 2; i <= n; ++i)
if (a[i].x[0] == a[i - 1].x[0] && a[i].x[1] == a[i - 1].x[1]) rk[a[i].buc] = idx;
else rk[a[i].buc] = ++idx;
}
inline void build() {
for (int i = 1; i <= n; ++i) a[i] = Node(s[i], 1, i);
sort(128);
for (int i = 1; i < n; i <<= 1) {
for (int j = 1; j <= n; ++j) a[j] = Node(rk[j], (i + j <= n) ? rk[i + j] : 0, j);
sort(n);
} for (int i = 1; i <= n; ++i) sa[rk[i]] = i;
for (int i = 1; i <= n; ++i)
if (rk[i] == 1) height[rk[i]] = 0;
else {
int p = sa[rk[i] - 1], l = max<int>(0, height[rk[i - 1]] - 1);
while (s[i + l] == s[p + l]) ++l;
height[rk[i]] = l;
}
}
} using namespace Suffix_Array;
vector<int> e[N];
inline int mdf(int rt, int l, int r, int pos, int val) {
int nw = ++tot;
lc[nw] = lc[rt], rc[nw] = rc[rt], mi[nw] = mi[rt];
if (l == r) return mi[nw] = val, nw;
int mid = l + r >> 1;
if (pos <= mid) lc[nw] = mdf(lc[rt], l, mid, pos, val);
else rc[nw] = mdf(rc[rt], mid + 1, r, pos, val);
mi[nw] = min(mi[lc[nw]], mi[rc[nw]]);
return nw;
}
inline int qry(int rt, int l, int r, int ll, int rr) {
if (!rt) return inf;
if (ll <= l && r <= rr) return mi[rt];
int mid = l + r >> 1, res = inf;
if (ll <= mid) res = qry(lc[rt], l, mid, ll, rr);
if (mid < rr) res = min(res, qry(rc[rt], mid + 1, r, ll, rr));
return res;
}
int fa[N];
set<int> se[N];
inline int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
int tag_idx, tag[N], cp[N];
inline void chg(int pos, int v) {
if (!tag[pos]) cp[++tag_idx] = pos;
tag[pos] = v;
}
inline void merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) return;
if (se[x].size() < se[y].size()) swap(x, y);
fa[y] = x;
for (const int &p : se[y]) {
auto it = se[x].lower_bound(p);
if (it != se[x].end()) chg(p, *it);
if (it != se[x].begin()) chg(*prev(it), p);
se[x].emplace(p);
} se[y].clear();
}
inline void sol() {
int q; cin >> n >> q;
scanf("%s", s + 1);
build();
mi[0] = 64000;
for (int i = 2; i <= n; ++i)
if (height[i]) e[height[i]].emplace_back(i);
for (int i = 1; i <= n; ++i) fa[i] = i, se[i].emplace(i);
for (int len = n - 1; len; --len) {
tag_idx = 0;
for (int &i : e[len]) merge(sa[i - 1], sa[i]);
root[len] = root[len + 1];
for (int i = 1; i <= tag_idx; ++i) {
root[len] = mdf(root[len], 1, n, cp[i], tag[cp[i]]);
tag[cp[i]] = 0;
}
}
while (q--) {
int l, r; cin >> l >> r;
int L = 1, R = r - l + 1, best = 0;
while (L <= R) {
int mid = L + R >> 1;
if (qry(root[mid], 1, n, l, r - mid + 1) <= r - mid + 1) best = mid, L = mid + 1;
else R = mid - 1;
} cout << best << '\n';
}
}
} // namespace lowspeed_song
:::
难度:黑-
鲜活指数:7
110. AT_arc158_d [ARC158D] Equation
:::info[Description] Link. :::
:::info[Solution]{open}
没有人类了。考虑记
| 然后如果真的随 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
inline void sol() {
int n, p; cin >> n >> p;
mt19937 mt(time(0));
uniform_int_distribution<int> dist(1, p - 1);
int a[3];
while (1) {
a[0] = dist(mt), a[1] = dist(mt), a[2] = dist(mt);
sort(a, a + 3);
if (a[0] == a[1] || a[1] == a[2]) continue;
int x = (a[0] + a[1] + a[2]) % p * (power(a[0], n, p) + power(a[1], n, p) + power(a[2], n, p)) % p * (power(a[0], n + n, p) + power(a[1], n + n, p) + power(a[2], n + n, p) % p) % p, y = (power(a[0], n + n + n, p) + power(a[1], n + n + n, p) + power(a[2], n + n + n, p)) % p, div = y * inversion(x, p) % p;
if (div) { a[0] = a[0] * div % p, a[1] = a[1] * div % p, a[2] = a[2] * div % p; break; }
} sort(a, a + 3);
cout << a[0] << ' ' << a[1] << ' ' << a[2] << '\n';
}
} // namespace lowspeed_song
:::
难度:无法评定
鲜活指数:无法评定
111. P6698 [BalticOI 2020] 病毒 (Day2)
:::info[Description] Link. :::
:::info[Solution]{open} 这个真难吧。。。。。。直接复读题解了
设
为了方便,后面记
初始条件就是对基因
然后考虑处理变异规则。假设有规则
| 但是直接拓扑排序显然是不行的,因为转移图可能存在环。因此考虑用一个类似 SPFA 的过程来维护维护转移过程,如果一个状态能够更新答案那么才将其加入到队列中即可。时间复杂度是 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int idx = 0, fail[N];
struct Node {
int ne[2], bad;
inline Node() { ne[0] = ne[1] = bad = 0; }
} tree[102];
struct Info {
int x;
vector<int> a;
} rule[102];
int G, n, m, f[52], g[52], h[102][102][52], vis[N];
vector<int> adj[N];
inline void ins() {
int n, now = 0; cin >> n;
while (n--) {
int c; cin >> c;
if (!tree[now].ne[c]) tree[now].ne[c] = ++idx;
now = tree[now].ne[c];
} tree[now].bad = 1;
}
inline void build() {
queue<int> q;
for (int i = 0; i < 2; ++i)
if (tree[0].ne[i]) q.emplace(tree[0].ne[i]);
while (q.size()) {
int t = q.front(); q.pop();
tree[t].bad |= tree[fail[t]].bad;
for (int i = 0; i < 2; ++i)
if (tree[t].ne[i]) fail[tree[t].ne[i]] = tree[fail[t]].ne[i], q.emplace(tree[t].ne[i]);
else tree[t].ne[i] = tree[fail[t]].ne[i];
}
}
inline int gao(int id) {
auto &[x, a] = rule[id];
memset(f, 0, sizeof f), memset(g, 0, sizeof g);
int ok = 0;
for (signed i = 0; i <= idx; ++i) {
for (signed j = 0; j <= idx; ++j) f[j] = inf;
f[i] = 0;
for (int &j : a) {
#pragma GCC unroll 8
for (signed k = 0; k <= idx; ++k) g[k] = inf;
#pragma GCC unroll 8
for (signed k = 0; k <= idx; ++k)
#pragma GCC unroll 8
for (int p = 0; p <= idx; ++p)
g[p] = min(g[p], f[k] + h[j][k][p]);
for (signed k = 0; k <= idx; ++k) f[k] = g[k];
}
#pragma GCC unroll 8
for (signed j = 0; j <= idx; ++j)
if (f[j] < h[x][i][j]) h[x][i][j] = f[j], ok = 1;
} return ok;
}
inline void sol() {
memset(h, 0x3f, sizeof h);
cin >> G >> n >> m;
for (int i = 0; i < n; ++i) {
cin >> rule[i].x;
int k; cin >> k;
while (k--) {
int x; cin >> x;
rule[i].a.emplace_back(x);
adj[x].emplace_back(i);
}
} while (m--) ins();
build();
memset(f, 0x3f, sizeof f);
for (int i = 0; i <= idx; ++i)
for (int j = 0; j < 2; ++j)
if(!tree[i].bad && !tree[tree[i].ne[j]].bad) h[j][i][tree[i].ne[j]] = 1;
queue<int> q;
q.emplace(0), q.emplace(1), vis[0] = vis[1] = 1;
while (q.size()) {
int t = q.front(); q.pop(), vis[t] = 0;
for (int &id : adj[t]) if (gao(id) && !vis[rule[id].x]) vis[rule[id].x] = 1, q.emplace(rule[id].x);
}
for (int i = 2; i < G; ++i) {
int res = inf;
for (int j = 0; j <= idx; ++j) res = min(res, h[i][0][j]);
if (res < inf / 2) cout << "NO " << res << '\n';
else cout << "YES\n";
}
}
} // namespace lowspeed_song
:::
难度:黑
鲜活指数:9
112. CF1286E Fedya the Potter Strikes Back
:::info[Description] Link. :::
:::info[Solution]{open}
太棒了我不会 KMP。
考虑每次只处理新加入字符对答案额外的贡献。设当前处理到的位置为
- 她期待的下一个字符等于当前字符,则继续延长长度。
- 否则,将会永久失效。
考虑类比 KMP 的结构,用 KMP 的失配链维护所有的 border,然后按照下一步期待哪个字符来分类以快速的删除所有失配的 border。因为每个 border 最多会被删除一次,因此均摊一下可得总删除次数为
然后考虑怎么算贡献。每个 border 对应一段区间,其贡献是区间权值最小值。考虑用 map 来对每个最小值
| 总时间复杂度为 |
|---|
:::success[Code]
namespace lowspeed_song {
int lg[N];
inline void init() {
lg[0] = -1;
for (int i = 1; i < N; ++i) lg[i] = lg[i >> 1] + 1;
}
inline void out(i128 x) {
if (x < 10) cout.put(x + '0');
else out(x / 10), cout.put(x % 10 + '0');
}
inline void write(i128 x) {
if (x < 0) x = -x, cout.put('-');
if (!x) cout.put('0');
else out(x);
}
char s[N];
int st[N][20], pi[N], ne[N][26], sum;
map<int, int> mp;
inline void add(int x, int v) {
sum += x * v;
if (!(mp[x] += v)) mp.erase(x);
}
inline void cap(int x) {
int cnt = 0;
for (auto it = mp.upper_bound(x); it != mp.end(); it = mp.erase(it))
cnt += it->second, sum -= it->first * it->second;
if (cnt) add(x, cnt);
}
inline int qry(int l, int r) {
int lgx = lg[r - l + 1];
return min(st[l][lgx], st[r - (1 << lgx) + 1][lgx]);
}
inline void sol() {
i128 res = 0;
int n, pre = inf; cin >> n;
for (int i = 1; i <= n; ++i) {
char c; int w; cin >> c >> w;
s[i] = (char)('a' + (c - 'a' + res % 26) % 26), w ^= (int)(res & ((1 << 30) - 1));
pre = min(pre, st[i][0] = w);
for (int j = 1; (1 << j) <= i; ++j)
st[i - (1 << j)][j] = min(st[i - (1 << j)][j - 1], st[i - (1 << (j - 1))][j - 1]);
if (i > 1) {
int p = pi[i - 1];
while (p && s[p + 1] != s[i]) p = pi[p];
if (s[p + 1] == s[i]) ++p;
pi[i] = p;
for (int j = 0; j < 26; ++j) ne[i][j] = ne[p][j];
if (p) ne[i][s[p + 1] - 'a'] = p;
for (int j = 0; j < 26; ++j) if (j != s[i] - 'a')
for (int k = ne[i - 1][j]; k; k = ne[k][j]) add(qry(i - k, i - 1), -1);
if (s[i] == s[1]) add(w, 1);
cap(w);
} res += sum + pre, write(res), cout << '\n';
}
}
} // namespace lowspeed_song
:::
难度:黑
鲜活指数:9
113. P5770 [JSOI2016] 无界单词
:::info[Description] Link. :::
:::info[Solution]{open}
无界单词其实就是没有真 border 的字符串。第一问考虑 dp,设
upd:第一问可以做到
| 第二问构造是简单的,直接按位贪心即可。 |
|---|
:::success[Code]
namespace lowspeed_song {
int n;
char s[N];
u64 f[N], g[N], pwr[N];
inline void init() {
pwr[0] = 1;
for (int i = 1; i <= 63; ++i) pwr[i] = pwr[i - 1] * 2;
for (int i = 1; i <= 64; ++i) {
f[i] = pwr[i];
for (int j = 1; j + j <= i; ++j) f[i] -= f[j] * pwr[i - j - j];
}
}
inline int same(int l1, int r1, int l2) {
for (int i = 0; i <= r1 - l1; ++i)
if (s[l1 + i] != s[l2 + i]) return 0;
return 1;
}
inline int good(int len) {
for (int i = 1; i <= len / 2; ++i)
if (same(1, i, len - i + 1)) return 0;
return 1;
}
inline u64 calc(int len) {
for (int i = 1; i <= n; ++i) {
if (i <= len) g[i] = good(i);
else {
g[i] = pwr[i - len];
for (int j = 1; j <= i / 2; ++j)
if (j <= i - len) g[i] -= g[j] * pwr[i - j - max(len, j)];
else if (same(1, len - i + j, i - j + 1)) g[i] -= g[j];
}
} return g[n];
}
inline void sol() {
u64 k; cin >> n >> k;
cout << f[n] << '\n';
for (int i = 1; i <= n; ++i) {
s[i] = 'a';
u64 cnt = calc(i);
if (cnt < k) k -= cnt, cout.put('b'), s[i] = 'b';
else cout.put('a'), s[i] = 'a';
} cout << '\n';
}
} // namespace lowspeed_song
:::
难度:青
鲜活指数:5
114. QOJ10333 Divide Digit String
:::info[Description] Link. :::
:::info[Solution]{open}
先考虑
把长度为
子串字典序排序可以 SA 或者 二分 + Hash,因此
然后考虑
| 总时间复杂度为 |
|---|
:::success[Code]
namespace lowspeed_song {
const u64 base = 13331;
u64 h[N], bit[N];
inline void init() {
bit[0] = 1;
for (int i = 1; i < N; ++i) bit[i] = bit[i - 1] * base;
}
char s[N];
int m, k;
namespace Suffix_Array {
int sa[N], rk[N], buc[N], height[N], n, idx;
struct Node {
int x[2], buc;
inline Node() { x[0] = x[1] = buc = 0; }
inline Node(int a, int b, int c) { x[0] = b, x[1] = a, buc = c; }
} a[N], b[N];
inline void sort(int up) {
for (int i : {0, 1}) {
for (int j = 0; j <= up + 1; ++j) buc[j] = 0;
for (int j = 1; j <= n; ++j) ++buc[a[j].x[i]];
for (int j = 1; j <= up; ++j) buc[j] += buc[j - 1];
for (int j = n; j; --j) b[buc[a[j].x[i]]--] = a[j];
for (int j = 1; j <= n; ++j) a[j] = b[j];
}
int idx = 1;
rk[a[1].buc] = 1;
for (int i = 2; i <= n; ++i)
if (a[i].x[0] == a[i - 1].x[0] && a[i].x[1] == a[i - 1].x[1]) rk[a[i].buc] = idx;
else rk[a[i].buc] = ++idx;
}
inline void build() {
for (int i = 1; i <= n; ++i) a[i] = Node(s[i], 1, i);
sort(128);
for (int i = 1; i < n; i <<= 1) {
for (int j = 1; j <= n; ++j) a[j] = Node(rk[j], (i + j <= n) ? rk[i + j] : 0, j);
sort(n);
} for (int i = 1; i <= n; ++i) sa[rk[i]] = i;
for (int i = 1; i <= n; ++i)
if (rk[i] == 1) height[rk[i]] = 0;
else {
int p = sa[rk[i] - 1], l = max<int>(0, height[rk[i - 1]] - 1);
while (s[i + l] == s[p + l]) ++l;
height[rk[i]] = l;
}
}
} using namespace Suffix_Array;
inline u64 get(int l, int r) {
return h[r] - h[l - 1] * bit[r - l + 1];
}
int buc_[N];
inline void sol() {
cin >> n >> m >> k;
scanf("%s", s + 1);
if (k > 1) {
for (int i = 1; i <= 9; ++i) {
for (int j = 0; j <= n; ++j) buc_[j] = 0;
int l = 1, r = n, cnt = m - k + 1;
while (l <= r && s[l] - '0' <= i) ++l, --cnt;
while (l <= r && s[r] - '0' <= i) --r, --cnt;
int lx = 0;
for (int j = l; j <= r; ++j)
if (s[j] - '0' > i) ++buc_[lx], lx = 0;
else ++lx;
int used = 0;
for (int j = n; j; --j)
while (cnt > 0 && buc_[j]) --buc_[j], cnt -= j, ++used;
if (used < k - 1 && cnt <= 0) {
cout << i << '\n';
return;
}
} throw;
} else {
build();
for (int i = 1; i <= n; ++i) h[i] = h[i - 1] * base + s[i];
int len = (n + m - 1) / m, v1 = n - m * (len - 1), v2 = m - v1;
vector<int> vec;
for (int i = 1; i <= n; ++i)
if (sa[i] + len - 1 <= n) vec.emplace_back(sa[i]);
function<bool(int, int)> comp = [&](int l, int r) {
if (get(l, l + len - 1) == get(r, r + len - 1)) return true;
return rk[l] < rk[r];
};
function<int(int)> chk = [&](int o) {
int pos = 1, x = v1, y = v2;
while (x || y) {
if (x && comp(pos, o)) --x, pos += len;
else if (y) --y, pos += len - 1;
else return 0;
} return 1;
};
// cerr << "???\n";
int l = 0, r = vec.size() - 1, best = -1;
while (l <= r) {
int mid = l + r >> 1;
chk(vec[mid]) ? (best = mid, r = mid - 1) : (l = mid + 1);
}
for (int i = vec[best]; i < vec[best] + len; ++i) cout << s[i];
cout << '\n';
}
}
} // namespace lowspeed_song
:::
难度:紫-
鲜活指数:5
115. P11284 「GFOI Round 2」Strings
:::info[Description] Link. :::
:::info[Solution]{open}
题目的条件可以看作是存在整数
考虑 dp,设
把上面东西合起来写可以得到递推式
然后考虑枚举最短回文前缀的长度
因此总的答案就是
(我想到这里就不会了)
记
先考虑怎么优化递推的时间复杂度。已知
现在考虑怎么优化算
然后考虑分别算两个求和的值。第一个求和中,
考虑分析一下时间复杂度。看上去很爆炸,但是考虑使用记忆化搜索来优化,设递归深度为 map 维护)等操作,因此总时间复杂度可以近似看作是
需要单独处理
递推过程中可能出问题的地方有下面三类:
第一类显然当且仅当
现在假设
当
比较复杂的情况是
如果
现在考虑特判这三个 corner case:
-
-
-
- 然后考虑算答案。当
m\equiv -1 时固定l 值后剩余部分的贡献为\sum\limits_{k=3l}^n(-1)^{k-2l}=\sum\limits_{k=3l}^n(-1)^k ,只有l\equiv n\pmod 2 时上式的值不为0 。此时考虑进一步分类讨论:- 如果
2\mid n 则只需要枚举偶数l=2i ,最终的答案可以表示为\lfloor\frac L2\rfloor-G(\lfloor\frac L2\rfloor) 的形式,其中L=\lfloor\frac n3\rfloor 。 - 如果
2\nmid n 则只需要枚举奇数l=2i+1 ,最终的答案可以表示为\lfloor\frac{L+1}2\rfloor-G(\lfloor\frac{L-1}2\rfloor) 的形式,其中L=\lfloor\frac n3\rfloor 。
- 如果
-
很显然 G(n) 可直接递归O(\log n) 时间复杂度求解,因此该部分算答案的总时间复杂度也是O(\log n) 的。
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int n, m;
map<pair<int, int>, int> mp;
inline int S(int n, int t) {
if (!n) return 0;
if (mp.count({n, t})) return mp[{n, t}];
int a = power(m, ((t + 1) % (mod - 1) + mod - 1) % (mod - 1), mod), b = power(m, ((i128)(n + 1) * (t + 1) % (mod - 1) + mod - 1) % (mod - 1), mod);
return mp[{n, t}] = inversion(mod + 1 - a) * (a - b - S(n / 2, t * 2) + mod * 2 + b * S(n / 2, -2) % mod) % mod;
}
inline int sumF(int n) {
if (!n) return 0;
return (mod + mod - sumF(n >> 1) - (n + 1) * i2 % mod) % mod;
}
inline int calc(int n) {
if (n & 1) return ((n / 3 + 1) / 2 % mod + mod - sumF(n / 2)) % mod;
return (n / 3 % mod + mod - sumF(n / 2)) % mod;
}
inline void sol() {
cin >> n >> m; m %= mod;
if (!m) cout << 0 << '\n';
else if (m == 1) cout << (n - 2) % mod << '\n';
else if (m == mod - 1) cout << calc(n) << '\n';
else {
mp.clear();
cout << inversion((1 - m + mod) % mod) * (S(n / 3, 1) - power(m, n + 1, mod) * S(n / 3, -2) % mod + mod) % mod << '\n';
}
}
} // namespace lowspeed_song
:::
难度:彩-
鲜活指数:-15
116. P10896 移言丁真:Unavoided linyue
:::info[Description] Link. :::
:::info[Solution]{open}
套路的考虑把括号串放到二维平面上考虑,记 ( 的权值为 ) 的权值为
因此一个括号串可以用一个三元组
接下来考虑这些括号串已经确定时,怎么排列才能让最终的权值最小。假设拼接后整条路径的最低点在第
考虑分别计算两个部分的期望值。对长度为
然后考虑算
考虑继续在折线图上考虑问题。把原来的折线路径整体向上平移
记上面的东西值为
因为总共存在
记
| 预处理阶乘,阶乘逆和二的幂的逆元即可做到线性求解问题。 |
|---|
:::success[Code]
namespace lowspeed_song {
int fac[N], inv[N], ifac[N], pwr[N], ipwr[N];
inline void init() {
fac[0] = inv[0] = ifac[0] = 1;
fac[1] = inv[1] = ifac[1] = 1;
for (int i = 2; i < N; ++i) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = mod - inv[mod % i] * (mod / i) % mod;
ifac[i] = ifac[i - 1] * inv[i] % mod;
}
pwr[0] = ipwr[0] = 1;
for (int i = 1; i < N; ++i) {
pwr[i] = pwr[i - 1] * 2 % mod;
ipwr[i] = ipwr[i - 1] * i2 % mod;
}
}
inline int binom(int a, int b) {
if (b < 0 || a < b) return 0;
return fac[a] * ifac[b] % mod * ifac[a - b] % mod;
}
int f[N], a[N];
inline void sol() {
int n = 0, m, res = 0, mx = 0; cin >> m;
for (int i = 1; i <= m; ++i) cin >> a[i], n += a[i], mx = max(mx, a[i] / 2);
for (int i = 0; i < N; ++i) f[i] = 1;
for (int i = 1; i <= m; ++i) {
res = (res + a[i] * i2 % mod + mod - a[i] * binom(a[i] - 1, (a[i] - 1) / 2) % mod * ipwr[a[i]] % mod) % mod;
int pre = 0;
for (int j = 1; j <= a[i] / 2; ++j) {
pre = (pre + ((a[i] & 1) ? 2 * binom(a[i], a[i] / 2 - j + 1) % mod : binom(a[i] + 1, a[i] / 2 - j + 1))) % mod;
f[j] = f[j] * pre % mod * ipwr[a[i]] % mod;
}
} for (int i = 1; i <= mx; ++i) res = (res + mod - 1 + f[i]) % mod;
cout << res << '\n';
}
} // namespace lowspeed_song
:::
难度:黑
鲜活指数:10
117. CF1762E Tree Sum
:::info[Description] Link. :::
:::info[Solution]{open}
容易发现
然后如果树的形态确定,则固定树的根为
也就是说,边权之和一侧点的数量的奇偶性有关,和树的具体形态无关。而且当
| 最后考虑算答案。枚举 |
|---|
:::success[Code]
namespace lowspeed_song {
int fac[N], inv[N], ifac[N], pwr[N], ipwr[N];
inline void init() {
fac[0] = inv[0] = ifac[0] = 1;
fac[1] = inv[1] = ifac[1] = 1;
for (int i = 2; i < N; ++i) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = mod - inv[mod % i] * (mod / i) % mod;
ifac[i] = ifac[i - 1] * inv[i] % mod;
}
pwr[0] = ipwr[0] = 1;
for (int i = 1; i < N; ++i) {
pwr[i] = pwr[i - 1] * 2 % mod;
ipwr[i] = ipwr[i - 1] * i2 % mod;
}
}
inline int binom(int a, int b) {
if (b < 0 || a < b) return 0;
return fac[a] * ifac[b] % mod * ifac[a - b] % mod;
}
inline void sol() {
int n; cin >> n;
if (n & 1) { cout << 0 << '\n'; return; }
int res = 0;
for (int i = 1; i < n; ++i) {
int val = binom(n - 2, i - 1) * power(i, i - 1, mod) % mod * power(n - i, n - i - 1, mod) % mod;
if (i & 1) res = (res + mod - val) % mod;
else res = (res + val) % mod;
} cout << res << '\n';
}
} // namespace lowspeed_song
:::
难度:紫
鲜活指数:8
118. P14368 [JOISC 2018] 修行 / Asceticism
:::info[Description] Link. :::
:::info[Solution]{open}
问题可以看作是问有多少个
设答案为
| 对这个东西做二项式反演可得 |
|---|
:::success[Code]
namespace lowspeed_song {
int fac[N], inv[N], ifac[N], pwr[N], ipwr[N];
inline void init() {
fac[0] = inv[0] = ifac[0] = 1;
fac[1] = inv[1] = ifac[1] = 1;
for (int i = 2; i < N; ++i) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = mod - inv[mod % i] * (mod / i) % mod;
ifac[i] = ifac[i - 1] * inv[i] % mod;
}
pwr[0] = ipwr[0] = 1;
for (int i = 1; i < N; ++i) {
pwr[i] = pwr[i - 1] * 2 % mod;
ipwr[i] = ipwr[i - 1] * i2 % mod;
}
}
inline int binom(int a, int b) {
if (b < 0 || a < b) return 0;
return fac[a] * ifac[b] % mod * ifac[a - b] % mod;
}
inline void sol() {
int n, k; cin >> n >> k;
int res = 0;
for (int i = 0; i <= k; ++i) {
int val = binom(n + 1, i) * power(k - i, n, mod) % mod;
if (i & 1) res = (res + mod - val) % mod;
else res = (res + val) % mod;
} cout << res << '\n';
}
} // namespace lowspeed_song
:::
难度:紫-
鲜活指数:2
119. CF1548C The Three Little Pigs
:::info[Description] Link. :::
:::info[Solution]{open}
题目中给的式子相当于是
设
假设存在某个
没有房子的高度 >x
此时所有的
| 因此建模的正确性得证,直接模拟上述流程跑 dinic 即可。一个小细节是注意每组限制条件都需要额外新开一个辅助点 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
namespace Flow {
int S, T, dep[N], cur[N];
struct edg {
int e, f, o;
edg(int a, int b) { e = a, f = b, o = 0; }
} ; vector<edg> adj[N];
inline void link(int s, int e, int f) {
adj[s].push_back({edg(e, f)}), adj[e].push_back(edg(s, 0));
int s1 = adj[s].size() - 1, s2 = adj[e].size() - 1;
adj[s][s1].o = s2, adj[e][s2].o = s1;
}
inline bool bfs() {
queue<int> q;
memset(dep, 0, sizeof dep), memset(cur, 0, sizeof cur);
dep[S] = 1, q.emplace(S);
while (q.size()) {
int p = q.front(); q.pop();
for (auto &[r, f, _] : adj[p]) if (f && !dep[r]) {
dep[r] = dep[p] + 1, q.emplace(r);
if (r == T) return 1;
}
} return 0;
}
inline int dfs(int p, int f) {
if (p == T) return f;
int s = 0;
for (int &i = cur[p]; i < adj[p].size(); ++i) {
if (!f) break;
int q = adj[p][i].e, fx = adj[p][i].f;
if (fx && dep[p] + 1 == dep[q]) {
int delta = dfs(q, min(f, fx));
if (!delta) continue;
adj[p][i].f -= delta, adj[q][adj[p][i].o].f += delta;
f -= delta, s += delta;
}
}
return s ? s : (dep[p] = 0);
}
inline int dinic() {
int s = 0;
while (bfs()) s += dfs(S, inf);
return s;
}
} // namespace Flow
inline void sol([[maybe_unused]]int __testcase_id) {
int n, h, m; cin >> n >> h >> m;
Flow::S = N - 1, Flow::T = N - 2;
function<int(int, int)> get = [&](int i, int j) { return (i - 1) * (h + 2) + j; };
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= h + 1; ++j)
Flow::link(get(i, j), get(i, j + 1), h * h - (j - 1) * (j - 1));
for (int i = 1; i <= n; ++i) {
Flow::link(Flow::S, get(i, 1), inf);
Flow::link(get(i, h + 2), Flow::T, inf);
}
int idx = n * (h + 2);
while (m--) {
int l, r, x, c; cin >> l >> r >> x >> c;
if (x == h) continue;
Flow::link(++idx, Flow::T, c);
for (int i = l; i <= r; ++i) Flow::link(get(i, x + 2), idx, inf);
} cout << n * h * h - Flow::dinic() << '\n';
}
} // namespace lowspeed_song
:::
难度:紫
鲜活指数:7
131. P11531 [THUPC 2025 初赛] 检查站
:::info[Description] Link. :::
:::info[Solution]{open}
套路的考虑最小割建模求解。把每个分部点
| 然后对原图上的一条边 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
namespace Flow {
int S, T, dep[N], cur[N];
struct edg {
int e, f, o;
edg(int a, int b) { e = a, f = b, o = 0; }
} ; vector<edg> adj[N];
inline void link(int s, int e, int f) {
adj[s].push_back({edg(e, f)}), adj[e].push_back(edg(s, 0));
int s1 = adj[s].size() - 1, s2 = adj[e].size() - 1;
adj[s][s1].o = s2, adj[e][s2].o = s1;
}
inline bool bfs() {
queue<int> q;
memset(dep, 0, sizeof dep), memset(cur, 0, sizeof cur);
dep[S] = 1, q.emplace(S);
while (q.size()) {
int p = q.front(); q.pop();
for (auto &[r, f, _] : adj[p]) if (f && !dep[r]) {
dep[r] = dep[p] + 1, q.emplace(r);
if (r == T) return 1;
}
} return 0;
}
inline int dfs(int p, int f) {
if (p == T) return f;
int s = 0;
for (int &i = cur[p]; i < adj[p].size(); ++i) {
if (!f) break;
int q = adj[p][i].e, fx = adj[p][i].f;
if (fx && dep[p] + 1 == dep[q]) {
int delta = dfs(q, min(f, fx));
if (!delta) continue;
adj[p][i].f -= delta, adj[q][adj[p][i].o].f += delta;
f -= delta, s += delta;
}
}
return s ? s : (dep[p] = 0);
}
inline int dinic() {
int s = 0;
while (bfs()) s += dfs(S, inf);
return s;
}
} // namespace Flow
inline void sol([[maybe_unused]]int __testcase_id) {
int n, m, c; cin >> n >> m >> c;
Flow::S = 1, Flow::T = n;
for (int i = n + 1; i <= n + c; ++i) Flow::link(i, i + c, 1);
for (int i = 1; i <= c; ++i) { int t; cin >> t; }
for (int i = 1; i <= m; ++i) {
int a, b, r; cin >> a >> b >> r;
Flow::link(a, r + n, inf), Flow::link(n + c + r, b, inf);
} cout << Flow::dinic() << '\n';
}
} // namespace lowspeed_song
:::
难度:紫-
鲜活指数:4
132. CF1061E Politics
:::info[Description] Link. :::
:::info[Solution]{open}
先把子树限制拆分成若干个互相独立的块,则一个块只有一个限制条件。此时对每个树都可以把所有点恰好分割到若干个块内,并知道每个块内必须恰好选多少个点。然后考虑一个城市
| 直接跑 MCMF 即可。具体的,从源点 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
namespace MCMF {
int S, T, hhh[N], e[N], f[N], ne[N], w[N], idxx, q[N], d[N], pre[N], incf[N], st[N];
inline void ae(int a, int b, int c, int d) {
e[idxx] = b, w[idxx] = d, f[idxx] = c, ne[idxx] = hhh[a], hhh[a] = idxx++;
e[idxx] = a, w[idxx] = -d, f[idxx] = 0, ne[idxx] = hhh[b], hhh[b] = idxx++;
}
inline int spfa() {
int hh = 0, tt = 1;
memset(d, 0x3f, sizeof d);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = inf;
while (hh != tt) {
int t = q[hh++];
if (hh == N) hh = 0;
st[t] = 0;
for (int i = hhh[t]; ~i; i = ne[i]) {
int v = e[i];
if (f[i] && d[v] > d[t] + w[i]) {
d[v] = d[t] + w[i];
pre[v] = i, incf[v] = min(incf[t], f[i]);
if (!st[v]) {
q[tt++] = v;
if (tt == N) tt = 0;
st[v] = 1;
}
}
}
}
return incf[T] > 0;
}
inline void ek(int &F, int &C) {
F = C = 0;
while (spfa()) {
int tt = incf[T];
F += tt, C += tt * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1]) f[pre[i]] -= tt, f[pre[i] ^ 1] += tt;
}
}
}
vector<int> adj[N], adj2[N];
int n, r1, r2, a[N], req[N], req2[N], bel[N], bel2[N], need[N], need2[N];
inline void dfs(int u, int fa, int las, vector<int> adj[N], int req[N], int bel[N], int need[N]) {
if (~req[u]) {
if (~las) need[las] -= req[u];
las = u;
} bel[u] = las;
for (int &v : adj[u]) if (v != fa) dfs(v, u, las, adj, req, bel, need);
}
inline int build(vector<int> adj[N], int rt, int req[N], int bel[N], int need[N]) {
for (int i = 0; i <= n + 1; ++i) bel[i] = -1, need[i] = 0;
for (int i = 1; i <= n; ++i) if (~req[i]) need[i] = req[i];
dfs(rt, 0, -1, adj, req, bel, need);
for (int i = 1; i <= n; ++i)
if (need[i] < 0 && ~req[i]) return 0;
return 1;
}
inline void sol([[maybe_unused]]int __testcase_id) {
memset(MCMF::hhh, -1, sizeof MCMF::hhh);
cin >> n >> r1 >> r2;
for (int i = 1; i <= n; ++i) cin >> a[i];
for (int i = 1; i < n; ++i) {
int a, b; cin >> a >> b;
adj[a].emplace_back(b), adj[b].emplace_back(a);
} for (int i = 1; i < n; ++i) {
int a, b; cin >> a >> b;
adj2[a].emplace_back(b), adj2[b].emplace_back(a);
}
memset(req, -1, sizeof req);
memset(req2, -1, sizeof req2);
int q; cin >> q;
while (q--) {
int k, x; cin >> k >> x;
req[k] = x;
} cin >> q;
while (q--) {
int k, x; cin >> k >> x;
req2[k] = x;
}
if (req[r1] != req2[r2]) cout << -1 << '\n';
else if (!build(adj, r1, req, bel, need)) cout << -1 << '\n';
else if (!build(adj2, r2, req2, bel2, need2)) cout << -1 << '\n';
else {
MCMF::S = 0, MCMF::T = n << 1 | 1;
for (int i = 1; i <= n; ++i)
if (~req[i]) MCMF::ae(MCMF::S, i, need[i], 0);
for (int i = 1; i <= n; ++i)
if (~req2[i]) MCMF::ae(n + i, MCMF::T, need2[i], 0);
for (int i = 1; i <= n; ++i)
MCMF::ae(bel[i], bel2[i] + n, 1, -a[i]);
int F, C; MCMF::ek(F, C);
cout << ((F != req[r1]) ? -1 : -C) << '\n';
}
}
} // namespace lowspeed_song
:::
难度:紫-
鲜活指数:5
133. CF2068K Amusement Park Rides
:::info[Description] Link. :::
:::info[Solution]{open}
相同的
设不同的值为
一个简单的想法是直接二分答案时刻为
因为一次只会新增一个时间点,而且其到汇点
| 时间复杂度是 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
namespace Flow {
int S, T, dep[N], cur[N];
struct edg {
int e, f, o;
edg(int a, int b) { e = a, f = b, o = 0; }
} ; vector<edg> adj[N];
inline void link(int s, int e, int f) {
adj[s].push_back({edg(e, f)}), adj[e].push_back(edg(s, 0));
int s1 = adj[s].size() - 1, s2 = adj[e].size() - 1;
adj[s][s1].o = s2, adj[e][s2].o = s1;
}
inline bool bfs() {
queue<int> q;
memset(dep, 0, sizeof dep), memset(cur, 0, sizeof cur);
dep[S] = 1, q.emplace(S);
while (q.size()) {
int p = q.front(); q.pop();
for (auto &[r, f, _] : adj[p]) if (f && !dep[r]) {
dep[r] = dep[p] + 1, q.emplace(r);
if (r == T) return 1;
}
} return 0;
}
inline int dfs(int p, int f) {
if (p == T) return f;
int s = 0;
for (int &i = cur[p]; i < adj[p].size(); ++i) {
if (!f) break;
int q = adj[p][i].e, fx = adj[p][i].f;
if (fx && dep[p] + 1 == dep[q]) {
int delta = dfs(q, min(f, fx));
if (!delta) continue;
adj[p][i].f -= delta, adj[q][adj[p][i].o].f += delta;
f -= delta, s += delta;
}
}
return s ? s : (dep[p] = 0);
}
inline int dinic() {
int s = 0;
while (bfs()) s += dfs(S, inf);
return s;
}
} // namespace Flow
int id[N];
inline void sol([[maybe_unused]]int __testcase_id) {
int n; cin >> n;
map<int, int> cnt;
for (int i = 0; i < n; ++i) {
int x; cin >> x;
++cnt[x];
}
vector<int> p, c;
for (auto &[x, y] : cnt) p.emplace_back(x), c.emplace_back(y);
Flow::S = 0, Flow::T = 1;
int idx = 2;
for (int i = 0; i < p.size(); ++i) id[i] = idx++, Flow::link(Flow::S, id[i], c[i]);
map<int, vector<int>> mp;
for (int i = 0; i < p.size(); ++i) mp[p[i]].emplace_back(i);
int flow = 0, res = 0;
while (flow < n) {
auto [t, v] = *mp.begin();
mp.erase(mp.begin());
int u = idx++;
for (int &i : v) Flow::link(id[i], u, 1);
Flow::link(u, Flow::T, 1);
if (Flow::dinic()) {
++flow;
for (int &i : v) mp[t + p[i]].emplace_back(i);
} res = t;
} cout << res << '\n';
for (int i = 0; i <= idx; ++i) Flow::adj[i].clear();
}
} // namespace lowspeed_song
:::
难度:紫
鲜活指数:6
134. CF1525F Goblins And Gnomes
:::info[Description] Link. :::
:::info[Solution]{open}
套路的考虑把每个点拆成左部点和右部点,对原图的边
然后考虑封锁一个点对答案的影响。如果操作
由 Konig 定理可知二分图最大匹配大小等于最小电覆盖大小,因此如果删除最小点覆盖终点任意一个点,那么剩下的点覆盖大小最多为
| 然后考虑 dp。设 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int f[55][55], pre[55][55], zx[55], x[55], y[55], banL[55], banR[55], mat[55], used[55], mp[55][55], n, m, k;
inline int dfs(int u) {
for (int i = 1; i <= n; ++i) {
if (!mp[u][i] || banR[i] || used[i]) continue;
used[i] = 1;
if (!mat[i] || dfs(mat[i])) return mat[i] = u, 1;
} return 0;
}
inline int calc() {
memset(mat, 0, sizeof mat);
int res = 0;
for (int i = 1; i <= n; ++i)
if (!banL[i]) memset(used, 0, sizeof used), res += dfs(i);
return res;
}
vector<int> adj[N];
inline void sol([[maybe_unused]]int __testcase_id) {
cin >> n >> m >> k;
while (m--) {
int a, b; cin >> a >> b;
adj[a].emplace_back(b), mp[a][b] = 1;
} for (int i = 1; i <= k; ++i) cin >> x[i] >> y[i];
vector<int> op;
int M = calc();
for (int i = M; i; --i) {
for (int j = 1; j <= n; ++j) {
if (!banL[j]) {
banL[j] = 1;
if (calc() < i) { op.emplace_back(j); break; }
banL[j] = 0;
} if (!banR[j]) {
banR[j] = 1;
if (calc() < i) { op.emplace_back(-j); break; }
banR[j] = 0;
}
}
}
// cerr << "!!!\n";
memset(f, -0x3f, sizeof f);
memset(pre, -1, sizeof pre);
f[0][0] = 0;
for (int i = 1; i <= k; ++i)
for (int j = 0; j <= M; ++j) {
for (int jj = j; jj <= M; ++jj) {
if (i + M - jj >= n) continue;
int w = max<int>(0, x[i] - (jj - j) * y[i]);
if (f[i][jj] < f[i - 1][j] + w) f[i][jj] = f[i - 1][j] + w, pre[i][jj] = j;
}
}
// cerr << "???\n";
zx[k] = max_element(f[k], f[k] + M + 1) - f[k];
for (int i = k; i; --i) zx[i - 1] = pre[i][zx[i]];
vector<int> res;
for (int i = 1; i <= k; ++i) {
for (int j = zx[i - 1]; j < zx[i]; ++j) res.emplace_back(op[j]);
res.emplace_back(0);
} cout << res.size() << '\n';
for (int &i : res) cout << i << ' ';
cout << '\n';
}
} // namespace lowspeed_song
:::
难度:紫+
鲜活指数:7
135. CF1766F MCF
:::info[Description] Link. :::
:::info[Solution]{open}
题目要求对每条边
考虑怎么处理提前流
但是
为了处理奇数添加的额外流量信息,考虑搞超级源汇点
然后因为没有限制图的流量,因此还需要额外从
| 然后每次增广直接找费用为负的 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
namespace MCMF {
int S, T, hhh[N], e[N], f[N], ne[N], idxx, q[N], pre[N], incf[N], st[N];
int w[N], d[N];
inline int ae(int a, int b, int c, int cost) {
int id = idxx;
e[idxx] = b, w[idxx] = cost, f[idxx] = c, ne[idxx] = hhh[a], hhh[a] = idxx++;
e[idxx] = a, w[idxx] = -cost, f[idxx] = 0, ne[idxx] = hhh[b], hhh[b] = idxx++;
return id;
}
inline int spfa() {
int hh = 0, tt = 1;
fill(d, d + N, inf);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = inf, st[S] = 1;
while (hh != tt) {
int t = q[hh++];
if (hh == N) hh = 0;
st[t] = 0;
for (int i = hhh[t]; ~i; i = ne[i]) {
int v = e[i];
if (f[i] && d[v] > d[t] + w[i]) {
d[v] = d[t] + w[i];
pre[v] = i, incf[v] = min(incf[t], f[i]);
if (!st[v]) {
q[tt++] = v;
if (tt == N) tt = 0;
st[v] = 1;
}
}
}
}
return incf[T] > 0;
}
inline void ek() {
while (spfa()) {
if (d[T] > 0) break;
int flow = incf[T];
for (int i = T; i != S; i = e[pre[i] ^ 1]) f[pre[i]] -= flow, f[pre[i] ^ 1] += flow;
}
}
inline void init() { memset(hhh, -1, sizeof hhh), idxx = 0; }
}
int c[N], id[N], e[N];
inline void sol([[maybe_unused]]int __testcase_id) {
int n, m; cin >> n >> m;
MCMF::init();
for (int i = 0; i < m; ++i) {
int x, y, w;
cin >> x >> y >> c[i] >> w;
if (c[i] & 1) ++e[x], --e[y];
id[i] = MCMF::ae(x, y, c[i] >> 1, 2 * w);
}
if (e[1] & 1) ++e[1], --e[n];
MCMF::S = N - 1, MCMF::T = N - 2;
vector<int> vec;
for (int i = 1; i <= n; ++i) {
if (e[i] & 1) { cout << "Impossible\n"; return; }
int x = abs(e[i]) / 2;
if (e[i] < 0) vec.emplace_back(MCMF::ae(MCMF::S, i, x, -1e9));
else if (e[i] > 0) vec.emplace_back(MCMF::ae(i, MCMF::T, x, -1e9));
}
MCMF::ae(MCMF::S, 1, inf, 0), MCMF::ae(n, MCMF::T, inf, 0), MCMF::ek();
for (int x : vec) if (MCMF::f[x]) { cout << "Impossible\n"; return; }
cout << "Possible\n";
for (int i = 0; i < m; ++i) cout << c[i] - 2 * MCMF::f[id[i]] << ' ';
cout << '\n';
}
} // namespace lowspeed_song
:::
难度:紫+
鲜活指数:5
136. CF708D Incorrect Flow
:::info[Description] Link. :::
:::info[Solution]{open} 好玩的来了。
考虑对于一条
f\le c
此时当前的容量是合法的,有下面的几种操作方式:
- 减少流量:每减少
1 就需要让f 减少1 ,花费1 的代价。本质可以看作是撤销原来一单位的u\to v 流量,也就是让费用流沿着反方向v\to u 走一单位。边的容量为f ,费用为1 。 - 增加流量,但是不超过
c :每增加1 就需要让f 增加1 ,花费1 的代价。u\to v 的边容量为c-f 费用为1 。 - 增加流量且超过
c :容量和费用都需要增加1 ,因此花费2 的代价。u\to v 的边容量为+\infin 费用为2 。
f>c
这条边初始就不合法,因此需要先花费
- 首先需要把前
f-c 单位的流量退流,连一条v\to u 容量为f-c 费用为0 的边,然后提前增加f-c 的代价即可。 - 然后如果要继续退流,则每单位的代价为
1 ,连一条v\to u 容量为c 费用为1 的边即可。 - 如果要增加流量,则每单位的代价为
2 ,连一条u\to v 容量为+\infin 费用为2 的边即可。
然后还需要考虑流量不守恒的问题。记
因为
| 最后算答案只需要跑一遍从 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
namespace MCMF {
int S, T, hhh[N], e[N], f[N], ne[N], w[N], idxx, q[N], d[N], pre[N], incf[N], st[N];
inline void ae(int a, int b, int c, int d) {
e[idxx] = b, w[idxx] = d, f[idxx] = c, ne[idxx] = hhh[a], hhh[a] = idxx++;
e[idxx] = a, w[idxx] = -d, f[idxx] = 0, ne[idxx] = hhh[b], hhh[b] = idxx++;
}
inline int spfa() {
int hh = 0, tt = 1;
memset(d, 0x3f, sizeof d);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = inf;
while (hh != tt) {
int t = q[hh++];
if (hh == N) hh = 0;
st[t] = 0;
for (int i = hhh[t]; ~i; i = ne[i]) {
int v = e[i];
if (f[i] && d[v] > d[t] + w[i]) {
d[v] = d[t] + w[i];
pre[v] = i, incf[v] = min(incf[t], f[i]);
if (!st[v]) {
q[tt++] = v;
if (tt == N) tt = 0;
st[v] = 1;
}
}
}
}
return incf[T] > 0;
}
inline void ek(int &F, int &C) {
F = C = 0;
while (spfa()) {
int tt = incf[T];
F += tt, C += tt * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1]) f[pre[i]] -= tt, f[pre[i] ^ 1] += tt;
}
}
}
int d[N];
inline void sol([[maybe_unused]]int __testcase_id) {
int n, m; cin >> n >> m;
memset(MCMF::hhh, -1, sizeof MCMF::hhh);
int res = 0;
while (m--) {
int u, v, c, f; cin >> u >> v >> c >> f, d[u] += f, d[v] -= f;
if (f <= c) MCMF::ae(v, u, f, 1), MCMF::ae(u, v, c - f, 1), MCMF::ae(u, v, inf, 2);
else res += f - c, MCMF::ae(v, u, f - c, 0), MCMF::ae(v, u, c, 1), MCMF::ae(u, v, inf, 2);
} MCMF::ae(n, 1, inf, 0), MCMF::S = n + 1, MCMF::T = n + 2;
for (int i = 1; i <= n; ++i)
if (d[i] > 0) MCMF::ae(i, MCMF::T, d[i], 0);
else if (d[i] < 0) MCMF::ae(MCMF::S, i, -d[i], 0);
int F, C; MCMF::ek(F, C);
cout << res + C << '\n';
}
} // namespace lowspeed_song
:::
难度:黑-
鲜活指数:8
137. CF843E Maximum Flow
:::info[Description] Link :::
:::info[Solution]{open}
特别神秘的题目。考虑最大流的残量网络。设从源点
考虑最小割建模。对一条原边
如果
对着这个最小割图跑一遍 dinic 得到该图的残量网络,然后还可以得到最小割两侧的点集
对
| 对 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
// 最小费用可行流
namespace MF {
int S, T, hhh[N], e[N], f[N], ne[N], idxx, q[N], dep[N], cur[N];
inline void init() { memset(hhh, -1, sizeof hhh), idxx = 0; }
inline int ae(int a, int b, int c) {
int id = idxx;
e[idxx] = b, f[idxx] = c, ne[idxx] = hhh[a], hhh[a] = idxx++;
e[idxx] = a, f[idxx] = 0, ne[idxx] = hhh[b], hhh[b] = idxx++;
return id;
}
inline int bfs() {
int hh = 0, tt = 0;
memset(dep, -1, sizeof dep);
q[tt++] = S, dep[S] = 0;
while (hh < tt) {
int u = q[hh++];
for (int i = hhh[u]; ~i; i = ne[i]) {
int v = e[i];
if (f[i] && !~dep[v]) dep[v] = dep[u] + 1, q[tt++] = v;
}
}
return dep[T] != -1;
}
inline int dfs(int u, int lim) {
if (u == T) return lim;
for (int &i = cur[u]; ~i; i = ne[i]) {
int v = e[i];
if (f[i] && dep[v] == dep[u] + 1) {
int x = dfs(v, min(lim, f[i]));
if (x) {
f[i] -= x, f[i ^ 1] += x;
return x;
}
}
} return 0;
}
inline int dinic(int s, int t) {
S = s, T = t;
int res = 0, x;
while (bfs()) {
memcpy(cur, hhh, sizeof hhh);
while ((x = dfs(S, inf))) res += x;
}
return res;
}
inline void reach(int u, int *vis) {
vis[u] = 1;
for (int i = hhh[u]; ~i; i = ne[i])
if (f[i] && !vis[e[i]]) reach(e[i], vis);
}
}
int d[N], u[N], v[N], g[N], id[N], vis[N];
inline void sol([[maybe_unused]]int __testcase_id) {
int n, m, s, t; cin >> n >> m >> s >> t; MF::init();
for (int i = 1; i <= m; ++i) {
cin >> u[i] >> v[i] >> g[i];
if (!g[i]) MF::ae(u[i], v[i], inf);
else MF::ae(u[i], v[i], 1), MF::ae(v[i], u[i], inf);
}
int res = MF::dinic(s, t);
memset(vis, 0, sizeof vis);
MF::reach(s, vis), MF::init();
memset(d, 0, sizeof d);
int S = n + 1, T = n + 2;
MF::ae(t, s, inf);
for (int i = 1; i <= m; ++i) if (g[i])
id[i] = MF::ae(u[i], v[i], inf), ++d[u[i]], --d[v[i]];
int need = 0;
for (int i = 1; i <= n; ++i)
if (d[i] > 0) MF::ae(i, T, d[i]), need += d[i];
else if (d[i] < 0) MF::ae(S, i, -d[i]);
int F = MF::dinic(S, T);
cout << res << '\n';
for (int i = 1; i <= m; ++i)
if (!g[i]) cout << 0 << ' ' << 1 << '\n';
else cout << MF::f[id[i] ^ 1] + 1 << ' ' << (vis[u[i]] && !vis[v[i]] ? MF::f[id[i] ^ 1] + 1 : MF::f[id[i] ^ 1] + 2) << '\n';
}
} // namespace lowspeed_song
:::
难度:黑
鲜活指数:8
138. AT_agc038_f [AGC038F] Two Permutations
:::info[Description] Link. :::
:::info[Solution]{open}
套路的在置换环的角度下考虑。把
因此可以看作是给每个置换环设一个布尔变量。对
最大化
- 若
P_i=Q_i=i 则不管怎样都只能有A_i=B_i=i 。 - 若
P_i=i,Q_i\neq i 则此时A_i 的值永远只能是i ,B_i=i 当且仅当Q 环不动也就是y=0 。因此此时A_i=B_i 当且仅当y=0 。产生一个一元费用c_{y,0}=1 ,连边y\to T ,边的容量为1 。 - 若
P_i\neq i,Q_i=i 则情况和前面的正好相反,此时产生一元费用c_{x,1}=1 ,连边S\to x ,边的容量为1 。 - 然后考虑复杂的二元关系。如果
P_i\neq Q_i 而且有P_i\neq i,Q_i\neq i 则如果想要让A_i=B_i 必须满足两边都取到i 也就是x=1,y=0 。因此费用表中只有c_{1,0}=1 。因为c_{1,0} 可以用反向边来表示,因此连一条y\to x ,容量为1 的边即可。 - 最后剩下
P_i=Q_i\neq i 的情况。此时(x,y)=(1,0) 或者(x,y)=(0,1) 都是可以产生贡献的(x,y) 组。费用表中有c_{0,1}=c_{1,0}=1 ,因此直接连边x\to y 和y\to x ,两条边的容量都是1 即可。
| 跑一遍 dinic 然后用 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
namespace Flow {
int S, T, dep[N], cur[N];
struct edg {
int e, f, o;
edg(int a, int b) { e = a, f = b, o = 0; }
} ; vector<edg> adj[N];
inline void link(int s, int e, int f) {
adj[s].push_back({edg(e, f)}), adj[e].push_back(edg(s, 0));
int s1 = adj[s].size() - 1, s2 = adj[e].size() - 1;
adj[s][s1].o = s2, adj[e][s2].o = s1;
}
inline bool bfs() {
queue<int> q;
memset(dep, 0, sizeof dep), memset(cur, 0, sizeof cur);
dep[S] = 1, q.emplace(S);
while (q.size()) {
int p = q.front(); q.pop();
for (auto &[r, f, _] : adj[p]) if (f && !dep[r]) {
dep[r] = dep[p] + 1, q.emplace(r);
if (r == T) return 1;
}
} return 0;
}
inline int dfs(int p, int f) {
if (p == T) return f;
int s = 0;
for (int &i = cur[p]; i < adj[p].size(); ++i) {
if (!f) break;
int q = adj[p][i].e, fx = adj[p][i].f;
if (fx && dep[p] + 1 == dep[q]) {
int delta = dfs(q, min(f, fx));
if (!delta) continue;
adj[p][i].f -= delta, adj[q][adj[p][i].o].f += delta;
f -= delta, s += delta;
}
}
return s ? s : (dep[p] = 0);
}
inline int dinic() {
int s = 0;
while (bfs()) s += dfs(S, inf);
return s;
}
} // namespace Flow
int a[N], b[N], p[N], q[N], pid[N], qid[N], idx;
inline void sol([[maybe_unused]]int __testcase_id) {
int n; cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i], ++a[i];
for (int i = 1; i <= n; ++i) cin >> b[i], ++b[i];
int res = n;
for (int i = 1; i <= n; ++i) {
if (!p[i]) {
p[i] = i;
for (int j = a[i]; j != i; j = a[j]) p[j] = i;
if (a[i] != i) pid[i] = ++idx;
} if (!q[i]) {
q[i] = i;
for (int j = b[i]; j != i; j = b[j]) q[j] = i;
if (b[i] != i) qid[i] = ++idx;
}
} Flow::S = N - 1, Flow::T = N - 2;
for (int i = 1; i <= n; ++i) {
if (a[i] == b[i] && a[i] == i) --res;
else if (a[i] == i && b[i] != i) Flow::link(Flow::S, qid[q[i]], 1);
else if (a[i] != i && b[i] == i) Flow::link(pid[p[i]], Flow::T, 1);
else if (a[i] == b[i]) Flow::link(pid[p[i]], qid[q[i]], 1), Flow::link(qid[q[i]], pid[p[i]], 1);
else Flow::link(pid[p[i]], qid[q[i]], 1);
} // cerr << "!!!\n";
cout << res - Flow::dinic() << '\n';
}
} // namespace lowspeed_song
:::
难度:黑-
鲜活指数:8
139. P11613 [PA 2016] 覆盖 / Pokrycia
:::info[Description] Link. :::
:::info[Solution]{open} 是人类能做的题吗,,,感谢 gpt 姐姐帮我理解这个题。
题目就是统计有多少张
有经典结论:任意
考虑从
先考虑原图中的
因此此时只需要考虑
- 若
u,v 两个点之间有边则她们不能同时出现在一个独立集中,又因为她们对外完全相同所以选择u,v 两个点完全没有区别,可以直接删除其中一个点。此时原来两个权值为1 的点就变成了一个权值为1 的点。 - 若
u,v 两个点之间没有边,则因为其对外邻接情况完全相同,所以如果一个最大独立集选择了u 点但是没选择v 点,则v 点和u 点的邻居必然完全一样,但是v 点却和u 点没有边,所以一定可以把v 点加入到最大独立集中。因此这类情况的u,v 在最大独立集 中要么都被选要么都不被选。因此此时u,v 两个点可以被合并为一个大点,这个大点的权值被设为2 。
然后考虑继续仿照上述操作进行合并所有的大点并继续配对得到权值更大的大点,以此类推直到无法合并当前最高级的大点为止。显然处理完后所有的大点的权值一定都是
用一个二进制编码来表示最终状态,该编码可以看作是将图中最后剩下的所有点的权值相加得到的值。容易证明一个二进制编码对应唯一一组大点权值的集合。
考虑 dp。设
因此 dp 转移式形如:
初始状态显然就是
然后考虑固定最终的大点组成的集合后,她们之间互相连边可以产生怎样的最大独立及大小。因为所有大点的权值都是互不相同的
先考虑如果所有大点都被选中到最大独立集中的情况,此时所有大点之间都必须没有任何边,对答案的贡献就是
然后再考虑只有最小权值的大点没有被选中到最大独立集中的情况。此时除了这个大点以外其余点都被选择,因此其余大点之间都不能有边相连。而最小值的大点想要不被选择就必须和至少一个其余被选中的点之间有边相连。如果共有
然后考虑如果还有其他大点没有被选中到最大独立集中的情况。则此时她在贪心的过程中必然和某个权值更大的大点有边相连。此时翻转她和和她相连的最小的超级点的边不会改变贪心得到的最大独立集,因此这些图可以再次两两配对,模
因此对一个状态
然后第二个贡献
| 预处理出 |
|---|
:::success[Code]
namespace lowspeed_song {
bitset<(1 << 14)> f[1 << 14], g[1 << 14];
inline void init() {
f[0][0] = 1;
for (int i = 1; i < (1 << 14); ++i)
for (int j = 1; j <= i; ++j) {
f[i][j] = f[i - 1][j - 1] ^ f[i - 1][j + (j &- j) - 1];
if (f[i][j]) {
g[i][j].flip();
if (j != (j &- j)) g[i][j - (j &- j)].flip();
}
}
}
inline void sol([[maybe_unused]]int __testcase_id) {
int n, k; cin >> n >> k;
cout << g[n][n - k] << '\n';
}
} // namespace lowspeed_song
:::
难度:彩-
鲜活指数:10
140. AT_agc036_f [AGC036F] Square Constraints
:::info[Description] Link. :::
:::info[Solution]{open}
题目中给的东西其实就是要求排列对应的坐标点落在大圆和小圆之间求排列的数量。记
考虑对内圆
考虑算
考虑进一步优化时间复杂度。容易想到 dp,套路的把所有的
然后考虑如果当前遇到了一个
如果选
如果不选
| 扫描完之后 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int F[N], nF[N];
inline void sol([[maybe_unused]]int __testcase_id) {
int n, mod; cin >> n >> mod;
vector<pair<int, int>> a;
for (int i = 0; i < n; ++i) {
int l = 0, r = 2 * n - 1;
while ((l + 1) * (l + 1) + i * i < n * n) ++l;
while (r * r + i * i > 4 * n * n) --r;
a.emplace_back(l, r);
}
for (int i = n; i < 2 * n; ++i) {
int r = 2 * n - 1;
while (r * r + i * i > 4 * n * n) --r;
a.emplace_back(r, 0);
}
sort(a.begin(), a.end());
function<int(int)> calc = [&](int k) {
fill(F, F + n + 1, 0), F[0] = 1;
int c1 = 0, c2 = 0;
for (auto &[l, r] : a) {
fill(nF, nF + n + 1, 0);
if (!r) {
for (int j = 0; j <= c2; ++j) nF[j] = F[j] * (l - c1 - j + 1) % mod;
++c1;
} else {
for (int j = 0; j <= c2; ++j) nF[j + 1] = (nF[j + 1] + F[j] * (l - c1 - j + 1) % mod) % mod, nF[j] = (nF[j] + F[j] * (r - (n + c2 - j + k) + 1) % mod) % mod;
++c2;
} copy(nF, nF + n + 1, F);
} return F[k];
};
int res = 0;
for (int k = 0; k <= n; ++k)
if (k & 1) res = (res - calc(k) + mod) % mod;
else res = (res + calc(k)) % mod;
cout << res << '\n';
}
} // namespace lowspeed_song
:::
难度:黑+
鲜活指数:9
141. P9682 Electro Master
:::info[Description] Link. :::
:::info[Solution]{open}
先考虑如果序列中不存在 ?,怎么计数序列的权值。先观察一下题目中给的操作的性质:
- 任意时刻序列中向左 / 向右移动的粒子数量不变。
- 所有粒子的相对位置在任何时刻都是不变的。
- 最终向左移动的第
i 个粒子和i-1 个粒子的碰撞次数为前i-1 个粒子中负电荷粒子的数量。
考虑判断第 B。容易发现这个条件等价于其经历的有效碰撞次数为奇数。设前
然后考虑加入 ? 的情况。为了方便处理,这里认为
记 ? 至少还需要有 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
char s[N];
int C[2010][2010], suf[N], suf2[N], f[2010][2][2][2], sc[2010][2010], g[2010][2][2][2];
inline int calc(int x, int y) {
if (y <= 0) return sc[x][0];
if (y > x) return 0;
return sc[x][y];
}
inline void add(int &x, int v) { x = (x + v) % mod; }
inline int chk(char c, int x) { return c == '?' || c == '+' && x || c == '-' && !x; }
inline void sol([[maybe_unused]]int __testcase_id) {
scanf("%s", s + 1);
int n = strlen(s + 1);
s[0] = '+', s[n + 1] = '-';
C[0][0] = 1;
for (int i = 1; i < 2010; ++i) {
C[i][0] = 1;
for (int j = 1; j <= i; ++j) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
} for (int i = 0; i < 2010; ++i)
for (int j = i; ~j; --j) sc[i][j] = (sc[i][j + 1] + C[i][j]) % mod;
for (int i = n; i; --i) suf[i] = suf[i + 1] + (int)(s[i] == '?'), suf2[i] = suf2[i + 1] + (int)(s[i] == '+');
for (int y : {0, 1}) for (int z : {0, 1}) if (chk(s[1], y) && chk(s[2], z)) f[1][1][y][z] = 1;
int res = 0;
for (int i = 1; i <= n; i++) {
// 统计第 i 个粒子的贡献
for (int j = 1; j <= i; ++j)
for (int x : {0, 1}) for (int y : {0, 1}) for (int z : {0, 1})
if (((int)(i - j) * (x != y) + (int)(i - j + !y) * (y != z)) & 1) add(res, f[j][x][y][z] * calc(suf[i + 2], i + 1 - j - y - z - suf2[i + 2]));
if (i == n) break;
memset(g, 0, sizeof g);
for (int j = 1; j <= i; ++j) for (int x : {0, 1}) for (int y : {0, 1}) for (int z : {0, 1}) for (int w : {0, 1})
if (chk(s[i + 2], w)) add(g[j + y][y][z][w], f[j][x][y][z]);
memcpy(f, g, sizeof f);
}
cout << res << '\n';
}
} // namespace lowspeed_song
:::
难度:紫
鲜活指数:7
142. CF1842G Tenzing and Random Operations
:::info[Description] Link. :::
:::info[Solution]{open}
直接推是可以做的但是比较无聊,因此考虑编一个组合意义。可以看作是一个人从
考虑直接 dp。设
- 不使用工具,
f_{i+1,j}\leftarrow f_{i,j}a_{i+1} - 使用工具,需要进一步分类讨论:
- 用一个以前用过的工具,
f_{i+1,j}\leftarrow f_{i,j}jv 。 - 新开一个工具,
f_{i+1,j+1}\leftarrow f_{i,j}(i+1)(m-j)v ,其中i+1 是该工具出现的位置,m-j 是可以选择的方案数。
- 用一个以前用过的工具,
| 答案是容易算的,枚举用的工具数量然后直接统计一下就好了。时间复杂度为 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int a[N], f[5010][5010];
inline void sol([[maybe_unused]]int __testcase_id) {
int n, m, v; cin >> n >> m >> v;
for (int i = 1; i <= n; ++i) cin >> a[i];
f[0][0] = 1;
for (int i = 0; i < n; ++i)
for (int j = 0; j <= min(m, i); ++j) {
f[i + 1][j + 1] = (f[i + 1][j + 1] + f[i][j] * (i + 1) % mod * v % mod * (m - j) % mod) % mod;
f[i + 1][j] = (f[i + 1][j] + f[i][j] * a[i + 1] % mod + f[i][j] * v % mod * j % mod) % mod;
}
int res = 0;
const int inv = inversion(n);
for (int i = 0; i <= min(n, m); ++i) res = (res + f[n][i] * power(inv, i, mod) % mod) % mod;
cout << res << '\n';
}
} // namespace lowspeed_song
:::
难度:紫
鲜活指数:9
143. CF1264E Beautiful League
:::info[Description] Link. :::
:::info[Solution]{open} 为啥有黑来着。
有经典结论:竞赛图三元环数量为
后面部分十分套路,考虑费用流建模。如果
则此时若
| 记第 |
|---|
:::success[Code]
namespace lowspeed_song {
vector<int> adj[N];
int deg[N], win[N], mt[55][55];
char res[55][55];
namespace MCMF {
int S, T, hhh[N], e[N], f[N], ne[N], w[N], idxx, q[N], d[N], pre[N], incf[N], st[N];
inline void ae(int a, int b, int c, int d) {
e[idxx] = b, w[idxx] = d, f[idxx] = c, ne[idxx] = hhh[a], hhh[a] = idxx++;
e[idxx] = a, w[idxx] = -d, f[idxx] = 0, ne[idxx] = hhh[b], hhh[b] = idxx++;
}
inline int spfa() {
int hh = 0, tt = 1;
memset(d, 0x3f, sizeof d);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = inf;
while (hh != tt) {
int t = q[hh++];
if (hh == N) hh = 0;
st[t] = 0;
for (int i = hhh[t]; ~i; i = ne[i]) {
int v = e[i];
if (f[i] && d[v] > d[t] + w[i]) {
d[v] = d[t] + w[i];
pre[v] = i, incf[v] = min(incf[t], f[i]);
if (!st[v]) {
q[tt++] = v;
if (tt == N) tt = 0;
st[v] = 1;
}
}
}
}
return incf[T] > 0;
}
inline void ek(int &F, int &C) {
F = C = 0;
while (spfa()) {
int tt = incf[T];
F += tt, C += tt * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1]) f[pre[i]] -= tt, f[pre[i] ^ 1] += tt;
}
}
}
inline void init() {
}
inline void sol([[maybe_unused]]int __testcase_id) {
int n, m; cin >> n >> m;
vector<pair<int, int>> unk;
memset(MCMF::hhh, -1, sizeof MCMF::hhh);
for (int i = 0; i < n; ++i) res[i][i] = '0';
while (m--) {
int u, v; cin >> u >> v; --u, --v;
adj[u].emplace_back(v), adj[v].emplace_back(u);
res[u][v] = '1', res[v][u] = '0', mt[u][v] = mt[v][u] = 1, ++win[u];
} for (int i = 0; i < n; ++i) for (int j = i + 1; j < n; ++j) if (!mt[i][j])
++deg[i], ++deg[j], unk.emplace_back(i, j);
MCMF::S = N - 1, MCMF::T = N - 2;
vector<tuple<int, int, int>> match;
for (int i = 0; i < unk.size(); ++i) {
auto [u, v] = unk[i];
MCMF::ae(MCMF::S, n + i + 1, 1, 0);
MCMF::ae(n + i + 1, u, 1, 0);
MCMF::ae(n + i + 1, v, 1, 0);
match.emplace_back(u, v, MCMF::idxx - 4);
}
for (int i = 0; i < n; ++i)
for (int j = 0; j < deg[i]; ++j) MCMF::ae(i, MCMF::T, 1, win[i] + j);
int F, C; MCMF::ek(F, C);
for (auto &[u, v, eu] : match) {
if (!MCMF::f[eu]) res[u][v] = '1', res[v][u] = '0';
else res[v][u] = '1', res[u][v] = '0';
} for (int i = 0; i < n; ++i, cout << '\n')
for (int j = 0; j < n; ++j) cout << res[i][j];
}
} // namespace lowspeed_song
:::
难度:紫
鲜活指数:8
144. AT_agc056_c [AGC056C] 01 Balanced
:::info[Description] Link. :::
:::info[Solution]{open}
记
-
- 对一组限制条件
(l,r) 而言必须满足d_r=d_{l-1} 。
因此考虑差分约束建模。对一组限制条件
然后对
然后从
| 直接跑 01-bfs 可以做到线性时间复杂度求解。 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int dis[N];
vector<pair<int, int>> adj[N];
inline void sol([[maybe_unused]]int __testcase_id) {
int n, m; cin >> n >> m;
for (int i = 1; i <= m; ++i) {
int l, r; cin >> l >> r;
adj[l - 1].emplace_back(r, 0);
adj[r].emplace_back(l - 1, 0);
} for (int i = 1; i <= n; ++i) adj[i].emplace_back(i - 1, 1), adj[i - 1].emplace_back(i, 1);
deque<int> q;
memset(dis, -0x3f, sizeof dis);
dis[0] = 0, q.emplace_back(0);
while (q.size()) {
int t = q.front(); q.pop_front();
for (auto &[g, w] : adj[t]) if (dis[g] < -inf / 2) {
dis[g] = dis[t] + w;
if (!w) q.emplace_front(g);
else q.emplace_back(g);
}
}
for (int i = 1; i <= n; ++i)
if (dis[i - 1] > dis[i]) cout << 1;
else cout << 0;
cout << '\n';
}
} // namespace lowspeed_song
:::
难度:紫
鲜活指数:8
145. P14019 [ICPC 2024 Nanjing R] 地铁
:::info[Description] Link :::
:::info[Solution]{open}
考虑用一个二元组
注意到导致边的数量爆炸的原因主要是换乘,因此考虑优化这个部分。在某个车站从线路
然后注意到
| 具体来说,跑 dijkstra 的过程中若当前弹出的状态是 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
vector<int> rail[N];
int n, k, a[N], b[N], frt[N], bel[N], e[N], pos[N], idx_, w[N], dis[N], res[N], vis[N];
namespace LiChaoSgt {
vector<int> tree[N];
pair<int, int> seg[N];
inline int calc(int id, int u, int x) {
if (!id) return inf;
return seg[id].first * a[bel[rail[u][x]]] + seg[id].second;
}
inline void ins(int u, int l, int r, int rt, int id) {
if (l == r) {
if (!tree[u][rt] || calc(id, u, l) < calc(tree[u][rt], u, l)) tree[u][rt] = id;
return;
} int mid = l + r >> 1;
if (calc(id, u, mid) < calc(tree[u][rt], u, mid)) swap(tree[u][rt], id);
if (calc(id, u, l) < calc(tree[u][rt], u, l)) ins(u, l, mid, rt << 1, id);
if (calc(id, u, r) < calc(tree[u][rt], u, r)) ins(u, mid + 1, r, rt << 1 | 1, id);
}
inline void ins(int u, int k, int b, int id) { seg[id] = {k, b}, ins(u, 0, (int)rail[u].size() - 1, 1, id); }
inline int qry(int u, int l, int r, int rt, int val) {
if (l == r) return calc(tree[u][rt], u, val);
int mid = l + r >> 1, now = calc(tree[u][rt], u, val);
if (val <= mid) return min(now, qry(u, l, mid, rt << 1, val));
return min(now, qry(u, mid + 1, r, rt << 1 | 1, val));
}
inline int qry(int u, int pos) { return qry(u, 0, (int)rail[u].size() - 1, 1, pos); }
}
inline void sol([[maybe_unused]]int __testcase_id) {
cin >> n >> k;
for (int i = 1; i <= k; ++i) cin >> a[i];
for (int i = 1; i <= k; ++i) cin >> b[i];
for (int i = 1; i <= k; ++i) {
int p, x, las = ++idx_;
cin >> p >> x;
frt[las] = x, bel[las] = i, rail[x].emplace_back(las);
for (int j = 1; j < p; ++j) {
int c, now = ++idx_; cin >> c >> x;
frt[now] = x, bel[now] = i, rail[x].emplace_back(now);
e[las] = now, w[las] = c, las = now;
}
}
for (int i = 1; i <= n; ++i) sort(rail[i].begin(), rail[i].end(), [&](auto &x, auto &y) { return a[bel[x]] < a[bel[y]]; });
for (int i = 1; i <= n; ++i) LiChaoSgt::tree[i].resize(rail[i].size() * 4 + 5);
memset(dis, 0x3f, sizeof dis), memset(res, 0x3f, sizeof res);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
for (int &i : rail[1]) dis[i] = 0, q.emplace(0, i);
// cerr << "!!!\n";
while (q.size()) {
auto [D, id] = q.top();
q.pop();
if (vis[id] || D != dis[id]) continue;
vis[id] = 1;
int u = frt[id], c = bel[id];
res[u] = min(res[u], D), LiChaoSgt::ins(u, b[c], D, id);
if (e[id] && dis[e[id]] > D + w[id]) dis[e[id]] = D + w[id], q.emplace(dis[e[id]], e[id]);
while (pos[u] < rail[u].size() && vis[rail[u][pos[u]]]) ++pos[u];
if (pos[u] < rail[u].size()) {
int nd = LiChaoSgt::qry(u, pos[u]);
if (dis[rail[u][pos[u]]] > nd) dis[rail[u][pos[u]]] = nd, q.emplace(nd, rail[u][pos[u]]);
}
}
for (int i = 2; i <= n; ++i) cout << res[i] << ' ';
cout << '\n';
}
} // namespace lowspeed_song
:::
难度:紫+
鲜活指数:7
146. P10573 [JRKSJ R8] C0mp0nents
:::info[Description] Link. :::
:::info[Solution]{open}
很显然
容易证明固定一个
-
- 每个
x\in (l,s) 都有边连向更小的位置。 - 每个
x\in(s,r) 都有边连向更大的位置。
因此考虑处理出
此时可以知道对一个起始位置
| 细节实现的好的话可以做到线性时间复杂度。代码没有太多细节。 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
vector<int> adj[N];
int n, m, k, res[N], a[N], diff[N], e[N], L[N], R[N], LL[N], RR[N], pre[N], suf[N];
inline void gao(int st) {
int idx = 0;
for (int i = st; i <= n; i += k) a[++idx] = i, e[i] = idx;
for (int i = 0; i <= idx + 1; ++i) diff[i] = 0;
for (int i = 1; i <= idx; ++i) {
L[i] = 0, R[i] = idx + 1;
for (int &v : adj[a[i]])
if (e[v] < i) L[i] = max(L[i], e[v]);
else R[i] = min(R[i], e[v]);
}
LL[1] = 1;
for (int i = 2; i <= idx; ++i) {
LL[i] = LL[i - 1];
if (L[i - 1] < LL[i]) LL[i] = i - 1;
}
RR[idx] = idx;
for (int i = idx - 1; i; --i) {
RR[i] = RR[i + 1];
if (R[i + 1] > RR[i]) RR[i] = i + 1;
}
pre[idx + 1] = idx + 1;
for (int i = idx; i; --i) {
pre[i] = pre[i + 1];
while (pre[i] > 1 && RR[pre[i] - 1] >= i) --pre[i];
}
suf[0] = 0;
for (int i = 1; i <= idx; ++i) {
suf[i] = suf[i - 1];
while (suf[i] < idx && LL[suf[i] + 1] <= i) ++suf[i];
}
for (int i = 1; i <= idx; ++i)
for (int &v : adj[a[i]]) if (i < e[v]) {
int l = max(i + 1, pre[e[v]]), r = min(e[v] - 1, suf[i]);
if (l <= r) ++diff[l], --diff[r + 1];
}
for (int i = 1; i <= idx; ++i) {
diff[i] += diff[i - 1];
if (L[i] < LL[i] && R[i] > RR[i])
LL[i] = RR[i] = i;
else if (!diff[i]) {
if (L[i] < LL[i]) LL[i] = i;
else if (R[i] > RR[i]) RR[i] = i;
}
res[a[i]] = RR[i] - LL[i] + 1;
}
}
inline void sol([[maybe_unused]]int __testcase_id) {
cin >> n >> m >> k;
while (m--) {
int a, b; cin >> a >> b;
if (a % k == b % k) adj[a].emplace_back(b), adj[b].emplace_back(a);
} for (int i = 1; i <= k; ++i) gao(i);
for (int i = 1; i <= n; ++i) cout << res[i] << ' '; cout << '\n';
}
} // namespace lowspeed_song
:::
难度:紫+
鲜活指数:7
147. P14383 [JOISC 2017] 港口设施 / Port Facility
:::info[Description] Link. :::
:::info[Solution]{open}
观察样例可得答案要么为
考虑若存在两个箱子
| 直接做时间复杂度是 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
pair<int, int> a[N];
struct DSU {
int fa[N];
inline DSU() { iota(fa, fa + N, 0); }
inline void init(int maxn) { iota(fa, fa + maxn, 0); }
inline int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
inline int merge(int x, int y) {
x = find(x), y = find(y);
if (x != y) return fa[x] = y, 1;
return 0;
}
} dsu;
int ne[N], n;
inline int chk(int i, int j) {
if (dsu.find(i) == dsu.find(j)) return 0;
return dsu.merge(i, j + n), dsu.merge(j, i + n), 1;
}
inline void sol([[maybe_unused]]int __testcase_id) {
cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i].first >> a[i].second;
sort(a + 1, a + n + 1);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
for (int i = 1; i <= n; ++i) {
auto [l, r] = a[i];
while (q.size() && q.top().first < l) {
int x = q.top().second; q.pop();
if (ne[x]) q.emplace(a[ne[x]].second, ne[x]);
} vector<int> v;
while (q.size() && q.top().first < r) v.emplace_back(q.top().second), q.pop();
for (int &j : v) if (!chk(i, j)) { cout << 0 << '\n'; return; }
for (int j = 0; j + 1 < v.size(); ++j) ne[v[j]] = v[j + 1];
if (v.size()) q.emplace(a[v[0]].second, v[0]);
q.emplace(r, i);
} int cnt = 0;
for (int i = 1; i <= n + n; ++i) cnt += (int)(dsu.find(i) == i);
cout << power(2, cnt >> 1, mod) << '\n';
}
} // namespace lowspeed_song
:::
难度:紫-
鲜活指数:6
148. CF1299D Around the World
:::info[Description] Link. :::
:::info[Solution]{open}
這也能做嗎.jpg
套路的考慮先求出圖的任意一個 DFS 生成樹,然後結合綫性基來維護答案。具體的,任意求出圖的一個 DFS 樹后記
因此題目中的限制條件等價於:所有當前和點
然後把點
因此:刪除點
如果塊内部本身合法就考慮她和
- 如果刪除了這條邊那麽這個塊和
1 號節點斷開,什麽都不用加入全局綫性基。 - 如果保留這條邊,那麽點
1 可以進入這個塊,因此把塊内綫性基B 合并到全局綫性基中即可。
如果有兩條邊設她們分別是
- 兩條邊都刪除:什麽也不用加入。
- 只保留一條邊:點
1 可以進入這個塊,只需要加入綫性基B 即可。注意有兩種保留一條邊的方法。 - 保留兩條邊:除了
B 以外還需要加入三元環的貢獻即 XORa\oplus b\oplus c 這個環。此時如果a\oplus b\oplus c 插入不了B 這個綫性記則兩條邊都保留這個操作本身就是非法的。
現在每個連通塊都被壓縮成了兩三個網全局綫性基中加入哪些數的選擇。問題只剩下如何把不同的連通塊合起來處理了。此時顯然不能只保證每個塊内部合法,因爲不同的塊之間也可以凑出 XOR 為
記
- 對只有一個便連向
1 的塊,轉移要麽仍然為S 要麽從S 變爲merge(S,B) 。 - 對於有兩條邊的塊,轉移方式有下面的三種:
-
需要處理一下綫性基無法插入也就是轉移非法的情況。
處理完所有連通塊后,答案可以被表示爲
| 正確性顯然是對的,時間複雜度懶得分析了,反正能過。 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
struct Basis {
int b[5];
inline Basis() { memset(b, 0, sizeof b); }
inline int ins(int x) {
for (int i = 4; ~i; --i) if (x >> i & 1) {
if (b[i]) x ^= b[i];
else return b[i] = x, 1;
} return 0;
}
inline int getmask() const {
int s = 1;
for (int i = 0; i < 5; ++i) if (b[i]) {
int t = s;
for (int j = 0; j < 32; ++j)
if (s >> j & 1) t |= 1 << (j ^ b[i]);
s = t;
} return s;
}
};
vector<pair<int, int>> adj[N];
int w_[N], vis[N], dep[N], xr[N], f[N], g[N];
int trans[710][710], idx;
Basis state[N];
inline void sol([[maybe_unused]]int __testcase_id) {
idx = 1;
map<int, int> mp; mp[1] = 0;
for (int i = 0; i < idx; ++i)
for (int j = 1; j < 32; ++j) {
Basis t = state[i];
if (!t.ins(j)) continue;
int s = t.getmask();
if (!mp.count(s)) mp[s] = idx, state[idx++] = t;
}
memset(trans, -1, sizeof trans);
for (int i = 0; i < idx; ++i)
for (int j = 0; j < idx; ++j) {
Basis t = state[i];
int ok = 1;
for (int k = 0; k < 5; ++k)
if (state[j].b[k] && !t.ins(state[j].b[k])) ok = 0;
if (ok) trans[i][j] = mp[t.getmask()];
}
int n, m; cin >> n >> m;
memset(w_, -1, sizeof w_);
while (m--) {
int u, v, w; cin >> u >> v >> w;
adj[u].emplace_back(v, w), adj[v].emplace_back(u, w);
if (u == 1) w_[v] = w;
if (v == 1) w_[u] = w;
} f[0] = 1;
for (int i = 2; i <= n; ++i) if (!vis[i]) {
Basis basis;
int ok = 0;
vector<pair<int, int>> vec;
function<void(int, int)> dfs = [&](int u, int fa) {
vis[u] = 1, dep[u] = dep[fa] + 1;
if (~w_[u]) vec.emplace_back(u, w_[u]);
for (auto &[v, w] : adj[u]) if (v != fa && v != 1) {
if (!vis[v]) xr[v] = xr[u] ^ w, dfs(v, u);
else if (dep[v] < dep[u] && !basis.ins(xr[u] ^ xr[v] ^ w)) ok = 1;
}
};
dfs(i, 0);
vector<pair<int, int>> vec_ = {{0, 1}};
if (!ok) {
int b = mp[basis.getmask()];
if (vec.size() == 1) vec_.emplace_back(b, 1);
else if (vec.size() == 2) {
vec_.emplace_back(b, 2);
int u = vec[0].first, v = vec[1].first, w = 0;
for (auto &[vv, c] : adj[u]) if (vv == v) { w = c; break; }
Basis T = basis;
if (T.ins(vec[0].second ^ vec[1].second ^ w)) vec_.emplace_back(mp[T.getmask()], 1);
}
}
for (int i = 0; i < idx; ++i) g[i] = 0;
for (int i = 0; i < idx; ++i) for (auto &[j, k] : vec_)
if (~trans[i][j]) g[trans[i][j]] = (g[trans[i][j]] + f[i] * k) % mod;
for (int i = 0; i < idx; ++i) f[i] = g[i];
}
int res = 0;
for (int &i : f) res = (res + i) % mod;
cout << res << '\n';
}
} // namespace lowspeed_song
:::
难度;黑
鲜活指数:8
149. AT_agc029_f [AGC029F] Construction of a tree
:::info[Description] Link. :::
:::info[Solution]{open}
考虑二分图建模。固定
先在二分图上求一个完美匹配,由 Hall 定理可知若不存在完美匹配则必然无解,否则设集合
从
如果 BFS 结束后仍然有结点没有被访问到,则说明已经无法通过任何集合向外继续扩展,因此此时不存在合法方案,直接输出
| 因为这个题数据范围太大了所以没法写匈牙利,只能被迫写 dinic 了。 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
namespace Flow {
int S, T, dep[N], cur[N];
struct edg {
int e, f, o;
edg(int a, int b) { e = a, f = b, o = 0; }
} ; vector<edg> adj[N];
inline void link(int s, int e, int f) {
adj[s].push_back({edg(e, f)}), adj[e].push_back(edg(s, 0));
int s1 = adj[s].size() - 1, s2 = adj[e].size() - 1;
adj[s][s1].o = s2, adj[e][s2].o = s1;
}
inline bool bfs() {
queue<int> q;
memset(dep, 0, sizeof dep), memset(cur, 0, sizeof cur);
dep[S] = 1, q.emplace(S);
while (q.size()) {
int p = q.front(); q.pop();
for (auto &[r, f, _] : adj[p]) if (f && !dep[r]) {
dep[r] = dep[p] + 1, q.emplace(r);
if (r == T) return 1;
}
} return 0;
}
inline int dfs(int p, int f) {
if (p == T) return f;
int s = 0;
for (int &i = cur[p]; i < adj[p].size(); ++i) {
if (!f) break;
int q = adj[p][i].e, fx = adj[p][i].f;
if (fx && dep[p] + 1 == dep[q]) {
int delta = dfs(q, min(f, fx));
if (!delta) continue;
adj[p][i].f -= delta, adj[q][adj[p][i].o].f += delta;
f -= delta, s += delta;
}
}
return s ? s : (dep[p] = 0);
}
inline int dinic() {
int s = 0;
while (bfs()) s += dfs(S, inf);
return s;
}
} // namespace Flow
vector<int> S[N];
pair<int, int> res[N];
int F[N], vis[N], fa[N], id[N];
inline void sol([[maybe_unused]]int __testcase_id) {
int n; cin >> n;
Flow::S = 1, Flow::T = n + n;
for (int i = 2; i <= n; ++i) Flow::link(Flow::S, i, 1);
for (int i = 1; i < n; ++i) {
int c; cin >> c;
while (c--) {
int w; cin >> w;
S[w].emplace_back(i);
if (w != 1) Flow::link(w, n + i, 1);
} Flow::link(n + i, Flow::T, 1);
} if (Flow::dinic() != n - 1) { cout << -1 << '\n'; return; }
for (int i = 1; i <= n; ++i)
for (auto &[e, f, o] : Flow::adj[n + i])
if (2 <= e && e <= n && f) { F[i] = e; break; }
queue<int> q; q.emplace(1);
while (q.size()) {
int t = q.front(); q.pop();
for (int &i : S[t]) if (!vis[i])
vis[i] = 1, fa[F[i]] = t, id[F[i]] = i, q.emplace(F[i]);
} for (int i = 1; i < n; ++i) if (!vis[i]) { cout << -1 << '\n'; return; }
for (int i = 2; i <= n; ++i) res[id[i]] = {fa[i], i};
for (int i = 1; i < n; ++i) cout << res[i].first << ' ' << res[i].second << '\n';
}
} // namespace lowspeed_song
:::
难度:黑
鲜活指数:9
150. P13583 [NWRRC 2023] Colorful Village
:::info[Description] Link :::
:::info[Solution]{open}
考虑到颜色为
一个特殊的地方是根结点必须选,所以需要从
| 直接做时间复杂度为 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
stack<int> stk;
vector<int> adj[N], col[N], adj_[N];
int n, a[N], dfn[N], low[N], bel[N], idx, tot, ins[N];
inline void dfs(int u) {
dfn[u] = low[u] = ++idx;
ins[u] = 1, stk.emplace(u);
for (int &v : adj_[u]) {
if (!dfn[v]) dfs(v), low[u] = min(low[u], low[v]);
else if (ins[v]) low[u] = min(low[u], dfn[v]);
} if (dfn[u] == low[u]) { ++tot;
while (stk.top() != u) {
int t = stk.top(); stk.pop();
ins[t] = 0, bel[t] = tot;
} int t = stk.top(); stk.pop();
ins[t] = 0, bel[t] = tot;
}
}
inline void dfs(int u, int fa) {
if (fa) adj_[u].emplace_back(fa), adj_[fa + (n << 1)].emplace_back(u + (n << 1));
for (int &v : adj[u]) if (v != fa) dfs(v, u);
}
inline void sol([[maybe_unused]]int __testcase_id) {
cin >> n;
for (int i = 1; i <= n + n; ++i) adj[i].clear(), adj_[i].clear(), col[i].clear();
for (int i = 1; i <= n + n; ++i) cin >> a[i], col[a[i]].emplace_back(i);
for (int i = 1; i < n + n; ++i) {
int a, b; cin >> a >> b;
adj[a].emplace_back(b), adj[b].emplace_back(a);
}
function<bool(int)> gao = [&](int rt) {
idx = tot = 0;
for (int i = 1; i <= n + n + n + n; ++i) dfn[i] = low[i] = 0, adj_[i].clear();
adj_[rt + (n << 1)].emplace_back(rt);
for (int i = 1; i <= n; ++i) {
adj_[col[i][0]].emplace_back(col[i][1] + (n << 1));
adj_[col[i][1]].emplace_back(col[i][0] + (n << 1));
adj_[col[i][0] + (n << 1)].emplace_back(col[i][1]);
adj_[col[i][1] + (n << 1)].emplace_back(col[i][0]);
} dfs(rt, 0);
for (int i = 1; i <= n + n; ++i)
if (!dfn[i]) dfs(i);
for (int i = 1; i <= n + n; ++i)
if (bel[i] == bel[i + n + n]) return 0;
return 1;
};
if (gao(col[1][0])) {
for (int i = 1; i <= n + n; ++i)
if (bel[i] < bel[i + n + n]) cout << i << ' ';
cout << '\n';
} else if (gao(col[1][1])) {
for (int i = 1; i <= n + n; ++i)
if (bel[i] < bel[i + n + n]) cout << i << ' ';
cout << '\n';
} else cout << -1 << '\n';
}
} // namespace lowspeed_song
:::
难度:紫-
鲜活指数:6
151. P9374 「DROI」Round 2 单图
:::info[Description] Link. :::
:::info[Solution]{open} 容易观察到合法的图结构必然是要么每个点没有入度要么没有出度要么是一个两个点的 SCC。
先不考虑两个点 SCC 这个结构,记有
| 然后考虑枚举两个点 SCC 的数量为 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int C[N][N], f[N], g[N];
inline void sol([[maybe_unused]]int __testcase_id) {
int T, mod; cin >> T >> mod;
C[0][0] = 1;
for (int i = 1; i < N; ++i) {
C[i][0] = 1;
for (int j = 1; j <= i; ++j) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
}
f[0] = 1;
for (int i = 0; i < N; ++i)
for (int j = 0; j < i; ++j)
f[i] = (f[i] + C[i][j] * power(power(2, i - j, mod) - 1, j, mod) % mod) % mod;
g[0] = 1;
for (int i = 1; i < N; ++i) {
g[i] = 1;
for (int j = 1; j <= i; ++j) g[i] = g[i] * (j + j - 1) % mod;
}
while (T--) {
int n; cin >> n;
int res = 0;
for (int i = 0; i <= n / 2; ++i)
res = (res + C[n][i + i] * g[i] % mod * f[n - i - i] % mod) % mod;
cout << res << '\n';
}
}
} // namespace lowspeed_song
:::
难度:蓝
鲜活指数:7
152. CF1268E Happy Cactus
:::info[Description] Link. :::
:::info[Solution]{open}
先考虑图是一个树的情况。此时定义
然后考虑把上述做法扩展到仙人掌上。考虑如果当前一条边
找仙人掌中所有的环是容易的,这里就不展开说了。
| 文字部分描述的可能不太清楚,建议参考代码实现理解。 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int n, m;
pair<int, int> e[N];
vector<pair<int, int>> adj[N];
int f[N], g[N], Min[N], fa[N], fa_[N], dep[N];
inline void gao(int u, int v, int id) {
vector<int> v1 = {id}, v2;
for (int i = u; i != v; i = fa[i]) v2.emplace_back(fa_[i]);
reverse(v2.begin(), v2.end());
for (int &i : v2) v1.emplace_back(i);
int mi = min_element(v1.begin(), v1.end()) - v1.begin();
int mx = max_element(v1.begin(), v1.end()) - v1.begin();
int ok = 1;
for (int i = mi; i != mx; ) {
if (v1[i] > v1[(i + 1) % v1.size()]) ok = 0;
i = (i + 1) % v1.size();
} for (int i = mi; i != mx; ) {
if (v1[i] > v1[(i + v1.size() - 1) % v1.size()]) ok = 0;
i = (i + v1.size() - 1) % v1.size();
} if (ok) Min[v1[mi]] = v1[mx];
}
inline void dfs(int u, int pid) {
for (auto &[v, id] : adj[u]) if (id != pid) {
if (!dep[v]) dep[v] = dep[u] + 1, fa[v] = u, fa_[v] = id, dfs(v, id);
else if (dep[u] > dep[v]) gao(u, v, id);
}
}
inline void sol([[maybe_unused]]int __testcase_id) {
cin >> n >> m;
for (int i = 1; i <= m; ++i) {
int u, v; cin >> u >> v;
e[i] = {u, v}, adj[u].emplace_back(v, i), adj[v].emplace_back(u, i);
} dep[1] = 1, dfs(1, 0);
for (int i = 1; i <= n; ++i) f[i] = 1;
for (int i = m; i; --i) {
auto &[u, v] = e[i];
int val = f[u] + f[v];
if (Min[i]) val -= g[Min[i]];
f[u] = f[v] = g[i] = val;
} for (int i = 1; i <= n; ++i) cout << f[i] - 1 << ' '; cout << '\n';
}
} // namespace lowspeed_song
:::
难度:紫
鲜活指数:7
153. CF1572D Bridge Club
:::info[Description] [Link(https://www.luogu.com.cn/problem/CF1572D)] :::
:::info[Solution]{open}
一个暴力的做法是直接二分图建模,注意到直接把可以组队的点连起来,相邻两个点的 popcount 值差一定为
| 但是边的数量是 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
namespace MCMF {
int S, T, hhh[N], e[N], f[N], ne[N], w[N], idxx, q[N], d[N], pre[N], incf[N], st[N];
inline void ae(int a, int b, int c, int d) {
e[idxx] = b, w[idxx] = d, f[idxx] = c, ne[idxx] = hhh[a], hhh[a] = idxx++;
e[idxx] = a, w[idxx] = -d, f[idxx] = 0, ne[idxx] = hhh[b], hhh[b] = idxx++;
}
inline int spfa() {
int hh = 0, tt = 1;
memset(d, 0x3f, sizeof d);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = inf;
while (hh != tt) {
int t = q[hh++];
if (hh == N) hh = 0;
st[t] = 0;
for (int i = hhh[t]; ~i; i = ne[i]) {
int v = e[i];
if (f[i] && d[v] > d[t] + w[i]) {
d[v] = d[t] + w[i];
pre[v] = i, incf[v] = min(incf[t], f[i]);
if (!st[v]) {
q[tt++] = v;
if (tt == N) tt = 0;
st[v] = 1;
}
}
}
}
return incf[T] > 0;
}
inline void ek(int &F, int &C) {
F = C = 0;
while (spfa()) {
int tt = incf[T];
F += tt, C += tt * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1]) f[pre[i]] -= tt, f[pre[i] ^ 1] += tt;
}
}
inline void ek(int &F, int &C, int k) {
F = C = 0;
while (k-- && spfa()) {
int tt = incf[T];
F += tt, C += tt * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1]) f[pre[i]] -= tt, f[pre[i] ^ 1] += tt;
}
}
}
int a[N], id[N], vis[N];
inline void sol([[maybe_unused]]int __testcase_id) {
int n, k, idx = 0; cin >> n >> k;
for (int i = 0; i < (1 << n); ++i) cin >> a[i];
k = min(k, (1ll << n) / 2);
memset(MCMF::hhh, -1, sizeof MCMF::hhh);
vector<tuple<signed, signed, int>> e;
for (int i = 0; i < (1 << n); ++i) if (!__builtin_parity(i))
for (int j = 0; j < n; ++j) e.emplace_back(a[i] + a[i ^ (1 << j)], i, i ^ (1 << j));
int m = min<int>(e.size(), k * (2 * n - 1) + 1);
sort(e.begin(), e.end(), greater<>()), e.resize(m);
memset(id, -1, sizeof id);
for (auto &[w, u, v] : e) {
if (!~id[u]) id[u] = idx++;
if (!~id[v]) id[v] = idx++;
}
MCMF::S = N - 1, MCMF::T = N - 2;
for (auto &[w, x, y] : e) {
int u = id[x], v = id[y];
MCMF::ae(u, v, 1, -w);
if (!vis[u]) MCMF::ae(MCMF::S, u, 1, 0), vis[u] = 1;
if (!vis[v]) MCMF::ae(v, MCMF::T, 1, 0), vis[v] = 1;
}
int F, C; MCMF::ek(F, C, k);
cout << -C << '\n';
}
} // namespace lowspeed_song
:::
难度:紫
鲜活指数:8
154. P14170 二分图最大匹配期望
:::info[Description] Link. :::
:::info[Solution]{open} 先回顾一下 Hall 定理:
对一张二分图
G 而言,设其左部点集合为V_1 ,右部点集合为V_2 ,则其存在大小为|V_1| 的最大匹配当且仅当\forall S\subseteq V_1\Longrightarrow |S|\le |N(S)| 。
这里给出 Hall 定理的一个推论;
对一张二分图
G ,设其左部点集合为V_1 ,右部点集合为V_2 ,则其最大匹配大小为|V_1|-\max\limits_{S\subseteq V_1}(|S|-|N(S)|) 。
对一个左部点集合
考虑对一个二分图,记这个二分图的好集合为其左部点集合的所有子集中,满足
然后考虑 dp。设
初始条件显然是
为了方便转移,这里记
然后考虑转移
先考虑第一个条件:
总结一下,
然后考虑转移
总结一下,
转移这两个数组的时候直接一起转移即可。
最后考虑怎么统计答案。设整张图的极好集合为
总结一下,这一类二分图度对答案的贡献可以表示为
| 该算法的时间复杂度为 |
|---|
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int p[610][610], tmp[610][610], all[610][610], noth[610][610], cnt[610], f[610][610], g[610][610];
inline void sol([[maybe_unused]]int __testcase_id) {
int n, m; cin >> n >> m;
const int inv100 = inversion(100);
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j) cin >> p[i][j], p[i][j] = p[i][j] * inv100 % mod;
for (int i = 0; i < n; i++) {
tmp[i][0] = 1;
for (int t = 1; t < (1 << m); ++t) {
int j = __builtin_ctz(t);
tmp[i][t] = tmp[i][t ^ (1 << j)] * (1 - p[i][j] + mod) % mod;
}
}
for (int t = 0; t < (1 << m); ++t) noth[0][t] = 1;
for (int s = 1; s < (1 << n); ++s) {
int i = __builtin_ctz(s), pre = s ^ (1 << i);
for (int t = 0; t < (1 << m); ++t) noth[s][t] = noth[pre][t] * tmp[i][t] % mod;
}
for (int s = 0; s < (1 << n); ++s) {
all[s][0] = 1;
for (int t = 1; t < (1 << m); ++t) {
all[s][t] = 1;
for (int S = t; S; S = (S - 1) & t) all[s][t] = (all[s][t] + mod - all[s][t ^ S] * noth[s][S] % mod) % mod;
}
}
for (int s = 0; s < (1 << n); ++s)
for (int t = 0; t < (1 << m); ++t) f[s][t] = g[s][t] = 0;
f[0][0] = g[0][0] = 1;
for (int s = 1; s < (1 << n); ++s) {
for (int t = 0; t < (1 << m); ++t) {
if (__builtin_popcount(s) > __builtin_popcount(t)) {
g[s][t] = all[s][t];
for (int a = (s - 1) & s; ; a = (a - 1) & s) {
for (int b = t; ; b = (b - 1) & t) {
if (__builtin_popcount(a) - __builtin_popcount(b) >= __builtin_popcount(s) - __builtin_popcount(t)) g[s][t] = (g[s][t] + mod - g[a][b] * f[s ^ a][t ^ b] % mod * noth[a][t ^ b] % mod) % mod;
if (!b) break;
} if (!a) break;
}
} else {
f[s][t] = all[s][t];
for (int a = s; a; a = (a - 1) & s)
for (int b = t; ; b = (b - 1) & t) {
if (__builtin_popcount(a) > __builtin_popcount(b)) f[s][t] = (f[s][t] + mod - g[a][b] * f[s ^ a][t ^ b] % mod * noth[a][t ^ b] % mod) % mod;
if (!b) break;
}
}
}
}
int res = 0;
for (int s = 0; s < (1 << n); ++s)
for (int t = 0; t < (1 << m); ++t)
for (int t0 = (((1 << m) - 1) ^ t);; t0 = (t0 - 1) & (((1 << m) - 1) ^ t)) {
res = (res + g[s][t] * noth[s][(((1 << m) - 1) ^ t)] % mod * f[((1 << n) - 1) ^ s][t0] % mod * noth[((1 << n) - 1) ^ s][(((1 << m) - 1) ^ t) ^ t0] % mod * (n - __builtin_popcount(s) + __builtin_popcount(t)) % mod) % mod;
if (!t0) break;
}
cout << res << '\n';
}
} // namespace lowspeed_song
:::