Codeforces Round #692 (Div. 2, based on Technocup 2021 Elimination Round 3)
本来以为会掉分,结果竟然涨了
A - In-game Chat
签到题
给定只含')'和小写字母的字符串,问结尾的')'数量是否比剩余的字符数量多
签到题,直接从后往前扫就行
B - Fair Numbers
思维
求大于等于\(n\)的能被每一位数整除的最小的数
如果一个数能被\(a,b\)整除,那么他一定也能被\(lcm(a,b)\)整除
\(1,2,...,9\)的最小公倍数是\(2520\),最小的公平数一定小于等于\(1,2,...,9\)都出现的公平数,所以公平数和\(n\)直接差距很小,直接枚举即可
这题浪费了比较长时间,一开始想了个假贪心,后来忽略除数为\(0\)的情况了
while (x) {
if (x % 10 == 0) { x /= 10; continue; }//!!!
if (y % (x % 10)) return 0;
x /= 10;
}
C - Peaceful Rooks
思维+图论
给定\(n*n\)的网格,有\(m\)个马\((m < n)\),每个马能垂直或者水平移动任意距离,马之间不能互相攻击
问最少多少步能把所有马都移动到主对角线\((i,i)\)
比赛时只想如果相互攻击,就可以想把一个移动到空闲位置,然后剩下的那个移动到对角线,空闲位置的再移动,但是并没有想到怎么实现
我们不妨建立图看一下,位置(x,y)对应图中边(x,y),根据题目的性质,每个点最多只有一条出边
如果出现了环,就说明这几个能互相攻击,我们都需要多一步去把其中一个先移动到空闲位置,也就是说环的步数比正常路径多了\(1\)
所以本题只需要建图找环就行了。
参考代码,用的并查集。
int T, n, m;
int f[N];
int find(int x){
if(f[x] == x) return x;
else return f[x] = find(f[x]);
}
int main()
{
T = read();
while(T--){
int ans = 0;
n = read(), m = read();
for(int i = 1; i <= n; ++i) f[i] = i;
for(int i = 1; i <= m; ++i){
int x = read(), y = read();
if(x == y) continue;
ans++;
int fx = find(x), fy = find(y);
if(fx == fy) ans++;//环
else f[fx] = fy;
}
cout << ans << '\n';
}
return 0;
}
本题提供了一中二维坐标系建图的思路
以前我只遇到过一次这种二维坐标系建图的题目,这一次完全没想到,要长点记性
D - Grime Zoo
思维+贪心
给你一个由\(‘0’\) 和\(‘1’\)和 \(‘?‘\)组成,其中有一个\(01\)子序列就会产生\(x\)点愤怒值。每有一个\(10\)子序列就会产生\(y\)点愤怒值。其中问号\(’?'\)可以变成\(0\)或者是\(1\)。问愤怒值的和最小为多少。
这题给我的第一感觉是\(dp\),可惜我只想到一个空间复杂度是\(n^2\)的\(dp\),数组开不下,竟然没想到贪心.....
接下来就说一下贪心的思路吧,这题应该有很多思路,不过我就看懂了贪心(最好想)
显然\(x\)和\(y\)哪个大,我们就尽量要哪个多,详细地来说就是:
如果\(x>y\),我们就让\(10\)尽量多,就把\(1\)尽量往前填。
如果\(x<y\),我们就让\(01\)尽量多,就把\(1\)尽量往后填。
具体实现是可以通过前缀和和后缀和加速。
代码参考网上的题解(我不会写
const int N = 1e5 + 2020;
char str[N];
int x, y, st[N][3], ed[N][3];
vector<int> g;
signed main()
{
scanf("%s", str + 1);
scanf("%lld%lld", &x, &y);
int len = strlen(str + 1);
for(int i = 1; i <= len; ++i){
st[i][0] = st[i - 1][0];
st[i][1] = st[i - 1][1];
st[i][2] = st[i - 1][2];
if(str[i] == '0') st[i][0]++;
else if(str[i] == '1') st[i][1]++;
else{
st[i][2]++;
g.push_back(i);
}
}
for(int i = len; i >= 1; --i){
ed[i][0] = ed[i + 1][0];
ed[i][1] = ed[i + 1][1];
ed[i][2] = ed[i + 1][2];
if(str[i] == '0') ed[i][0]++;
else if(str[i] == '1') ed[i][1]++;
else ed[i][2]++;
}
int num0 = 0, num1 = 0;
int ans = 0;
for(int i = 1; i <= len; ++i){
if(str[i] == '0'){
ans += num1 * y;
num0++;
}
else {//此处默认?是1,注意后面把?修改成0时要先减去此时多算的部分
ans += num0 * x;
num1++;
}
}
int res = ans;
if(x < y) //10更大
{
for(int i = 0; i < g.size(); ++i){
int pos = g[i];
res = res - (st[pos - 1][0] + st[pos - 1][2]) * x - ed[pos + 1][0] * y;
res = res + st[pos - 1][1] * y + (ed[pos + 1][1] + ed[pos + 1][2]) * x;
ans = min(ans, res);
}
}
else {
for(int i = g.size() - 1; i >= 0; --i){
int pos = g[i];
res = res - st[pos - 1][0] * x - (ed[pos + 1][0] + ed[pos + 1][2]) * y;
res = res + (st[pos - 1][1] + st[pos - 1][2]) * y + ed[pos + 1][1] * x;
ans = min(res, ans);
}
}
cout << ans;
return 0;
}
注意题目是要求怒气值最小,不是最大(大雾
E - Poman Numbers
贪心
给出一个长度为 \(n\) 的字符串,每个字符串实质上代表一个数字\(2^{pos(i)}\),现在需要寻找一种递归顺序,使得每个位置非负即正,递归规则如下:
\[f(S)=-f(S[1, m])+f(S[m+1,|S|]) \]其中\(m\)是\([l,r]\)中任意的一个位置,且每一步的\(m\)都可以独立选择
问能否公国某种递归顺序,使得整个序列之和为给定的值\(sum\)
结论:第\(n\)个位置的符号一定为正,第\(n−1\) 个位置的符号一定为负,其余 \(n − 2\) 个位置的符号随意
设最终我们得到的符号是\(a_1,a_2,a_3,...,a_n\)
-
\(n=2\)时,\(a_1=-1,a_2=1\)
-
\(n>2\)时
- 假设要使得左边第一个是\(-1\),递归处理\((-a_1)(a_2,a_3,...,a_n)\)即可
- 假设要使得左边第一个是\(1\),找到从左边开始的第一个非负数记为\(i\),满足\(a_{i-1}=1,a_i=-1\)则递归子问题\((-a_1,-a_2,...,-a_{i-1},-a_i)(a_{i+1},...,a_{n})\)即\(a_i\)最后一定是\(1\),且\(a_1.a_2,...a_{i-1}\)都能递归成正数
目前,问题就变为了前\(n-2\)个数得到剩余的数\(X\)(减去最后两个数),贪心选取即可。
const int N = 1e6 + 2020;
int f[N], n, cnt[N], T;
char s[N];
signed main()
{
f[0] = 1;
for(int i = 1; i <= 26; ++i) f[i] = f[i - 1] * 2;
n = read(), T = read();
scanf("%s", s + 1);
T -= f[s[n] - 'a'] - f[s[n - 1] - 'a'];
for(int i = 1; i <= n - 2; ++i) T += f[s[i] - 'a'], cnt[s[i] - 'a']++;
for(int i = 25; i >= 0; --i) {
while(cnt[i] && T >= 2 * f[i]){
T -= 2 * f[i];
cnt[i]--;
}
}
if(T) cout << "NO";
else cout << "YES";
return 0;
}
F - The Thorny Path
贪心
又是贪心,更难了.....