AtCoder Beginner Contest 285
A - Edge Checker 2 (abc285 a)
题目大意
给定如下一棵树。
给定 \(a,b(a<b)\),问两者是否有连边。
解题思路
观察数可发现其为二叉树,两者有连边当且仅当\(b=2a\)或 \(b=2a+1\)
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int a, b;
cin >> a >> b;
if (a * 2 != b && a * 2 + 1 != b)
cout << "No" << '\n';
else
cout << "Yes" << '\n';
return 0;
}
B - Longest Uncommon Prefix (abc285 b)
题目大意
给定一个长度为\(n\)字符串\(s\),对于每个\(i \in [1, n - 1]\) ,找到最大的\(l\)使得
- \(i + l \leq N\)
- \(\forall k \in [1, l], s_k \neq s_{k + i}\)
解题思路
数据范围不大,暴力即可。
即对于每个\(i\), 枚举 \(l\)从 \(1\)到第一个 \(s_l \neq s_{k+l}\)为止。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n;
string s;
cin >> n >> s;
auto solve = [&](int x){
int ans = 0;
while(ans < n - x){
if (s[ans] == s[ans + x])
break;
++ ans;
}
return ans;
};
for(int i = 1; i < n; ++ i){
cout << solve(i) << '\n';
}
return 0;
}
C - abc285_brutmhyhiizp (abc285 c)
题目大意
有一堆字符串,其字典序顺序为:
- 'a', 'b', ..., 'z', 'aa', 'ab', ..., 'az', 'ba', 'bb', ..., 'bz', ..., 'za', 'zb', ..., 'zz', 'aaa', 'aab', ..., 'aaz', 'aba', 'abb', ..., 'abz', ..., 'zzz', 'aaaa', ...
现给定其中一个字符串,问其字典序大小。
解题思路
这题是abc171 c的反过来形式。
每个位置,以a
-z
的26为一循环,但第一次循环时还有一个0
(a
可以看成是0a
)。所以 a
应当看成数字\(1\)。
因此高位转低位时,进位时\(26\),但末尾数字减去'A'后还要加一。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
string s;
cin >> s;
LL ans = 0;
for(auto &i : s){
ans = ans * 26 + (i - 'A' + 1);
}
cout << ans << '\n';
return 0;
}
D - Change Usernames (abc285 d)
题目大意
给定\(n\)对字符串 \(s_i,t_i\)。表示有 \(n\)个人,原本叫 \(s_i\),现在需要改名为 \(t_i\)。
你现在要处理该请求,每次只能改一个人的名,问改名过程中会不会出现重名,即一个人改成\(t_i\)了,但还有另一个还没改名的 \(s_j = t_i\)
解题思路
字符串\(s_i\)改成 \(t_i\),则从 \(s_i\)连一条有向边到 \(t_i\)。
如果存在一个环,则改名过程必定重名。
有向边判环的方法即拓扑排序。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n;
cin >> n;
vector<string> s(n), t(n);
set<string> qwq;
map<string, int> tr;
for(int i = 0; i < n; ++ i){
cin >> s[i] >> t[i];
qwq.insert(s[i]);
qwq.insert(t[i]);
}
int cnt = 0;
for(auto &i : qwq){
tr[i] = cnt;
++ cnt;
}
vector<int> du(cnt);
vector<vector<int>> edge(cnt);
for(int i = 0; i < n; ++ i){
int u = tr[s[i]];
int v = tr[t[i]];
edge[u].push_back(v);
du[v] ++;
}
queue<int> team;
for(int i = 0; i < cnt; ++ i){
if (du[i] == 0)
team.push(i);
}
while(!team.empty()){
auto u = team.front();
-- cnt;
team.pop();
for(auto &v : edge[u]){
du[v] --;
if (du[v] == 0)
team.push(v);
}
}
if (cnt)
cout << "No" << '\n';
else
cout << "Yes" << '\n';
return 0;
}
E - Work or Rest (abc285 e)
题目大意
一周有\(n\)天。 给定一个长度为\(n\)的数组 \(a\)。
你需要指定 至少一天为休息日,其余为工作日。要求最大化工作日的产能。
一个工作日的产能定义为\(a_\min(x,y)\),其中 \(x\)是该日距离过去最近的休息日的天数, \(y\)是该日距离未来最近的休息日天数。注意周与周之间是连贯的,第一周的第一天的过去可以是上一周。
解题思路
如果不考虑连贯的影响,即认为一周是独立的,可以设\(dp[i]\)表示第 \(i\)天为休息日的情况下最大的产生值。转移时枚举上一次休息日的天数 \(j\)。
观察 \(j|j+1,j+2,...,i-1|i\),中间 \(j+1\)到 \(i-1\)的产能值,即为 \(a_1,a_2,...,a_2,a_1\),即下标是一个先递增后递减的规律,就是一个 \(a\)的前缀和的两倍,根据奇偶性可能多出一个\(a_i\)。这样转移可以 \(O(1)\)。复杂度就是 \(O(n^2)\)
但是一周并不是独立的,这样的方程具有后效性,归根结底就在于我们不知道一周第一次的休息日的日子,无法算出该日子之前和最后一个休息日之后的这几天的产能贡献。但如果设\(dp[i][j]\)表示第一次休息日在第 \(i\)天,然后在第 \(j\)天也是休息日的最大产能,其复杂度为 \(O(n^3)\)。
但是,注意到天与天之间没有区别的,意思就是说,如果我们确定了第一次休息日的天数\(a\),以及最后一次休息日的天数\(b\),那这两个休息日之间,里面休息日任意选,获得的产生最大值,其实就是 \(dp[b-a]\)。
因此我们预处理 \(dp[i]\),然后枚举第一次休息日\(a\)和最后一次休息日 \(b\)所在天数,那答案 \(dp[b-a]\)加上前 \(a\)天和后 \(b\)天工作日的产能贡献了。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n;
cin >> n;
vector<LL> a(n);
for(auto &i : a)
cin >> i;
vector<LL> sum(n);
partial_sum(a.begin(), a.end(), sum.begin());
vector<LL> dp(n + 1, 0);
auto solve = [&](int len){
int dig = (len & 1);
int half = (len >> 1);
LL tmp = 0;
if (half)
tmp += 2ll * sum[half - 1];
if (dig)
tmp += a[half];
return tmp;
};
for(int i = 1; i <= n; ++ i){
for(int j = 0; j < i; ++ j){
dp[i] = max(dp[i], dp[j] + solve(i - j - 1));
}
}
LL ans = 0;
for(int i = 0; i < n; ++ i)
for(int j = i; j < n; ++ j){
ans = max(ans, dp[j - i] + solve(i + n - j - 1));
}
cout << ans << '\n';
return 0;
}
F - Substring of Sorted String (abc285 f)
题目大意
给定一个字符串\(s\),定义字符串 \(t\)为串 \(s\)各字母从小到大排序的字符串。进行 \(q\)次操作,操作分两种:
1 x c
,令 \(s_x = c\)2 x y
,问\(s[x..y]\)是不是 串\(t\)的一个子串
解题思路
对于询问,其等价于:串\(a\)是串 \(t\)的子串,说明
- 其串是递增的
- 除首字母和末字母之外,中间的字母出现次数都是串 \(s\)中出现的次数
初步想法是预处理出\(l[i],r[i]\)数组表示第 \(i\)个字母的左边、右边第一个非该字母的位置。
对于一个询问 \(2\),\(s[r[x], l[y]]\)就是中间字母,其长度和各字母的出现次数都必须符合上述要求,也就是说这段字符串必须是确定的某一个,可以用字符串hash来判断,而由于操作一涉及到修改(该确定的字符串的hash可以在\(O(26)\)的复杂度计算出),因此需要用线段树hash的方式维护其hash值。
同时\(l[i],r[i]\)数组同样也要维护,考虑操作一的影响,这涉及到区间修改,需要用线段树维护,但逻辑稍微复杂,写起来比较麻烦。
神奇的代码
G - Tatami (abc285 g)
题目大意
给定一个\(h \times w\)的网格,格子上有 1 2 ?
这三个符号,要求用\(1\times 1\)和 \(1\times 2\)(可旋转)的矩形覆盖。其中 1
的格必须被 \(1\times 1\)的格子覆盖, 2
的格必须被\(1 \times 2\)的格子覆盖, ?
的则随意。
问是否存在一种方案。
解题思路
<++>
神奇的代码
Ex - Avoid Square Number (abc285 h)
题目大意
给定\(n\)和一个长度为 \(k\)的序列 \(E\),要求求一个长度为 \(n\)的正整数序列的数量,满足:
- 所有元素都不是平方数
- 所有元素的乘积为 \(\prod_{i=1}^{K}p_i^{E_i}\)
其中 \(p_i\)是第 \(i\)小的质数。
解题思路
<++>
神奇的代码