本蒟蒻该阶段应该顶多处理这几题了,希望以后还能补出更多的题!
补题顺序
- [I. Elevator](https://codeforces.com/gym/104396/problem/I)
- [H.Neil's Machine](https://codeforces.com/gym/104396/problem/H)
- [J.Similarity (Easy Version)](https://codeforces.com/gym/104396/problem/J)
- [K.Similarity (Hard Version)](https://codeforces.com/gym/104396/problem/K)
- [A.Today's Word](https://codeforces.com/gym/104396/problem/A)
- [F. Timaeus](https://codeforces.com/gym/104396/problem/F)
I. Elevator
签到题
题面解读
一个电梯里有n个人,选择了m个不同的楼层,询问最多可能有多少人在同一楼层。
分析
显然,当其他 m − 1 m-1 m−1 个人在选择其他楼层时,剩下那个楼层的人一定是最多的。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
int n, m;
void solve()
{
cin >> n >> m;
cout << n - (m - 1) << "\n";
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0);
int t = 1;
cin >> t;
while(t--) solve();
return 0;
}
H.Neil’s Machine
题面解读
将字符串 S S S 转变成 T T T 需要的操作次数,每次操作是将字符串的后缀子串全部 + K +K +K,其中 1 < = K < = 25 1 <= K <= 25 1<=K<=25。
分析
可以看出,每次操作必须先将前部分全部转为与目标串
T
T
T 一致;否则,如果先改后面再改前面,会使得改前部分时后缀部分再次被修改。
所以我们从前往后遍历即可,使用标记记录前方已经修改了多少,逐位比较当前是否已经被修改与目标串一致。如果根据前部分的修改,当前位置已经与目标串一致,则无需操作;否则,执行一次后缀修改。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int n, arr[N], modify = 0, res = 0;
string s, t;
int main()
{
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> s >> t;
int tag = -1;
for(int i =0; i < n; ++i)
{
arr[i] = t[i] - s[i];
if(tag == -1 && arr[i] != 0) tag = i;
}
if(tag == -1)
{
cout << 0 << endl; // 说明目标串与初始串完全相同
return 0;
}
for(int i = tag; i < n; ++i)
{
int temp = (arr[i] % 26 + 26) % 26;
if(temp == modify % 26) continue;
res ++;
modify += ((temp - modify) % 26 + 26) % 26; // 记录前面已经修改了多少
}
cout << res;
}
J.Similarity (Easy Version)
动态规划,最长公共子串
题面解读
对于每组测试数据,给出 n n n 个字符串,比较这 n n n 个字符串的最长公共子串的长度。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 55;
int n;
string str[N];
int check(string str1, string str2)
{
int len1 = str1.length(), len2 = str2.length();
int start = 0, mx = 0;
vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
for(int i = 1;i<=len1;i++){
for(int j = 1;j<=len2;j++){
if(str1[i-1] == str2[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
if(dp[i][j] > mx){
mx = dp[i][j];
start = i - mx;
}
}
}
// cout << str1.substr(start,mx) << "\n";
return mx;
}
void solve()
{
cin >> n;
for(int i = 1; i <= n; ++i) cin >> str[i];
int mx = 0;
for(int i = 1; i <= n; ++i)
for(int j = i + 1; j <= n; ++j)
{
// 比较第i和j个
mx = max(mx, check(str[i], str[j]));
}
cout << mx << "\n";
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0);
int t = 1;
cin >> t;
while(t--) solve();
return 0;
}
K.Similarity (Hard Version)
构造题
题面解读
构造出 n n n 个长度为 k k k 的最长公共子串为 m m m 的字符串,每个字符串不能一样。
分析
首先,排除无法构造出来的情况。如果,最长公共子串长度为
0
0
0 且需要的个数大于26,或者最长的长度大于等于其本身的长度
k
k
k。
第一个可以构造一个全为
a
a
a 的字符串,第二个构造一个与第一个相似度刚好为
m
m
m 的字符串,后面全跟
b
b
b。
随后,构造最长公共子串为 1 1 1 的字符串。形如 a c a c , . . . , a z a z , b c b c , . . . , b z b z , . . . , y z y z acac,...,azaz,bcbc,...,bzbz, ...,yzyz acac,...,azaz,bcbc,...,bzbz,...,yzyz。
参考代码
#include<bits/stdc++.h>
using namespace std;
int n, m, k;
string s1, s2, s;
void solve()
{
cin >> n >> m >> k;
if((m == 0 && n > 26) || m >= k)
{
cout << "No\n";
return;
}
cout << "Yes\n";
if(m == 0)
{
// 相似度为0,则按序输出即可全a,全b...即可
for(int i = 0; i < n; ++i)
{
for(int j = 0; j < k; ++j)
{
cout << char('a' + i);
}
cout << "\n";
}
return;
}
for(int i = 1; i <= k; ++i) s1 += 'a';
s2 = s1.substr(0, m);
for(int i = m + 1; i <= k; ++i) s2 += 'b';
cout << s1 << "\n" << s2 << "\n";
// 构造一个abab...abab,这个不可采用
// 按照之前构造,ab字段与上面相似度至少为2,如果m == 1,则不符合要求.现在需要构造一个相似度最多为1的字符串
for(int i = 0; i < k; ++i) s += s1[i];
for(int i = 1; i <= k; i += 2) s[i] = char(s1[i] + 1);
// 构造形如acac,...,azaz,bcbc,...,yzyz的字符串
int cnt = 3;
while(cnt <= n)
{
if(s[1] == 'z')
{
for(int i = 1; i < k; i += 2) s[i] = s[0] + 1;
for(int i = 0; i < k; i += 2) s[i] += 1;
}
for(int i = 1; i < k; i += 2) s[i] += 1;
cout << s << "\n";
cnt++;
}
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0);
solve();
}
A.Today’s Word
题面解读
将一个字符串分成前后两部分,每次前半部分保持不动,将后部分执行一个next操作,中间插入一个操作前的字符串。询问执行了 1 0 100 10^{100} 10100 之后,长度为 m m m 的后缀字符串是什么。
分析
因为模拟的次数足够大,所以最后主要就是看next()处理的后缀字符串。
由于next操作是将字母+1,所以会是一个以26为周期的循环,我们先快速幂求一下
1
0
100
m
o
d
26
10^{100} mod 26
10100mod26 的结果为
16
16
16。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
ll ksm(ll a, ll b, ll mod)
{
ll res = 1;
while(b)
{
if(b & 1) res = (res * a) % mod;
b >>= 1;
a = a * a % mod;
}
return res % mod;
} // 10e100 % 26 = 16
int n, m;
string str;
string next(string s)
{
string ret;
for(int i = 0; i < s.length(); ++i)
ret += ((char)('a' + (s[i] - 'a' + 1) % 26));
return ret;
}
void solve()
{
cin >> n >> m >> str;
str = str.substr(str.length() / 2);
int op = 0;
while(str.length() < m) str = str + next(str), op++;
str = str.substr(str.length() - m);
op = (16 - op + 26) % 26;
while(op--) str = next(str);
cout << str << "\n";
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0);
int t = 1;
// cin >> t;
while(t--) solve();
return 0;
}
F. Timaeus
题面解读
总共有 A A A 个常规材料,每次可以将 B B B 个常规材料合成为一个高级材料,现在有两种buff,每次合成只能使用一个:
- 每次可以合成出两个高级材料,概率为 P P P
- 合成一个高级材料返还一个常规材料,概率为 Q Q Q
需要最大化合成出的高级材料的数量期望。
分析
此题考虑使用动态规划进行一个概率期望计算,线性判断使用到第 i i i 个常规材料时的最大期望数量。其中,通过状态转移方程,可以发现,当每次合成只消耗一个时,需要特判。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e6 + 5;
int a, b;
double p, q, dp[N];
void solve()
{
cin >> a >> b >> p >> q;
p = p / 100, q = q / 100;
for(int i = b; i <= a; ++i)
if(b!=1) dp[i] = max(p * (dp[i - b] + 2) + (1 - p) * (dp[i - b] + 1), q * (dp[i - b + 1] + 1) + (1 - q) * (dp[i - b] + 1));
else dp[i] = max(p * (dp[i - b] + 2) + (1 - p) * (dp[i - b] + 1), 1 / (1 - q) + dp[i - b]);
printf("%.9lf\n", dp[a]);
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0);
int t = 1;
// cin >> t;
while(t--) solve();
return 0;
}