Solution Set【2024.1.15】
A. Giao 徽的烤鸭 / GYM103428H city safety
考虑设 \(f_{u, i}\) 表示选择全部距离 \(u\) 不超过 \(i\) 的情况下的最大收益,有转移:
其中 \(j \ge i - 1\) 是需要满足 \(u\) 的限制,\(j \le i + 1\) 是需要满足 \(v\) 的限制。
复杂度 \(O(n^2)\)。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
valueType N;
ValueVector W, V;
ValueMatrix F, G;
void dfs(valueType x, valueType from) {
F[x][0] = 0;
for (valueType i = 1; i <= N; ++i)
F[x][i] = V[i - 1] - W[x];
for (auto to : G[x]) {
if (to == from)
continue;
dfs(to, x);
F[x][0] += std::max(F[to][0], F[to][1]);
for (valueType i = 1; i <= N; ++i)
F[x][i] += std::max({F[to][i - 1], F[to][i], F[to][i + 1]});
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("duck.in", "r", stdin);
freopen("duck.out", "w", stdout);
#endif
std::cin >> N;
W.resize(N + 1);
V.resize(N + 1);
G.resize(N + 1);
F.resize(N + 1, ValueVector(N + 2, std::numeric_limits<valueType>::min() >> 1));
for (valueType i = 1; i <= N; ++i)
std::cin >> W[i];
for (valueType i = 0; i < N; ++i)
std::cin >> V[i];
for (valueType i = 1; i < N; ++i) {
valueType u, v;
std::cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, 0);
std::cout << *std::max_element(F[1].begin(), F[1].end()) << std::endl;
return 0;
}
B. A Dance of Fire and Ice / GYM103428C Assign or Multiply
使用原根将转移转化为形如:
的形式,使用 bitset 维护即可,复杂度 \(\mathcal{O}(n + p^2)\)。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
namespace MODINT_WITHOUT_MOD {
template<typename T1, typename T2, typename T3 = valueType>
void Inc(T1 &a, T2 b, const T3 &mod) {
a = a + b;
if (a >= mod)
a -= mod;
}
template<typename T1, typename T2, typename T3 = valueType>
void Dec(T1 &a, T2 b, const T3 &mod) {
a = a - b;
if (a < 0)
a += mod;
}
template<typename T1, typename T2, typename T3 = valueType>
T1 sum(T1 a, T2 b, const T3 &mod) {
return a + b >= mod ? a + b - mod : a + b;
}
template<typename T1, typename T2, typename T3 = valueType>
T1 sub(T1 a, T2 b, const T3 &mod) {
return a - b < 0 ? a - b + mod : a - b;
}
template<typename T1, typename T2, typename T3 = valueType>
T1 mul(T1 a, T2 b, const T3 &mod) {
return (long long) a * b % mod;
}
template<typename T1, typename T2, typename T3 = valueType>
void Mul(T1 &a, T2 b, const T3 &mod) {
a = (long long) a * b % mod;
}
template<typename T1, typename T2, typename T3 = valueType>
T1 pow(T1 a, T2 b, const T3 &mod) {
T1 result = 1;
while (b > 0) {
if (b & 1)
Mul(result, a, mod);
Mul(a, a, mod);
b = b >> 1;
}
return result;
}
}// namespace MODINT_WITHOUT_MOD
constexpr valueType V = 200000;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("dance.in", "r", stdin);
freopen("dance.out", "w", stdout);
#endif
valueType P, N;
std::cin >> P >> N;
valueType G = 1;
ValueVector factor;
{
valueType x = P - 1;
for (valueType i = 2; i * i <= x; ++i) {
if (x % i == 0) {
factor.push_back(i);
while (x % i == 0)
x /= i;
}
}
if (x > 1)
factor.push_back(x);
}
while (true) {
bool flag = true;
for (auto &i : factor) {
if (MODINT_WITHOUT_MOD::pow(G, (P - 1) / i, P) == 1) {
flag = false;
break;
}
}
if (flag)
break;
else
++G;
}
ValueVector log(P, 0), count(P, 0);
for (valueType i = 1, x = 1; i < P; ++i) {
x = MODINT_WITHOUT_MOD::mul(x, G, P);
log[x] = i;
}
log[1] = 0;
std::bitset<V> F;
bool existZero = false;
F[0] = true;
for (valueType i = 0; i < N; ++i) {
valueType type, x;
std::cin >> type >> x;
if (x == 0) {
existZero = true;
continue;
}
if (type == 1) {
++count[log[x]];
} else {
F[log[x]] = true;
}
}
for (valueType x = 1; x < P; ++x) {
std::bitset<V> next;
for (valueType i = 0; i < count[x]; ++i) {
next = F | (F << x) | (F >> (P - 1 - x));
if (next == F)
break;
F = next;
}
}
valueType ans = 0;
if (existZero)
++ans;
for (valueType i = 0; i < P - 1; ++i)
if (F[i])
++ans;
std::cout << ans << std::endl;
return 0;
}
C. 挖掘机技术哪家强 / GYM103428L shake hands
考虑将求最大团转化为求原图的最小点覆盖。
由于对于一般图最大独立集是 NP-Hard 的,所以我们考虑寻找一些性质,发现对于 \(a < b < c\),若在补图中满足存在边 \(\left(a, b\right)\) 和边 \(\left(b, c\right)\),则补图中一定存在 \(\left(a, c\right)\)。这一性质根据操作的方式可以很容易地证明。
进而我们考虑对补图中的边进行定向,对于每条边定向为从编号较小的点指向编号较大的点,这样就可以发现图中满足传递闭包的性质。进而在求该 DAG 的最大独立集时可以转化为求最长反链,其等于最小链覆盖。
考虑如何求最小链覆盖,不妨在初始时将每个点均视作一条链,这样可以保证链对补图的覆盖。考虑每次操作将两条链合并,这样可以保证合并后的链仍然对补图的覆盖。不难发现每个点只会被选择两次,分别是作为出点和入点,这启发我们将每个节点 \(u\),拆分为 \(u_{\operatorname{out}}\) 和 \(u_{\operatorname{in}}\),分别表示作为出点和入点的情况,这样就可以保证每个点只会被选择一次。
接下来问题转化为了求该二分图的最大匹配,可以发现经过定向后的补图实际上是一个竞赛图删去一些边后的结果,进而我们的问题转化为了:
给定一个删除一些边后的满二分图,求其最大匹配。
发现若在没有补图的影响下左部点的出度依次为 \(0, 1, 2, \cdots, n - 1\),也就是说若没有补图影响我们不需要退流操作也可以得到最大匹配,因此我们考虑先在不进行退流操作的情况下求出最大匹配,然后再进行退流操作。具体的,维护所有可以匹配的右部点集合,遍历左部点,并寻找可以与之匹配的右部点,若找到则将其从右部点集合中删除。
在此之后不妨假设我们有 \(k\) 个左部点没有匹配,那么可以发现其一定对于剩余没有匹配的 \(k\) 个右部点均没有边,也就是说在原图中存在 \(\mathcal{O}(k^2)\) 条边,因此不能匹配的节点数目是 \(\mathcal{O}(\sqrt{m})\) 级别的,因此我们可以直接对其进行暴力匹配,使用并查集维护可以匹配的右部点即可,复杂度为 \(\mathcal{O}(\left(n + m\right)\sqrt{m} \alpha(n))\)。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::set<valueType> ValueSet;
typedef std::vector<bool> bitset;
class DSU {
private:
valueType N;
ValueVector Father;
public:
DSU() = default;
explicit DSU(valueType n) : N(n), Father(n) {
std::iota(Father.begin(), Father.end(), 0);
}
void reset(valueType n) {
N = n;
Father.resize(N);
std::iota(Father.begin(), Father.end(), 0);
}
valueType Find(valueType x) {
return Father[x] == x ? x : Father[x] = Find(Father[x]);
}
void Merge(valueType x, valueType y) {
Father[Find(x)] = Find(y);
}
};
valueType N, M;
ValueMatrix G;
ValueVector to, from;
valueType ans;
bitset visited;
DSU dsu;
valueType dfs(valueType x) {
if (visited[x])
return 0;
visited[x] = true;
for (valueType i = 1; i < x; i = dsu.Find(i + 1)) {
if (std::find(G[x].begin(), G[x].end(), i) == G[x].end()) {
dsu.Merge(i, i + 1);
if (!from[i] || dfs(from[i]) > 0) {
to[x] = i;
from[i] = x;
return 1;
}
}
}
return 0;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("excavator.in", "r", stdin);
freopen("excavator.out", "w", stdout);
#endif
std::cin >> N >> M;
G.resize(N + 1);
to.resize(N + 1);
from.resize(N + 1);
visited.resize(N + 1);
ans = N;
ValueVector ID(N + 1);
std::iota(ID.begin(), ID.end(), 0);
for (valueType m = 0; m < M; ++m) {
valueType x;
std::cin >> x;
G[std::max(ID[x], ID[x + 1])].push_back(std::min(ID[x], ID[x + 1]));
std::swap(ID[x], ID[x + 1]);
}
for (auto &vec : G) {
std::sort(vec.begin(), vec.end());
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
}
ValueSet S;
for (valueType i = 1; i <= N; ++i) {
for (auto const &j : S) {
if (std::find(G[i].begin(), G[i].end(), j) == G[i].end()) {
to[i] = j;
from[j] = i;
S.erase(j);
--ans;
break;
}
}
S.insert(i);
}
for (valueType i = 1; i <= N; ++i) {
if (to[i] == 0) {
std::fill(visited.begin(), visited.end(), false);
dsu.reset(N + 1);
ans -= dfs(i);
}
}
std::cout << ans << std::endl;
return 0;
}