Codeforces Round #767 (Div. 1) 题解 A – D2
A. Meximum Array
从左到右扫描, 维护每一个数字当前出现的次数和总的次数, 以此来 \(O(1)\) 地得到 \(x\) 是否存在于后缀中. 利用 std::set
维护 mex, 如果当前 mex 不存在于后缀, 即不会再变大, 则贪心地结束当前子串, 因为剩下的数字越多字典序越大(或不变).
#include <bits/stdc++.h>
using namespace std;
#define inc(x, l, r) for (int x = l; x <= r; x++)
#define ll long long
const int maxn = 1e6 + 5;
int a[maxn], ap[maxn], sz[maxn];
void solve() {
int n;
cin >> n;
inc(i, 0, n) sz[i] = ap[i] = 0;
inc(i, 1, n) {
cin >> a[i];
sz[a[i]]++;
}
int mex = 0;
set<int> s;
vector<int> ans;
inc(i, 1, n) {
ap[a[i]]++;
s.insert(a[i]);
while (s.count(mex))
mex++;
if (ap[mex] == sz[mex]) {
ans.push_back(mex);
mex = 0;
s.clear();
}
}
if (s.size())
ans.push_back(mex);
cout << ans.size() << "\n";
inc(i, 0, (int)ans.size() - 1) cout << ans[i] << " \n"[i + 1 == ans.size()];
}
int main() {
int T;
cin >> T;
while (T--) {
solve();
}
}
B. Peculiar Movie Preferences
首先如果集合中有回文串, 则只取该串就可以得到"拼接"后的回文串. 特别地, 长度为 \(1\) 的串都是回文的.
此外, 如果存在两个串 s t, s == reverse(t)
, 那么只取这两个串也可以得到题意要求的.
如果不满足上述条件但仍然存在合法方案, 则第一个串和最后一个串长度一个是 \(2\) 另一个是 \(3\). 不妨设第一个串是 ab
, 那么最后一个串一定得是 xba
, x
可以是任何字符. 此时发现只取第一个串和最后一个串已经是一个合法方案.
剩下要做的事情就是, 枚举当前串为第一个串, 查询结尾为合法情况的串是否存在(hash 存储, 用上一题类似的方法查询后缀中是否存在某某).
#include <bits/stdc++.h>
using namespace std;
#define inc(x, l, r) for (int x = l; x <= r; x++)
#define ll long long
const int maxn = 2e4 + 5;
int sz[maxn], ap[maxn];
int Hash(string s) {
int r = 0;
for (auto e : s) {
r = r * 27 + e - 'a' + 1;
}
return r;
}
string rev(string s) {
reverse(s.begin(), s.end());
return s;
}
void solve() {
int n;
cin >> n;
vector<string> v;
int ok = 0;
inc(i, 0, 20000) sz[i] = ap[i] = 0;
inc(i, 1, n) {
string s;
cin >> s;
if (s[0] == s.back())
ok = 1;
sz[Hash(s)]++;
v.push_back(s);
}
inc(i, 0, n - 1) {
ap[Hash(v[i])]++;
int o = Hash(rev(v[i]));
if (ap[o] < sz[o]) {
ok = 1;
}
if (v[i].size() == 2) {
string p = rev(v[i]);
inc(j, 0, 25) {
int u = Hash((char)(j + 'a') + p);
if (ap[u] < sz[u]) {
ok = 1;
}
}
} else if (v[i].size() == 3) {
string p = rev(v[i]).substr(1);
int u = Hash(p);
if (ap[u] < sz[u]) {
ok = 1;
}
}
}
if (ok)
cout << "YES\n";
else
cout << "NO\n";
}
int main() {
int T;
cin >> T;
while (T--) {
solve();
}
}
C. Grid Xor
如果能找到一个 \((i,j)\) 集合, 表示只取 \(a[i][j]'\) 时, 原矩阵每个元素的贡献都是 \(1\). 那么答案就是 \(a[i][j]\) 的异或和. 用 \(c[i][j]\) 表示 \(a[i][j]\) 是否取, \(1\) 取 \(0\) 不取.
我们第一行随意赋值 \(c[1][i]\)(官方解法1是令\(c[1][i]=1\)). 然后从第二行开始从上到下扫描, 对于当前位置 \((i,j)\), 我们的目标是让 \(a[i-1][j]\) 的贡献为 \(1\), 即
这样便得到了 \(c[i][j]\).
不过现在还需要证明最后一行 \(a[n][i]\) 的贡献都是 \(1\).
考虑现在有一个合法方案 \(c[i][j]'\), 我们逐一比对求得的 \(c[1][i]\) 与 \(c[1][i]'\), 如果不一样则修改 \(c[1][i]'\), 然后依次修改受影响的 \(c[i][j]'(i > 1)\), 整个过程保证 \(a[i][j]'\) 的贡献仍然为 \(1\). 可以发现最后两行一定是修改 \((n-1,n-i), (n-1)(n-i+2), (n,n-i+1)\), 则最后一行 \(a[i][j]'\) 的贡献不变, 仍为合法.
#include <bits/stdc++.h>
using namespace std;
#define inc(x, l, r) for (int x = l; x <= r; x++)
const int maxn = 1e3 + 5;
int a[maxn][maxn], c[maxn][maxn];
void solve() {
int n;
cin >> n;
inc(i, 1, n) inc(j, 1, n) cin >> a[i][j];
inc(i, 1, n) c[1][i] = rand() & 1;
inc(i, 1, n) c[i][n + 1] = 0;
inc(i, 2, n) inc(j, 1, n) {
c[i][j] = c[i - 2][j] ^ c[i - 1][j - 1] ^ c[i - 1][j + 1] ^ 1;
}
int ans = 0;
inc(i, 1, n) inc(j, 1, n) if (c[i][j]) ans ^= a[i][j];
cout << ans << "\n";
}
int main() {
srand(time(NULL));
int T;
cin >> T;
while (T--) {
solve();
}
}
另外还有构造图形的方法, 比如用一组两个相邻位置作为基本元素, 拼成一个 \(n\times n\) 的图形. Codeforces 题解讨论里还有一种黑白棋染色的方法也很巧妙.
D1. Game on Sum (Easy Version)
我们先把 \(k\) 视为单位一, 最后乘上就行.
定义 \(f(n, m)\) 为还剩 \(n\) 轮, 需要做 \(m\) 次加法的结果. 有 \(f(n, n) = n\), \(f(n, 0) = 0\). 另外当 \(n > m > 0\) 时:
得
至此, 我们可以 \(O(nm)\) 地 DP 解决 D1 的数据.
#include <bits/stdc++.h>
using namespace std;
#define inc(x, l, r) for (int x = l; x <= r; x++)
#define ll long long
const int maxn = 2e3 + 5;
const int N = 2e3;
const int mod = 1e9 + 7;
int dp[maxn][maxn];
ll ksm(ll a, ll x) {
ll r = 1;
a %= mod;
while (x) {
if (x & 1)
r = r * a % mod;
a = a * a % mod;
x >>= 1;
}
return r;
}
ll inv(ll a) {
return ksm(a, mod - 2);
}
void solve() {
ll n, m, k;
cin >> n >> m >> k;
cout << dp[m][n] * k % mod << "\n";
}
int main() {
ll i2 = inv(2);
inc(i, 1, N) {
dp[i][i] = i;
inc(j, i + 1, N) {
dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - 1]) % mod * i2 % mod;
}
}
int T;
cin >> T;
while (T--) {
solve();
}
}
D2. Game on Sum (Hard Version)
沿用前面的 \(f(n,m)\) 的定义. 我们知道整个 DP 流程非常类似于杨辉三角, 只是多了个除以 \(2\), 这只要在考虑贡献时乘以 \(2^{-len}\). 也就是说 \(f(n,m)= \sum_{1\leq i\leq n} f(i,i) \times C \times 2^{-len}\), \(C\) 为 从 \((i+1,i)\) 到 \((n,m)\) 的路径数(每次向右走一步或向右上走一步).
#include <bits/stdc++.h>
using namespace std;
#define inc(x, l, r) for (int x = l; x <= r; x++)
#define ll long long
const int N = 1e6;
const int mod = 1e9 + 7;
ll fac[N + 5];
ll ksm(ll a, ll x) {
ll r = 1;
a %= mod;
while (x) {
if (x & 1)
r = r * a % mod;
a = a * a % mod;
x >>= 1;
}
return r;
}
ll inv(ll a) {
return ksm(a, mod - 2);
}
ll C(int n, int m) {
return fac[n] * inv(fac[m]) % mod * inv(fac[n - m]) % mod;
}
void solve() {
ll n, m, k;
cin >> n >> m >> k;
ll ans = 0;
if (n == m) {
ans = n;
} else {
inc(i, 1, m) ans =
(ans + i * C(n - i - 1, m - i) % mod * ksm(inv(2), n - i)) % mod;
}
cout << ans * k % mod << "\n";
}
int main() {
fac[0] = 1;
inc(i, 1, N) fac[i] = fac[i - 1] * i % mod;
int T;
cin >> T;
while (T--) {
solve();
}
}
另一种做法, 按照 \(f(i,j)\) 求解方法补足 \(f(i,j)(i<j)\) 的值, 发现最后有 \(f(0,i)=2\times i\)(没有实际意义), 然后一样套用杨辉三角的方法.
void solve() {
ll n, m, k;
cin >> n >> m >> k;
ll ans = 0;
inc(i, 0, m) ans = (ans + i * 2 * C(n, m - i)) % mod;
ans = ans * inv(ksm(2, n)) % mod;
cout << ans * k % mod << "\n";
}