Solution Set【2024.1.9】
A. k 大值
不喜欢 k 大值,所以转化为求第 \(n - k + 1\) 小值。
注意到在 \(\left[0, V\right]\) 中均匀随机生成 \(n\) 个变量,其中第 \(k\) 小值的期望为 \(\frac{k}{n+1}V\),因此我们可以设置一个阈值 \(t\),并且存储位于 \(\left[\frac{k - t}{n + 1}V, \frac{k + t}{n + 1}V\right]\) 的数有哪些,同时记录小于这个区间左边界的数的个数和大于这个区间右边界的数的个数,这样我们就可以计算出应该求的是在这个范围内的第几小的数,然后再在这个范围内使用现行求第 \(k\) 小值的算法即可。
时间复杂度为 \(O(n)\),空间复杂度期望为 \(O(t)\)。
Code
#include <bits/stdc++.h>
typedef unsigned long long valueType;
typedef std::vector<valueType> ValueVector;
valueType xor_shift(valueType x) {
x ^= (x << 13);
x ^= (x >> 17);
x ^= (x << 5);
return x;
}
constexpr valueType L = 2e6, half = L / 2;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("kth.in", "r", stdin);
freopen("kth.out", "w", stdout);
#endif
valueType N, K, T;
std::cin >> N >> K >> T;
K = N - K + 1;
if (N <= L) {
ValueVector A(N);
for (valueType i = 0; i < N; i++) {
A[i] = T;
T = xor_shift(T);
}
std::nth_element(A.begin(), A.begin() + K - 1, A.end());
std::cout << A[K - 1] << std::endl;
} else {
valueType const mid = std::numeric_limits<valueType>::max() / (N + 1);
valueType const min = mid * static_cast<valueType>(std::max<int>((int) 0, (int) K - (int) half)), max = mid * std::min<valueType>(N + 1, K + half);
valueType leftCount = 0, rightCount = 0;
ValueVector A;
A.reserve(L);
for (valueType i = 0; i < N; ++i) {
if (T < min) {
++leftCount;
} else if (T > max) {
++rightCount;
} else {
A.push_back(T);
}
T = xor_shift(T);
}
K -= leftCount;
std::nth_element(A.begin(), A.begin() + K - 1, A.end());
std::cout << A[K - 1] << std::endl;
}
return 0;
}
B. 染色 / ARC004F Namori
Trees are always bipartite.
首先考虑树的情况。
考虑对树进行黑白染色,那么我们的操作就可以变为了每次选择一个黑点和一个白点,将这两个点染成相反的颜色。不妨将其视作给定两个点集 \(S, T\) 满足 \(S \cup T = V\), \(S \cap T = \varnothing\),初始时在 \(S\) 中的每个点上都有一个硬币,每次操作可以将一枚硬币移动一个距离,求能否使得 \(T\) 的每个点上都有一个硬币。
考虑如何解决这个问题,首先可以发现硬币的总数是不变的,因此若 \(\left\lvert S \right\rvert \neq \left\lvert T \right\rvert\),那么一定无解,反之一定存在一组解。对于答案的统计可以考虑每一条边的贡献,设其连接的两个连通块分别为 \(G_1, G_2\),其中 \(\left\lvert G_1 \cap S \right\rvert = x, \left\lvert G_2 \cap T \right\rvert = y\),那么这条边的贡献就是 \(\left\lvert x - y \right\rvert\),所有边的贡献之和就是答案。
下面考虑基环树的情况,因为对图进行了黑白染色,故按环长进行分类讨论。
对于环长为偶数的情况,可以发现若去掉环上的一条边,那么剩下的图就是一棵树,因此可以直接套用树的情况,这样我们唯一需要处理的就是删去的边带来的贡献。
设 \(f_i\) 表示在树的情况下点 \(i\) 连向其父亲节点的边的贡献。若我们设删去的边 \(\left(u, v\right)\) 的权值为 \(k\),即有 \(k\) 个硬币从 \(u\) 移动到了 \(v\),若 \(k < 0\) 那么其意味着有 \(-k\) 个硬币从 \(v\) 移动到了 \(u\),那么可以发现 \(k\) 的值影响了原图中环上节点即树上被这条边覆盖的区域的边的权值,具体的,这部分的边的贡献为
那么我们要求的便是
取 \(k\) 为 \(f_i\) 序列的中位数即可。
对于环长为奇数的情况,可以发现一定存在环上的一条边 \(\left(u_0, v_0\right)\),满足 \(u_0\) 和 \(v_0\) 属于同一个集合,不妨设其均属于 \(S\)。那么考虑这条边上的操作,发现其等价于在推硬币的操作上增加了如下操作:
- 若 \(u_0\) 和 \(v_0\) 上都有硬币,那么拿走这两枚硬币
- 若 \(u_0\) 和 \(v_0\) 上都没有硬币,那么放上两枚硬币
不难发现通过这样的操作,我们可以调节 \(\left\lvert S \right\rvert\) 的值,进而若 \(\left\lvert\left\lvert S \right\rvert - \left\lvert T \right\rvert\right\rvert\) 的值为偶数,我们可以使得其有解。具体的设 \(k = \left\lvert S \right\rvert - \left\lvert T \right\rvert\),那么我们可以假设在初始情况下 \(u_0\) 和 \(v_0\) 上均额外存在 \(\frac{k}{2}\) 枚硬币,那么继续使用树的情况求解即可。若 \(\left\lvert S \right\rvert < \left\lvert T \right\rvert\) 那么我们可以将 \(S\) 和 \(T\) 互换,然后使用上述方法求解。
时间复杂度为 \(O(n)\)。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
valueType N, M;
ValueMatrix G;
ValueVector inCircle, weight;
valueType A, B;
bool odd = false;
void coloring(valueType x, valueType from) {
for (auto const &to : G[x]) {
if (to == from)
continue;
if (weight[to] == 0) {
weight[to] = -weight[x];
coloring(to, x);
} else {
A = x;
B = to;
if (weight[to] == weight[x])
odd = true;
}
}
}
void dfs(valueType x, valueType from) {
for (auto const &to : G[x]) {
if (to == from || (x == A && to == B) || (x == B && to == A))
continue;
dfs(to, x);
inCircle[x] += inCircle[to];
weight[x] += weight[to];
}
}
void failed() {
std::cout << -1 << std::endl;
std::exit(0);
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("color.in", "r", stdin);
freopen("color.out", "w", stdout);
#endif
std::cin >> N >> M;
if (N & 1)
failed();
G.resize(N + 1);
inCircle.resize(N + 1, 0);
weight.resize(N + 1, 0);
for (valueType i = 0; i < M; ++i) {
valueType u, v;
std::cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
weight[1] = 1;
coloring(1, 0);
valueType const sum = std::accumulate(weight.begin(), weight.end(), 0);
valueType ans = 0;
if (M == N - 1) {
if (sum != 0)
failed();
} else {
if (odd) {
ans += std::abs(sum) / 2;
weight[B] -= sum / 2;
weight[A] -= sum / 2;
} else {
if (sum != 0)
failed();
inCircle[A] = 1;
inCircle[B] = -1;
}
}
dfs(1, 0);
ValueVector pool({0});
pool.reserve(N);
for (valueType i = 1; i <= N; ++i) {
if (inCircle[i] != 0)
pool.push_back(weight[i]);
else
ans += std::abs(weight[i]);
}
std::nth_element(pool.begin(), pool.begin() + pool.size() / 2, pool.end());
valueType const mid = pool[pool.size() / 2];
for (auto const &w : pool)
ans += std::abs(w - mid);
std::cout << ans << std::endl;
return 0;
}
C. 函数 / TopCoder FunctionalEquation
首先我们有如下结论:
下面给出证明:
设 \(b = 2f(a) - a + 1\),那么我们有
设 \(d = 2f(b) - b + 1\),那么我们有
同时我们有
因此我们有 \(f(a + 2C) = f(d) = f(a) + 2C\)。
所以可以发现只要我们对于所有 \(x_0 \in \left[0, 2C\right)\),确定了 \(f(x_0)\) 的值,那么我们就可以确定 \(f(x)\) 的值。
但是我们发现,当我们对于 \(a_0 \in \left[0, 2C\right)\),确定了 \(f(a_0)\) 的值后,设 \(b = 2f(a_0) - a_0 + 1\),那么我们有:
因此这也限制了 \(f(b)\) 的取值。
不妨设 \(b_0 = b - n \cdot 2C\) 且 \(b_0 \in \left[0, 2C\right)\),那么我们有:
不难发现等式右侧为奇数,因此 \(a_0\) 和 \(b_0\) 的奇偶性不同,这样我们可以将 \(\left[0, 2C\right)\) 中的所有奇数和偶数分开,然后进行配对。
不妨设 \(a_0\) 为奇数,设 \(a_0 = 2x, b_0 = 2y\),那么我们有:
因为有 \(f(b) = f(a_0) + C, f_(b_0) = f(b - 2nC) = f(b) = 2nC\):,因此我们有:
下面考虑如何计算 \(\min\sum\left\lvert f(x_i) - y_i \right\rvert\) 的值。
对于数对 $$\left(x_i, y_i\right)$$,若其满足 \(x_i \equiv a_0 \pmod {2C}\),那么我们称其和 \(a_0\) 为一组,类似的,若其满足 \(x_i \equiv b_0 \pmod {2C}\),那么我们称其和 \(b_0\) 为一组。
对于和 \(a_0\) 为一组的数对,不妨设 \(x_i = a_0 + 2kC\),那么我们有:
设 \(f_i = x + y + 2kC - y_i\),我们有 \(\left\lvert f(x_i) - y_i \right\rvert = \left\lvert f_i + nC \right\rvert\)
类似的,对于和 \(b_0\) 为一组的数对,不妨设 \(x_i = b_0 + 2kC\),那么我们有:
设 \(f_i = -x -y -2kC -C + y_i\),我们有 \(\left\lvert f(x_i) - y_i \right\rvert = \left\lvert f_i + nC \right\rvert\)
所以我们要求的即为
不难发现 \(-nC\) 越接近 \(f\) 的中位数答案越小,因此我们可以先求出 \(f_i\) 序列的中位数,计算出距离其最近的两个满足是 \(nC\) 的倍数的数,然后再按这两个数计算答案后取最小值即可。
这样我们就可以进行二分图最小权匹配了,使用费用流求解即可。
时间复杂度为 \(\mathcal{O}(nC + C^2)\),二分图的点数为 \(\mathcal{O}(C)\),边数为 \(\mathcal{O}(C^2)\)。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::pair<valueType, valueType> ValuePair;
typedef std::vector<ValuePair> PairVector;
typedef std::vector<PairVector> PairMatrix;
auto const _begin = std::chrono::steady_clock::now();
template<typename T = valueType, typename Compare = std::less<T>>
class MCMF {
private:
struct Edge {
public:
typedef std::vector<Edge> container;
T to;
T cap;
T flow;
T cost;
T pair;
Edge() : to(-1), cap(-1), flow(-1), cost(0), pair(){};
Edge(T to, T cap, T c, T pair) : to(to), cap(cap), flow(0), cost(c), pair(pair){};
};
typedef typename Edge::container Graph;
typedef std::vector<bool> bitset;
T N, edgeCount;
Graph edges;
ValueMatrix G;
std::vector<T> dist;
ValueVector start;
bool Initialized;
public:
explicit MCMF(T N, T M) : N(N), edgeCount(0), edges(2 * M + 1), G(N + 1), dist(N + 1, 0), start(N + 1), Initialized(false){};
void addEdge(T from, T to, T cap, T cost) {
if (__builtin_expect(Initialized, false))
throw std::runtime_error("MCMF: addEdge after init");
edges[++edgeCount] = Edge(to, cap, cost, 0);
G[from].push_back(edgeCount);
edges[++edgeCount] = Edge(from, 0, -cost, 0);
G[to].push_back(edgeCount);
edges[G[from].back()].pair = G[to].back();
edges[G[to].back()].pair = G[from].back();
}
void init() {
if (__builtin_expect(Initialized, false))
throw std::runtime_error("MCMF: init twice");
Initialized = true;
std::fill(dist.begin(), dist.end(), 0);
for (T i = 1; i <= N; ++i)
start[i] = 0;
}
void reset() {
if (__builtin_expect(!Initialized, false))
throw std::runtime_error("MCMF: reset before init");
for (T i = 1; i <= N; ++i)
for (auto &iter : G[i])
iter.flow = 0;
std::fill(dist.begin(), dist.end(), 0);
Initialized = false;
}
std::pair<T, T> maxFlow(T _S, T _T) {
if (__builtin_expect(!Initialized, false))
throw std::runtime_error("MCMF: maxFlow before init");
std::pair<T, T> result(0, 0);
while (bfs(_S, _T)) {
// std::cerr << "Time[3] : " << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - _begin).count() << "[ms]" << std::endl;
ValueVector begin = start;
bitset visited(N + 1, false);
T const flow = dfs(_S, _T, std::numeric_limits<T>::max(), begin, visited);
result.first += flow;
result.second += flow * dist[_T];
}
return result;
}
private:
bool bfs(T _S, T _T) {
static_assert(std::is_same<Compare, std::less<T>>::value || std::is_same<Compare, std::greater<T>>::value || std::is_same<Compare, std::less<>>::value || std::is_same<Compare, std::greater<>>::value, "MCMF: bfs only support less or greater");
static valueType const constexpr initValue = std::is_same<Compare, std::less<T>>::value || std::is_same<Compare, std::less<>>::value ? std::numeric_limits<T>::max() : std::numeric_limits<T>::min();
std::fill(dist.begin(), dist.end(), initValue);
bitset visited(N + 1, false);
std::queue<T> Q;
Q.push(_S);
dist[_S] = 0;
visited[_S] = true;
while (!Q.empty()) {
T const u = Q.front();
visited[u] = false;
Q.pop();
for (auto const &iter : G[u]) {
auto const &e = edges[iter];
if (e.cap > e.flow && Compare()(dist[u] + e.cost, dist[e.to])) {
dist[e.to] = dist[u] + e.cost;
if (!visited[e.to]) {
Q.push(e.to);
visited[e.to] = true;
}
}
}
}
return dist[_T] != initValue;
}
T dfs(T u, T _T, T flow, ValueVector &Begin, bitset &visited) {
if (u == _T || flow == 0)
return flow;
T result = 0;
visited[u] = true;
for (auto &iter = Begin[u]; iter != G[u].size() && flow > 0; ++iter) {
auto &e = edges[G[u][iter]];
if (!visited[e.to] && e.cap > e.flow && dist[e.to] == dist[u] + e.cost) {
T const f = dfs(e.to, _T, std::min(flow - result, e.cap - e.flow), Begin, visited);
if (f == 0) {
dist[e.to] = std::numeric_limits<T>::max();
continue;
}
e.flow += f;
edges[e.pair].flow -= f;
result += f;
if (result == flow) {
visited[u] = false;
return flow;
}
}
}
visited[u] = false;
return result;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("function.in", "r", stdin);
freopen("function.out", "w", stdout);
#endif
valueType N, C;
std::cin >> N >> C;
valueType const M = 2 * C;
ValueMatrix weight(M);
for (valueType i = 0; i < N; ++i) {
valueType x, y;
std::cin >> x >> y;
weight[x % M].push_back(y - x + (x % M));
}
ValueVector odd(C + 1, 0), even(C + 1, 0);
valueType size = 0;
valueType const S = ++size, T = ++size;
for (valueType i = 0; i < C; ++i) {
odd[i] = ++size;
even[i] = ++size;
}
MCMF<valueType> mcmf(size, C * C * 2 + 100);
std::cerr << "Time[1] : " << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - _begin).count() << "[ms]" << std::endl;
ValueVector pool;
pool.reserve(N);
auto calc = [&](valueType X, valueType Y) -> valueType {
valueType const A = 2 * X, B = 2 * Y + 1;
pool.clear();
for (auto const &w : weight[A])
pool.push_back(w - ((A + B) / 2));
for (auto const &w : weight[B])
pool.push_back(((A + B) / 2) + C - w);
if (pool.empty())
return 0;
std::nth_element(pool.begin(), pool.begin() + pool.size() / 2, pool.end());
valueType mid = pool[pool.size() / 2] / C * C;
valueType result1 = 0, result2 = 0, result3 = 0;
for (auto const &v : pool) {
result1 += std::abs(v - mid);
result2 += std::abs(v - mid - C);
result3 += std::abs(v - mid + C);
}
return std::min({result1, result2, result3});
};
for (valueType i = 0; i < C; ++i) {
mcmf.addEdge(S, odd[i], 1, 0);
mcmf.addEdge(even[i], T, 1, 0);
for (valueType j = 0; j < C; ++j)
mcmf.addEdge(odd[i], even[j], 1, calc(i, j));
}
std::cerr << "Time[2] : " << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - _begin).count() << "[ms]" << std::endl;
mcmf.init();
auto const [flow, cost] = mcmf.maxFlow(S, T);
std::cout << cost << std::endl;
return 0;
}