AtCoder Beginner Contest 263
咕咕咕咕。
E - Sugoroku 3
反着跑DP,或者说逆向归纳。
记从\(i\)开始走到\(n\)的期望步数为\(dp_i\)。易得\(dp_n = 0\),然后\(dp_i\)可以由\(dp_{j}, i + 1 \le j \le i + a_i\)推出,从后往前推即可算出\(dp_1\),也就是答案。
具体就是假设摇骰子摇到\(x\),那么就可以花\(1\)步走到\(dp_{i + x}\),而\(dp_{i + x}\)是已经解决了的子问题,摇到\(x\)的概率恒为\(\frac{1}{a_i + 1}\),由此可得
解得
朴素的DP是\(O(n^2)\)的会超时,但是式子中求和那部分可以后缀和优化一下,然后就能做到\(O(n)\)。
AC代码
// Problem: E - Sugoroku 3
// Contest: AtCoder - LINE Verda Programming Contest(AtCoder Beginner Contest 263)
// URL: https://atcoder.jp/contests/abc263/tasks/abc263_e
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
#define freep(p) p ? delete p, p = nullptr, void(1) : void(0)
#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#endif
using i64 = int64_t;
using u64 = uint64_t;
void solve_case(int Case);
int main(int argc, char* argv[]) {
CPPIO;
int T = 1;
// std::cin >> T;
for (int t = 1; t <= T; ++t) {
solve_case(t);
}
return 0;
}
template <typename ValueType, ValueType mod_, typename SupperType = int64_t>
class Modular {
private:
ValueType value_;
ValueType normalize(ValueType value) const {
if (value >= 0 && value < mod_)
return value;
value %= mod_;
if (value < 0)
value += mod_;
return value;
}
ValueType power(ValueType value, size_t exponent) const {
ValueType result = 1;
ValueType base = value;
while (exponent) {
if (exponent & 1)
result = SupperType(result) * base % mod_;
base = SupperType(base) * base % mod_;
exponent >>= 1;
}
return result;
}
public:
Modular() : value_(0) {}
Modular(const ValueType& value) : value_(normalize(value)) {}
ValueType value() const { return value_; }
Modular inv() const { return Modular(power(value_, mod_ - 2)); }
Modular power(size_t exponent) const { return Modular(power(value_, exponent)); }
friend Modular operator+(const Modular& lhs, const Modular& rhs) {
ValueType result = lhs.value() + rhs.value() >= mod_ ? lhs.value() + rhs.value() - mod_
: lhs.value() + rhs.value();
return Modular(result);
}
friend Modular operator-(const Modular& lhs, const Modular& rhs) {
ValueType result = lhs.value() - rhs.value() < 0 ? lhs.value() - rhs.value() + mod_
: lhs.value() - rhs.value();
return Modular(result);
}
friend Modular operator*(const Modular& lhs, const Modular& rhs) {
ValueType result = SupperType(1) * lhs.value() * rhs.value() % mod_;
return Modular(result);
}
friend Modular operator/(const Modular& lhs, const Modular& rhs) {
ValueType result = SupperType(1) * lhs.value() * rhs.inv().value() % mod_;
return Modular(result);
}
};
template <typename StreamType, typename ValueType, ValueType mod, typename SupperType = int64_t>
StreamType& operator<<(StreamType& out, const Modular<ValueType, mod, SupperType>& modular) {
return out << modular.value();
}
template <typename StreamType, typename ValueType, ValueType mod, typename SupperType = int64_t>
StreamType& operator>>(StreamType& in, Modular<ValueType, mod, SupperType>& modular) {
ValueType value;
in >> value;
modular = Modular<ValueType, mod, SupperType>(value);
return in;
}
// using Mint = Modular<int, 1'000'000'007>;
using Mint = Modular<int, 998'244'353>;
class Binom {
private:
std::vector<Mint> f, g;
public:
Binom(int n) {
f.resize(n + 1);
g.resize(n + 1);
f[0] = Mint(1);
for (int i = 1; i <= n; ++i)
f[i] = f[i - 1] * Mint(i);
g[n] = f[n].inv();
for (int i = n - 1; i >= 0; --i)
g[i] = g[i + 1] * Mint(i + 1);
}
Mint operator()(int n, int m) {
if (n < 0 || m < 0 || m > n)
return Mint(0);
return f[n] * g[m] * g[n - m];
}
};
void solve_case(int Case) {
int n;
std::cin >> n;
std::vector<int> a(n + 1);
for (int i = 1; i <= n - 1; ++i)
std::cin >> a[i];
std::vector<Mint> dp(n + 1, Mint(0));
std::vector<Mint> s(n + 2, Mint(0));
for (int i = n - 1; i >= 1; --i) {
Mint S = (s[i + 1] - s[i + a[i] + 1]);
dp[i] = (S + Mint(a[i] + 1)) / Mint(a[i]);
s[i] = s[i + 1] + dp[i];
logd(i, s[i + 1].value(), s[i + a[i] + 1].value(), S.value(), dp[i].value());
}
std::cout << dp[1].value() << "\n";
}
F - Tournament
DP。
可以把赛程图看成一个满二叉树,从底下往上对层从\(0\)开始编号。
对于某个选手\(j\),假设他一直打到了第\(i\)层,则这层中的比赛他能够参加的只有一个。
对于第\(i\)层的某场比赛,这场比赛可能的参赛选手有\(2^i\)个,且这场比赛是由前一半的选手之一和后一半的选手之一来打。
假设\(dp_{i, j}\)表示\(i\)层的比赛中,\(j\)能参加的那场比赛的胜者为\(j\),以这场比赛为根的子树中所有比赛的最大收益之和。\(dp_{n, j}\)即为最终胜者为\(j\)的收益,由此答案即为\(\max_j dp_{n, j}\)。
\(dp_{i, j}\)其实是对应一个二叉树,可以由根节点以及根节点的两棵子树加起来得到。
假设这场比赛的胜者为来自前一半的\(x\),负者为来自后一半的\(y\),那么这场比赛对应收益\(dp_{i, x} = dp_{i - 1, x} + c_{x, i} - c_{x, i - 1} + \max_y dp_{i - 1, y}\)。其中\(c_{x, i} - c_{x, i - 1}\)为这场比赛的收益,剩余为前\(i - 1\)层中的比赛的收益。
分别枚举\(x\)和\(y\)即可算出\(dp_{i, x}\)但是会超时。观察可得\(x\)的改变并不会影响\(\max_y dp_{i - 1, y}\),所以可以先把\(\max_y dp_{i - 1, y}\)算出来当常数用,后续就不用重复再算了。
类似地可以求出\(dp_{i, y}\)。
然后\(dp_{i}\)只和\(dp_{i - 1}\)有关,所以可以搞滚动数组优化节省空间。
AC代码
// Problem: F - Tournament
// Contest: AtCoder - LINE Verda Programming Contest(AtCoder Beginner Contest 263)
// URL: https://atcoder.jp/contests/abc263/tasks/abc263_f
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
#define freep(p) p ? delete p, p = nullptr, void(1) : void(0)
#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#endif
using i64 = int64_t;
using u64 = uint64_t;
void solve_case(int Case);
int main(int argc, char* argv[]) {
CPPIO;
int T = 1;
// std::cin >> T;
for (int t = 1; t <= T; ++t) {
solve_case(t);
}
return 0;
}
void solve_case(int Case) {
int n;
std::cin >> n;
int m = (1 << n);
std::vector<std::vector<i64>> c(m, std::vector<i64>(n + 1, 0));
for (int i = 0; i < m; ++i) {
for (int j = 1; j <= n; ++j) {
std::cin >> c[i][j];
}
}
std::vector<i64> dp(m, 0), temp;
for (int i = 1; i <= n; ++i) {
temp = dp;
for (int j = 0; j < m; j += (1 << i)) {
i64 lmx = *std::max_element(temp.begin() + j, temp.begin() + j + (1 << (i - 1)));
i64 rmx = *std::max_element(temp.begin() + j + (1 << (i - 1)), temp.begin() + j + (1 << i));
for (int k = j; k < j + (1 << (i - 1)); ++k) {
dp[k] = temp[k] + (c[k][i] - c[k][i - 1]) + rmx;
}
for (int k = j + (1 << (i - 1)); k < j + (1 << i); ++k) {
dp[k] = temp[k] + (c[k][i] - c[k][i - 1]) + lmx;
}
}
}
i64 ans = *std::max_element(dp.begin(), dp.end());
std::cout << ans << "\n";
}
G - Erasing Prime Pairs
网络流。
先不考虑存在\(a_i = 1\)的情况,则此时由两个数加起来能够得到的素数都大于\(2\),所以都是奇数,所以可以拆成奇数加偶数,把奇数和偶数分成两部分,然后就是二分图最大匹配了,可以最大流跑。
现在考虑存在\(a_i = 1\)的情况,这个时候有一种可能就是\(1 + 1 = 2\)搞出来偶素数,这个就比较麻烦了。
记\(f(x)\)表示跑了\(x\)次\(1 + 1\),那么可以证明\(f(x) + x\)是单峰的。具体就是其相邻两项的差值为一段\(+1\),一段\(0\),一段\(-1\)。\(+1\)对应有多余的\(1\)两两匹配;\(0\)对应原本有\(1+x\)和\(1+y\)两个匹配,现在变成了\(1+1\)和\(x+y\)两个匹配;\(-1\)对应原本有\(1+x\)和\(1+y\)两个匹配,现在\(x\)和\(y\)匹配不上变成了只有\(1+1\)这个匹配。
把\(f(x) + x\)的图像画出来,观察可得根据最左边的点和最右边的点,就可以推出极值的下标。过程大概就是找出另外一个点使得\(f(a) = f(0)\),这个根据最后一段斜率为\(-1\)就可以得到,再然后就是\(\frac{a}{2}\)处一定取极值。
AC代码
// Problem: G - Erasing Prime Pairs
// Contest: AtCoder - LINE Verda Programming Contest(AtCoder Beginner Contest 263)
// URL: https://atcoder.jp/contests/abc263/tasks/abc263_g
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
#define freep(p) p ? delete p, p = nullptr, void(1) : void(0)
#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#endif
using i64 = int64_t;
using u64 = uint64_t;
void solve_case(int Case);
int main(int argc, char* argv[]) {
CPPIO;
int T = 1;
// std::cin >> T;
for (int t = 1; t <= T; ++t) {
solve_case(t);
}
return 0;
}
const int INF = 0x3f3f3f3f;
namespace PollardRho {
static std::mt19937_64 rng_(std::chrono::steady_clock::now().time_since_epoch().count());
inline int64_t Rand(int64_t l, int64_t r) {
return l + rng_() % (r - l + 1);
}
inline int64_t Add(int64_t a, int64_t b, int64_t mod) {
return ((__int128_t)a + b) % mod;
}
inline int64_t Substract(int64_t a, int64_t b, int64_t mod) {
return (((__int128_t)a - b) % mod + mod) % mod;
}
inline int64_t Multiply(int64_t a, int64_t b, int64_t mod) {
return (__int128_t)a * b % mod;
}
inline int64_t Power(int64_t a, int64_t b, int64_t mod) {
int64_t r = 1;
while (b) {
if (b & 1)
r = Multiply(r, a, mod);
a = Multiply(a, a, mod);
b >>= 1;
}
return r;
}
// Time Complexity: $O(k \log^{3} n)$
bool MillerRabinTest(int64_t n) {
// Strong enough for $n < 2^64$, see https://oeis.org/A014233.
constexpr static int kTestRounds = 12;
constexpr static int kTestBase[kTestRounds] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
if (n <= kTestBase[kTestRounds - 1]) {
return *std::lower_bound(kTestBase, kTestBase + kTestRounds, n) == n;
}
int64_t d = n - 1, r = 0;
while (d % 2 == 0) {
d >>= 1;
r = r + 1;
}
for (int round = 0; round < kTestRounds; ++round) {
int64_t a = kTestBase[round];
// Fermet primality test.
int64_t x = Power(a, d, n);
if (x == 1 || x == n - 1)
continue;
// Witness primality test.
for (int i = 0; i < r - 1; ++i) {
x = Multiply(x, x, n);
if (x == n - 1)
break;
}
if (x != n - 1)
return false;
}
return true;
}
int64_t Rho(int64_t n) {
// Can not factor 4 because the faster step gap is 2.
if (n == 4)
return 2;
const static int kMaxStepSize = 1 << 7;
int64_t c;
std::function<int64_t(int64_t)> f = [&n, &c](int64_t x) { return Add(Multiply(x, x, n), c, n); };
for (;;) {
c = Rand(3, n - 1);
int64_t t = f(Rand(0, n - 1)), r = f(t);
for (int goal = 1; t != r; goal = std::min(goal << 1, kMaxStepSize)) {
int64_t d1, d2 = 1;
for (int step = 0; step < goal; ++step) {
d1 = Multiply(d2, Substract(t, r, n), n);
if (d1 == 0)
break;
d2 = d1;
t = f(t);
r = f(f(r));
}
int64_t d = std::gcd(d2, n);
if (d != 1 and d != n)
return d;
}
}
}
std::vector<int64_t> Factor(int64_t n) {
std::vector<int64_t> factors;
std::function<void(int64_t)> factor = [&](int64_t n) {
if (n < 2)
return;
if (MillerRabinTest(n)) {
factors.push_back(n);
} else {
int64_t x = Rho(n);
while (n % x == 0)
n /= x;
factor(x);
factor(n);
}
};
factor(n);
std::sort(factors.begin(), factors.end());
return factors;
}
}; // namespace PollardRho
template <typename CapacityType>
class MaxFlowGraph {
struct Edge {
int from, to;
CapacityType capacity, flow;
Edge() {}
Edge(int _from, int _to, CapacityType _capacity, CapacityType _flow)
: from(_from), to(_to), capacity(_capacity), flow(_flow) {}
};
int n_;
int m_;
std::vector<Edge> edges_;
std::vector<std::vector<int>> adjacent_;
public:
explicit MaxFlowGraph(int n) : n_(n), m_(0), edges_(0), adjacent_(n) {}
void AddEdge(int from, int to, CapacityType capacity) {
assert(0 <= from and from < n_);
assert(0 <= to and to < n_);
edges_.emplace_back(from, to, capacity, 0);
adjacent_[from].push_back(m_);
++m_;
edges_.emplace_back(to, from, 0, 0);
adjacent_[to].push_back(m_);
++m_;
}
CapacityType Dinic(int src, int dst) {
const static CapacityType INF = std::numeric_limits<CapacityType>::max();
std::vector<int> level(n_);
std::vector<int> start_index(n_);
std::function<bool()> bfs = [&]() -> bool {
std::fill(level.begin(), level.end(), -1);
std::queue<int> q;
q.push(src);
level[src] = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int edge_id : adjacent_[u]) {
auto [from, to, capacity, flow] = edges_[edge_id];
CapacityType residual_capacity = capacity - flow;
if (residual_capacity > 0 && level[to] == -1) {
level[to] = level[u] + 1;
if (to == dst)
break;
q.push(to);
}
}
}
return level[dst] != -1;
};
std::function<CapacityType(int, CapacityType)> dfs =
[&](int u, CapacityType max_augment) -> CapacityType {
if (u == dst)
return max_augment;
if (max_augment == 0)
return 0;
CapacityType total_augment = 0;
int i = start_index[u];
for (; i < (int)adjacent_[u].size(); ++i) {
int edge_id = adjacent_[u][i];
auto [from, to, capacity, flow] = edges_[edge_id];
if (level[to] == level[u] + 1) {
CapacityType residual_capacity = capacity - flow;
CapacityType new_augment = dfs(to, std::min(max_augment, residual_capacity));
if (new_augment <= 0)
continue;
max_augment -= new_augment;
total_augment += new_augment;
edges_[edge_id].flow += new_augment;
edges_[edge_id ^ 1].flow -= new_augment;
if (max_augment == 0)
break;
}
}
start_index[u] = i;
if (total_augment == 0)
level[u] = -1;
return total_augment;
};
CapacityType max_flow = 0;
while (bfs()) {
std::fill(start_index.begin(), start_index.end(), 0);
CapacityType new_flow = dfs(src, INF);
logd(new_flow);
max_flow += new_flow;
}
return max_flow;
}
};
void solve_case(int Case) {
int n;
std::cin >> n;
std::vector<std::pair<int, int>> v0, v1;
int c1 = 0;
for (int i = 0; i < n; ++i) {
int a, b;
std::cin >> a >> b;
if (a & 1) {
v1.push_back({a, b});
} else {
v0.push_back({a, b});
}
if (a == 1) {
c1 = b;
}
}
auto f = [&](int k) {
i64 ans = 0;
MaxFlowGraph<i64> g(n + 2);
int S = n, T = S + 1;
for (int i = 0; i < v0.size(); ++i) {
auto [a, b] = v0[i];
g.AddEdge(S, i, b);
}
for (int i = 0; i < v1.size(); ++i) {
auto [a, b] = v1[i];
if (a == 1) {
g.AddEdge(v0.size() + i, T, b - 2 * k);
} else {
g.AddEdge(v0.size() + i, T, b);
}
}
for (int i = 0; i < v0.size(); ++i) {
for (int j = 0; j < v1.size(); ++j) {
auto [x, _0] = v0[i];
auto [y, _1] = v1[j];
if (PollardRho::MillerRabinTest(x + y)) {
g.AddEdge(i, v0.size() + j, INF);
}
}
}
return g.Dinic(S, T) + k;
};
i64 y0 = f(0), y1 = f(c1 / 2);
i64 x = (c1 / 2 - (y0 - y1)) / 2;
std::cout << f(x) << "\n";
}
Ex - Intersection 2
To be solved。