比赛链接:
https://codeforces.com/gym/103931
A. Another A+B Problem
题意:
给定一个等式和等式每个位置对应的颜色。
如果颜色是绿色,那答案的这一位也要是这个字符。
如果是紫色,那答案要有这个字符,但是不能在这一位上。
如果是黑色,那答案要不能有这个字符或者其它颜色的该字符都要在字符串中出现。
输出所有可能的等式及数量。
思路:
因为数量很小,直接暴力跑所有的结果,判断等式是否符合要求。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
string E, C;
cin >> E >> C;
map<char, int> B, P;
for (int i = 0; i < 8; i ++ ){
if (C[i] == 'B') B[E[i]] ++ ;
if (C[i] == 'P') P[E[i]] ++ ;
}
auto check = [&](string s){
map<char, int> cnt;
for (int i = 0; i < 8; i ++ ){
if (s[i] == E[i]){
if (C[i] != 'G')
return false;
}
else {
if (C[i] == 'G')
return false;
cnt[s[i]] ++ ;
}
}
for (char c = '0'; c <= '9'; c ++ ){
if (B[c] && P[c] != cnt[c]) return false;
if (!B[c] && P[c] > cnt[c]) return false;
}
return true;
};
vector <string> f;
for (int i = 0; i <= 99; i ++ ){
for (int j = 0; j <= 99; j ++ ){
if (i + j >= 100) continue;
string t = "";
t += '0' + i / 10;
t += '0' + i % 10;
t += '+';
t += '0' + j / 10;
t += '0' + j % 10;
t += '=';
t += '0' + (i + j) / 10;
t += '0' + (i + j) % 10;
if (check(t)) f.push_back(t);
}
}
cout << f.size() << "\n";
for (auto x : f)
cout << x << "\n";
return 0;
}
E. Expenditure Reduction
题意:
给定两个字符串 \(s\) 和 \(f\),找到 \(s\) 最短的一个子串,使 \(f\) 是它的子序列。
思路:
brute force
直接预处理每个字母往后跳到哪里,即这个字母下一次出现在哪里,然后暴力维护即可。
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
map <char, int> Map;
void init(){
int cnt = 0;
for (char c = 'a'; c <= 'z'; c ++ )
Map[c] = ++ cnt;
for (char c = '0'; c <= '9'; c ++ )
Map[c] = ++ cnt;
}
void solve(){
string s, f;
cin >> s >> f;
int n = s.size(), m = f.size();
vector < vector <int> > to(37, vector <int>(n + 1, n));
vector <int> pos(37, n);
for (int i = n - 1; i >= 0; i -- ){
to[Map[s[i]]][i] = pos[Map[s[i]]];
pos[Map[s[i]]] = i;
}
vector <int> now(m);
int len = 1e9, p = 0;
for (int i = 0, j = 0; i < n; i ++ ){
if (s[i] == f[j]){
now[j] = i;
for (int k = j - 1; k >= 0; k -- ){
while(to[Map[f[k]]][now[k]] < now[k + 1]){
now[k] = to[Map[f[k]]][now[k]];
}
}
if (j == m - 1){
if (i - now[0] + 1 < len){
len = i - now[0] + 1;
p = now[0];
}
}
else j ++ ;
}
}
cout << s.substr(p, len) << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
init();
int T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
dp
设 \(dp_{i, j}\) 表示 \(s\) 以第 \(i\) 个字母结尾,\(f\) 的第 \(j\) 个字母结尾的子序列最早在 \(s\) 的哪里出现过。
那么 \(i - dp_{i, m - 1}\) 就是最终字符串的长度,找到最小的就行。
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
void solve(){
string s, f;
cin >> s >> f;
int n = s.size(), m = f.size();
vector<vector<int>> dp(n, vector<int>(m, -1));
for (int i = 0; i < n; i ++ ){
if (s[i] == f[0]) dp[i][0] = i;
if (i != 0){
for (int j = 0; j < m; j ++ ){
if (j != 0 && s[i] == f[j])
dp[i][j] = max(dp[i][j], dp[i - 1][j - 1]);
dp[i][j] = max(dp[i][j], dp[i - 1][j]);
}
}
}
int L = 0, R = n - 1;
for (int i = 0; i < n; i ++ ){
if (dp[i][m - 1] != -1 && i - dp[i][m - 1] < R - L){
L = dp[i][m - 1];
R = i;
}
}
cout << s.substr(L, R - L + 1) << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
G. Gua!
题意:
玩家在 \(S\) 秒内用枪造成了 \(D\) 点伤害,已知枪每发的最大伤害为 \(B\),\(RPM\)(每分钟射速)为 \(R\),问这人是不是开挂。
思路:
得到射速为 \(R * S / 60 + 1\),因为打一枪需要等 \(\frac{1}{R}\) 分钟,所以最多可以多打一枪,但是这要建立在 \(R > 0\) 的基础上。判断一下造成伤害是否大于这个值即可。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
void solve(){
int B, R, D, S;
cin >> B >> R >> D >> S;
if (D > (R * S / 60 + 1 * (R > 0)) * B) cout << "gua!\n";
else cout << "ok\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
H. Heirloom Painting
题意:
长为 \(n\) 的环,颜色从 1 到 \(m\),每次可以涂连续的 \(k\) 个格子使其变成一个颜色,问最少几步能涂成题目所给的那样的环。
思路:
最后一步肯定将连续的 \(k\) 个格子涂成一个颜色了,所以先找有没有连续的 \(k\) 个及以上的格子。因为是环,要将后面与开头相同的数字放到开头先。
对于一段长为 \(len\) 的区间,最少花 \(\lceil \frac{len}{k} \rceil\) 步,由此可以计算出答案。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
void solve(){
int n, m, k;
cin >> n >> m >> k;
vector <int> c(n);
for (int i = 0; i < n; i ++ )
cin >> c[i];
int R = n - 1;
vector <int> a;
for (; R >= 0 && c[R] == c[0]; R -- )
a.push_back(c[R]);
for (int i = 0; i <= R; i ++ )
a.push_back(c[i]);
vector <int> len;
for (int i = 0; i < n; i ++ ){
int j = i + 1;
while(j < n && a[j] == a[j - 1]){
j ++ ;
}
len.push_back(j - i);
i = j - 1;
}
if (*max_element(len.begin(), len.end()) >= k){
LL ans = 0;
for (auto x : len)
ans += (x + k - 1) / k;
cout << ans << "\n";
}
else{
cout << "-1\n";
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T = 1;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
I. It Takes Two of Two
题意:
有 \(n\) 个点,每次随机选取两个点进行连接,当该边满足条件时才会被添加进图。
1.图中无重边和自环。
2.每个点的度不超过 2。
问不能再添加边时的添加次数期望。
思路:
考虑可以添加的情况,只有单独的点和链,链又可分为两类,长为 1,以及长度大于 1 的(可以头尾相连,变成环)。记录三者的数量,进行转移。
当前这种情况的期望即 \(\frac{所有子情况的期望和 + 1}{当前情况的概率}\)。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 210;
double dp[N][N][N];
bool vis[N][N][N];
int n;
double dfs(int i, int j, int k){
if (vis[i][j][k]) return dp[i][j][k];
if (i == 0 && j == 0 && k == 0) return dp[i][j][k] = 0;
if (i == 1 && j == 0 && k == 0) return dp[i][j][k] = 0;
if (i == 0 && j == 1 && k == 0) return dp[i][j][k] = 0;
double ans = 0, sum = 0;
if (i >= 2){ //点和点
double p = 1.0 * i * (i - 1) / (n * n);
sum += p;
ans += dfs(i - 2, j + 1, k) * p;
}
if (i >= 1 && j >= 1){ //点和长度为 1 的链
double p = 2.0 * i * 2 * j / (n * n);
sum += p;
ans += dfs(i - 1, j - 1, k + 1) * p;
}
if (i >= 1 && k >= 1){ //长度为 1 的链和长大于 1 的链
double p = 2.0 * i * 2 * k / (n * n);
sum += p;
ans += dfs(i - 1, j, k) * p;
}
if (j >= 2){ //两条短链合成长链
double p = 1.0 * 2 * j * (2 * j - 2) / (n * n);
sum += p;
ans += dfs(i, j - 2, k + 1) * p;
}
if (j >= 1 && k >= 1){ //短链和长链合成长链
double p = 2.0 * 2 * j * 2 * k / (n * n);
sum += p;
ans += dfs(i, j - 1, k) * p;
}
if (k >= 2){ //两条长链合成
double p = 1.0 * 2 * k * 2 * (k - 1) / (n * n);
sum += p;
ans += dfs(i, j, k - 1) * p;
}
if (k >= 1){ //形成环
double p = 1.0 * 2 * k / (n * n);
sum += p;
ans += dfs(i, j, k - 1) * p;
}
vis[i][j][k] = true;
return dp[i][j][k] = (ans + 1) / sum;
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> n;
cout << fixed << setprecision(9) << dfs(n, 0, 0) << "\n";
return 0;
}
L. Last Warning of the Competition Finance Officer
题意:
给定一个字符串 \(s\),一个包含 \(n\) 个字符串的字典,字典中的每个字符串有一个权值,求出 \(s\) 的每个前缀的权值。
一个字符串的权值为它所有组合的权值之和。
一个组合中,每个字符串都要在字典中,或者为空。空串权值为 1。
思路:
定义 \(dp_i\) 为以第 \(i\) 个字符结尾的字符串的权重。容易得到 \(dp_i = \sum_{j = 1}^{n} dp_{i - len_j} * w_j\)。
直接转移显然超时,想到通过AC自动机进行转移,将字典中的字符串都放到自动机中然后对模式串进行匹配。
但在 \(fail\) 树上跑每个子串数量太大了,只需要匹配有权值的答案,所有要让每一个串向存在的串进行转移。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mod = 998244353, N = 2e5 + 10;
struct ACA{
LL ch[N][26], fail[N], idx = 0, len[N], w[N], f[N], id[N], Next[N];
void insert(string s, LL val){
LL u = 0;
for (int i = 0; i < s.size(); i ++ ){
LL v = s[i] - 'a';
if (!ch[u][v]) ch[u][v] = ++ idx;
u = ch[u][v];
}
w[u] = val;
len[u] = s.size();
}
void get_fail(){
iota(id, id + idx + 1, 0);
queue <LL> q;
for (int i = 0; i < 26; i ++ ){
if (ch[0][i]){
fail[ch[0][i]] = 0;
q.push(ch[0][i]);
}
}
while (q.size()){
LL u = q.front();
q.pop();
Next[u] = id[fail[u]];
if (!w[u]) id[u] = Next[u]; //向有权值的点去跳
for (int v = 0; v < 26; v ++ ){
if (ch[u][v]){
fail[ch[u][v]] = ch[fail[u]][v];
q.push(ch[u][v]);
}
else ch[u][v] = ch[fail[u]][v];
}
}
}
void query(string t){
LL u = 0;
f[0] = 1;
for (int i = 0; i < t.size(); i ++ ){
u = ch[u][t[i] - 'a'];
f[i + 1] = f[i];
for (LL v = id[u]; w[v]; v = Next[v]){
f[i + 1] = (f[i + 1] + f[i + 1 - len[v]] * w[v] % mod) % mod;
}
}
for (int i = 1; i <= t.size(); i ++ )
cout << f[i] << " \n"[i == t.size()];
}
}aca;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
string s;
cin >> s;
int n;
cin >> n;
for (int i = 0; i < n; i ++ ){
string t;
LL val;
cin >> t >> val;
aca.insert(t, val);
}
aca.get_fail();
aca.query(s);
return 0;
}
M. My University Is Better Than Yours
题意:
给定 \(m\) 个 \(n\) 个大学的排行榜,从左向右由高到低,问每个大学比多少个大学的排名高。
思路:
如果遍历每个排行榜的前 \(i\) 个,记出现的大学数量为 \(k\),当 \(k = i\) 时,说明前面只有这些大学,它们的排名都比后面所有的大学高,由此可以记录答案。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n, m;
cin >> n >> m;
vector<vector<int>> s(m, vector<int>(n));
for (int i = 0; i < m; i ++ )
for (int j = 0; j < n; j ++ )
cin >> s[i][j];
vector<bool> vis(n + 1);
vector<int> ans(n + 1);
queue<int> q;
for (int j = 0, num = 0, res = 1; j < n; j ++ ){
for (int i = 0; i < m; i ++ ){
if (!vis[s[i][j]]){
vis[s[i][j]] = true;
q.push(s[i][j]);
num ++ ;
}
}
if (num == j + 1){
while(!q.empty()){
int x = q.front();
q.pop();
ans[x] = n - res;
}
res = num + 1;
}
}
for (int i = 1; i <= n; i ++ )
cout << ans[i] << " \n"[i == n];
return 0;
}
N. Nine Is Greater Than Ten
题意:
比较给定字符串大小。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
string a, b;
cin >> a >> b;
if (a > b) cout << a << ">" << b << "\n";
else if (a == b) cout << a << "=" << b << "\n";
else cout << a << "<" << b << "\n";
return 0;
}