Pinely Round 5 (Div.1 + Div.2)
A - E 题解。
A
考虑 \(R\) 一定是越小越好,这样可以尽可能让 Div.2 也 Rated,于是每次 Rated Round 都有 \(R \gets \max(0, R - D)\)。模拟即可。
B
神人 b 题。YES 只有两种可能:黑格可补成一个 \(2\times 2\) 的正方形,或者黑格可以补成一条折线,比如这样:
.##...
..##..
...##.
....##
分别判断,\(\mathcal O(n^3)\) 模拟即可。
注意到还有一种刻画方法:黑格 \(x-y\) 的极差 \(\le 1\) 或者 \(x+y\) 的极差 \(\le 1\)。队友赛后给我讲了这个东西,但我没有尝试。
#include <bits/stdc++.h>
#define pb emplace_back
#define fir first
#define sec second
using pii = std::pair<int, int>;
std::pair<int, int> up = {-1, 0}, dn = {1, 0}, lef = {0, -1}, rig = {0, 1};
void work() {
int n;
std::cin >> n;
std::vector<std::string> s(n);
int tot = 0;
for (int i = 0; i < n; ++i) {
std::cin >> s[i];
}
bool ok = true;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (s[i][j] == '#') {
++tot;
}
}
}
if (n <= 2) return std::cout << "YES\n", void();
for (int i = 0; i < n - 1; ++i) {
for (int j = 0; j < n - 1; ++j) {
int c = 0;
for (int dx = 0; dx < 2; ++dx) {
for (int dy = 0; dy < 2; ++dy) {
c += s[i + dx][j + dy] == '#';
}
}
if (c == tot) {
return std::cout << "YES\n", void();
}
}
}
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
bool tmp = false;
auto chk = [&](int x, int y, pii del1, pii del2) -> bool {
int cur = 0;
do {
cur += s[x][y] == '#';
x += del1.fir, y += del1.sec;
std::swap(del1, del2);
} while (0 <= x && x < n && 0 <= y && y < n);
return cur == tot;
};
for (auto ud : {up, dn}) {
for (auto lr : {lef, rig}) {
tmp |= chk(x, y, ud, lr);
tmp |= chk(x, y, lr, ud);
}
}
if (tmp) return std::cout << "YES\n", void();
}
}
std::cout << "NO\n";
return;
}
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int t;
std::cin >> t;
while (t--) work();
return 0;
}
C
Key observation: 可以获得 bonus point 的次数一定,为 \(\lfloor \frac{\sum a_i}{X} \rfloor\)。
那么我们希望构造一组解,满足答案是前 \(\lfloor\frac{\sum a_i}{X} \rfloor\) 大。
考虑这样一个 idea:用较小的 \(a_i\) 垫,用最大的 \(a_i\) 完成收割。
对 \(a_i\) 升序排序,双指针,若 \(S + a_r\ge X\),那么取得一个 bonus point,\(S\gets (S+a_r)-X,r\gets r-1\)。
反之用 \(a_l\) 垫,\(S\gets S+a_l, l\gets l+1\)。
#include <bits/stdc++.h>
#define pb emplace_back
#define fir first
#define sec second
void work() {
int n, X;
std::cin >> n >> X;
std::vector<int> a(n);
for (auto& x : a) std::cin >> x;
std::sort(a.begin(), a.end());
long long ans = 0;
int S = 0;
int l = 0, r = n - 1;
std::vector<int> wt;
while (l <= r) {
if (S + a[r] >= X) {
(S += a[r]) -= X;
wt.pb(a[r]);
ans += a[r--];
} else {
wt.pb(a[l]);
S += a[l++];
}
}
std::cout << ans << '\n';
for (auto& x : wt) std::cout << x << ' ';
std::cout << '\n';
return;
}
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int t;
std::cin >> t;
while (t--) work();
return 0;
}
D
Key observation: 假设 \(x\) 在序列中的出现位置为 \(y_1,y_2,\dots y_m\),那么最优方案里,要么不留 \(x\),要么留下连续的一段 \(y_{[l,r]}\)。
证明很好理解,考虑如果你选上了 \(y_l, y_r\),那么 \(y_r\) 对 \(x-1\) 造成的影响最大,\(y_l\) 对 \(x+1\) 造成的影响最大,\(y_{(l, r)}\) 这一部分不选白不选。
那么考虑对值域 dp。设 \(f_{i, j}\) 表示值为 \(i\) 的数里,留下的最靠前的一个为 \(a_j\) 的最大贡献,\(g_{i, j}\) 表示值为 \(i\) 的数里,留下的最靠后的一个为 \(a_k\) 的最大贡献。若 \(j=n+1\) 表示没选 \(i\)。
这个状态可能会让人觉得很怪,其实只是我想形式化一点地描述 "贡献延后计算" 的过程,我自己思考的时候只定义了 \(f\)。
考虑转移,\(g_{i, j} = \max\limits_{k>j} (f_{i-1,k}+1), f_{i,j} = \max\limits_{k\ge j} (g_{i, k} + [[j, k)\ \mathrm{间}\ i \ \mathrm{出现的次数}])\)。其中蕴含了贡献延后计算的思想。
考虑开 \(n\) 个 vector 维护每个值 \(i\) 出现的位置。这样 \(f_{i, *}\) 的转移是简单的,我们考虑如何维护 \(g_{i, *}\) 的转移。
首先这是一个后缀 max 的形式,使用树状数组可以 \(\mathcal O(n\log n)\) 地维护。
但我们还可以做得更好!考虑双指针,两个指针分别倒着扫 \(i,i-1\) 出现的位置,对 \(i-1\) 那个指针维护一个后缀 max 即可。时间复杂度 \(\mathcal O(n)\)。考场上我写了树状数组的 \(\mathcal O(n\log n)\) 做法。
#include <bits/stdc++.h>
#define pb emplace_back
#define fir first
#define sec second
constexpr int maxn = 3e5 + 5;
int c[maxn], n, a[maxn];
std::vector<int> pos[maxn];
int lowbit(int x) {
return x & -x;
}
std::vector<int> stk;
void add(int x, int y) {
for (; x; x -= x & -x) {
stk.pb(x);
c[x] = std::max(c[x], y);
}
return;
}
int query(int x) {
int ans = 0;
for (; x <= n + 1; x += x & -x) {
ans = std::max(ans, c[x]);
}
return ans;
}
void work() {
std::cin >> n;
for (int i = 1; i <= n + 1; ++i) {
pos[i].clear();
a[i] = c[i] = 0;
}
stk.clear();
for (int i = 1; i <= n; ++i) {
std::cin >> a[i];
pos[a[i]].pb(i);
}
std::vector<int> ans(n + 1, 0);
for (int i = 1; i <= n; ++i) {
std::reverse(pos[i].begin(), pos[i].end());
std::vector<std::pair<int, int>> mdf(pos[i].size());
int cur = -1e9;
ans[i] = query(1);
for (int j = 0; j < pos[i].size(); ++j) {
cur = std::max(cur, query(pos[i][j] + 1) - j);
mdf[j] = {pos[i][j], cur + j + 1};
ans[i] = std::max(ans[i], cur + j + 1);
}
for (auto& x : stk) {
if (x <= n) c[x] = 0;
}
stk.clear();
add(n + 1, ans[i - 1]);
for (auto& [x, y] : mdf) add(x, y);
}
std::cout << n - std::max(ans[n], query(n + 1)) << '\n';
return;
}
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int t;
std::cin >> t;
while (t--) work();
return 0;
}
E
不妨设 \(0\) 开头,\(1\) 开头类似。
考虑合法的 \(s\) 的形态,乱玩几组样例可以发现,这道题的关键在于 \(01\) 交替的那个位置。
假设 \(s_{[1, x]} = 0, s_{x+1} = 1\),那么 \(x\) 开头的这个区间,必然是 \(0\) 出现了 \(\frac{k+1}2\) 次,\(1\) 出现了 \(\frac{k-1}2\) 次。并且 \([x+1,n]\) 会形成一个长度为 \(k-1\) 的周期,即 \(\forall y\in [x + 1, n], s_y = s_{y + k - 1}\)。
有点懒得证明,而且感觉不好证明,自己手摸一下就感受出来了(
考虑如何处理,根据循环节长度 \(k - 1\) 和输出的 \(s\) 的限制,预处理 \(s_i\) 可否为 \(0,1\),分为三类:
- \(s_i\) 只可取 \(0\)。
- \(s_i\) 只可取 \(1\)。
- \(s_i\) 可取 \(0,1\)。
枚举上文中的 \(x\),发现我们其实就是要求出 \(s_{[x+1,x+k-1]}\) 内 \(0,1\) 出现次数均为 \(\frac{k-1}2\) 的方案数。
筛掉那些已经固定的 \(s_i\),用组合数求出其余 \(s_i\) 的取值方案即可。
还有一种可能,直到最后一个区间都是 \(0\) 开头,和上面的问题类似,特判一下即可。时间复杂度 \(\mathcal O(n)\)。
#include <bits/stdc++.h>
#define pb emplace_back
#define fir first
#define sec second
using i64 = long long;
using pii = std::pair<int, int>;
constexpr int mod = 998244353;
void add(int& x, int y) { if ((x += y) >= mod) x -= mod; return; }
void sub(int& x, int y) { if ((x -= y) < 0) x += mod; return; }
namespace binomial {
std::vector<int> fac, ifac, inv;
void init(int n) {
fac.resize(n + 5, 0), ifac.resize(n + 5, 0), inv.resize(n + 5, 0);
fac[0] = fac[1] = ifac[0] = ifac[1] = inv[1] = 1;
for (int i = 2; i <= n; ++i) {
fac[i] = 1ll * fac[i - 1] * i % mod;
inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
ifac[i] = 1ll * ifac[i - 1] * inv[i] % mod;
}
return;
}
int C(int n, int m) {
if (n < 0 || m < 0 || n < m) return 0;
return 1ll * fac[n] * ifac[n - m] % mod * ifac[m] % mod;
}
}
using namespace binomial;
void work() {
int n, k;
std::cin >> n >> k;
std::string s;
std::cin >> s;
binomial::init(n);
std::vector<std::vector<bool>> div(n, std::vector<bool>(2, false));
for (int i = n - 1; ~i; --i) {
for (int j = 0; j < 2; ++j) {
div[i][j] = (s[i] == '0' + j) || (s[i] == '?');
if (i + k - 1 < n) div[i][j] = div[i][j] & div[i + k - 1][j];
}
}
std::vector<std::vector<int>> sum(n + 1, std::vector<int>(4, 0));
for (int i = n - 1; ~i; --i) {
++sum[i][div[i][0] + 2 * div[i][1]];
for (int j = 0; j < 4; ++j)
sum[i][j] += sum[i + 1][j];
}
int ans = 0;
for (int op = 0; op < 2; ++op) {
for (int i = 0; i < n; ++i) {
if (s[i] == '0' + (op ^ 1)) break;
if (i + 1 + k - 1 >= n) {
for (int tar = 0; tar <= k; ++tar) {
if (tar <= k - tar) continue;
if (sum[i + 1][0]) break;
int A = tar - sum[i + 1][op + 1], B = k - tar - sum[i + 1][(op ^ 1) + 1];
--A;
if (A < 0 || B < 0) continue;
add(ans, binomial::C(A + B, A));
}
break;
}
if (sum[i + 1][0] - sum[i + k][0] > 0) continue;
int tar = (k - 1) / 2, A = tar - (sum[i + 1][(op ^ 1) + 1] - sum[i + k][(op ^ 1) + 1]),
B = tar - (sum[i + 1][op + 1] - sum[i + k][op + 1]);
if (s[i + 1] == '?' && div[i + 1][0] && div[i + 1][1]) --A;
if (!div[i + 1][op ^ 1]) continue;
if (A < 0 || B < 0) continue;
add(ans, binomial::C(A + B, A));
}
}
std::cout << ans << '\n';
return;
}
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int t;
std::cin >> t;
while (t--) work();
return 0;
}

噢噢噢噢噢噢噢噢哦哦啊啊啊啊啊啊啊啊啊啊
浙公网安备 33010602011771号