Codeforces Round 929 (Div. 3)
D. Turtle Tenacity: Continual Mods
链接:Problem - D - Codeforces
题意:
给定一个数组,可以改变该数组中任意元素的位置,从左 m o d mod mod 到右,若结果不为零则输出 y e s yes yes,否则输出 n o no no。
思路:
如果最小值出现的次数为一的话,那么最后的结果就不会为零,或者后面的数 m o d mod mod最小值不为零的话,结果也不会等于零
代码:
#include <iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int N = 1e5 + 10;
int a[N];
void solve()
{
int n ; cin >> n;
for(int i = 1 ; i <= n ; i++) cin >> a[i];
sort(a + 1 , a + n + 1);//先排序
if(a[1] != a[2])//如果最小值只出现一次,则直接输出“YES”
{
cout << "YES" << endl;
return;
}
else
{
for(int i = 2 ; i <= n ; i++)//寻找是否有值mod最小值不为零
if(a[i] % a[1] != 0)//如果有则输出“YES”
{
cout << "YES" << endl;
return;
}
}
cout << "NO" << endl;//如果没有则输出“NO”
return;
}
int main()
{
int t ; cin >> t;
while(t--) solve();
return 0;
}
E. Turtle vs. Rabbit Race: Optimal Trainings
链接:Problem - E - Codeforces
题意:
给定一个数组 a a a,数组长度为 n n n,有 q q q次询问。对于每个询问,会给定左边界 l l l和一个特定值 t a r g e t target target,然后求出最小的右边界 r r r使得 l l l到 r r r之间的数字之和与 t a r g e t target target相差最小。
思路:
前缀和 + 二分
- 用一个数组存储从 1 1 1到 n n n的前缀和
- 二分出小于 t a r g e t target target最大右边界 r r r
- 判断 r + 1 r + 1 r+1是不是比 r r r与 t a r g e t target target的差值更小,如果更小,则答案为 r + 1 r + 1 r+1,否则答案为 r r r
代码:
#include <iostream>
using namespace std;
#define ll long long
const int N = 1e5 + 10;
int a[N] , sum[N];
void solve()
{
int n ; cin >> n;
for(int i = 1 ; i <= n ; i++)
{
cin >> a[i] ;
sum[i] = sum[i - 1] + a[i];//前缀和
}
int q ; cin >> q;
while(q--)
{
int l , u;
cin >> l >> u;
int left = l , right = n;
while(left < right)//二分出最右边答案(二分右边界的模板)
{
int mid = (left + right + 1) >> 1;
if(sum[mid] - sum[l - 1] <= u) left = mid;
else right = mid - 1;
}
int r = right ;
if(r == n || u - (sum[r] - sum[l - 1]) < sum[r + 1] - sum[l - 1] - u)//判断右边界为r + 1是不是比右边界为r小
cout << r << ' ';
else
cout << r + 1 << ' ';
}
cout << endl;
}
int main()
{
int t ; cin >> t;
while(t--) solve();
return 0;
}
F. Turtle Mission: Robot and the Earthquake
链接:Problem - F - Codeforces
题意:
- 给定一个 n n n ∗ * ∗ m m m的二维数组,输入数据 0 0 0表示这个位置没有石头, 1 1 1表示这个位置有石头,一开始我们在左上角,然后要走到右下角(注意:编号是从 0 0 0开始,对于行来说,也就是 0 、 1 、 ⋯ 、 n − 1 0、1、\cdots、n-1 0、1、⋯、n−1)对于第一个位置和最后一列都没有石头
- 每过一秒钟石头会往上移动一个单位,对于行来说当 x = 0 x=0 x=0时,往上移一个单位就会到达最底层也就是 x = n − 1 x=n-1 x=n−1的位置,列是不能循环的
- 人只能往上、往右、往下走,当要走的位置有石头或者会碰到石头时,则不能往这边走
- 最后求人到达目的地最短的时间,如果无法到达目的地的话输出 − 1 -1 −1
思路:
小知识:signed main 和 int main 的区别?
算法:广度优先搜索(bfs)
- 因为石头每秒都会向上移动一个单位,所以我们可以看成石头不动,目的地每秒往下走一个单位,人的移动方式如下所示:
- 往下走:往下走两个格子
- 往上走:不动
- 往右走:往右下走
- 综上所述:人最好的方式是往右下走或者是往下走两个格子
- 当广度优先搜索完了之后,遍历最后一列,看看当前位置与目的地的差距(这个时候可以恢复成原来的走法,就是目的地不变,人往上走或者往下走),计算找到最小值即可
代码:
#include <bits/stdc++.h>
#define int long long//将int类型定义为long long
using namespace std;
typedef pair<int , int> PII;//定义一个存储数据的pair
const int N = 1e3 + 10;
int n , m;
int g[N][N];//存储图
int dis[N][N];//用来判断这个位置是否走过,并且可以记录走到这个地方的最小步数
void solve()
{
cin >> n >> m;
for(int i = 0 ; i < n ; i++)
for(int j = 0 ; j < m ; j++)
{
cin >> g[i][j];//输入图
dis[i][j] = -1;//将上次走过的路线清零
}
queue<PII> q;//定义队列,用来写bfs
q.emplace(0 , 0);//将第一个位置存进队列中
dis[0][0] = 0;//第一个位置的步数为零
while(q.size())//判断队列中是否还有元素,没有就说明整个图能走到的位置都走了
{
auto p = q.front();//把队列首个元素取出来
q.pop();//将队列第一个元素删除
int x = p.first , y = p.second;//将队列第一个元素用x,y坐标表示出来
if(!g[(x + 1) % n][y] && !g[(x + 2) % n][y] && dis[(x + 2) % n][y] == -1)//当向下走两格时,判断有没有石头或者下面第二格是否走过
{
dis[(x + 2) % n][y] = dis[x][y] + 1;//如果可以走,则下面两格的步数为之前步数加一
q.emplace((x + 2) % n , y);//并且将这个位置存进队列中
}
if(y + 1 < m && !g[(x + 1) % n][y + 1] && dis[(x + 1) % n][y + 1] == -1)//当向右下走时,判断有没有超出列的范围,并判断右下是否有石子,还要判断右下这个位置是否走过
{
dis[(x + 1) % n][y + 1] = dis[x][y] + 1;//如果可以走,则右下的格子的步数为之前步数加一
q.emplace((x + 1) % n , y + 1);//并且将这个位置存进队列中
}
}
int ans = 1e18;//定义最后走的步数(注意:前面将int定义为long long了,所以不会超出范围)
for(int i = 0 ; i < n ; i++)//主要是判断最后一列,然后恢复成原来的模样,终点不变,我们向上或者向下走
{
if(dis[i][m - 1] == -1) continue;//如果不能走到这个位置,就跳过
int zhong = (n - 1 + dis[i][m - 1]) % n;//可以走到这个位置,就用zhong来记录终点所在的行
ans = min(ans , dis[i][m - 1] + min((zhong - i + n) % n , (i - zhong + n) % n));//用来判断最短的步数
//1.前面的min是判断直线的最短路径和现在的最短路径哪个更短
//2.后面的min是判断最后向上走离终点近还是向下走离终点近
}
if(ans == 1e18)//判断ans是否发生变化
cout << -1 << endl;//如果不变,则表示最后不可能到达终点,输出'-1'
else
cout << ans << endl;//如果发生了变化,则表示可以到达终点,输出最小路径ans即可
}
signed main()//注意这里不能用int,会报错,可以去搜搜signed的用法
{
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
G. Turtle Magic: Royal Turtle Shell Pattern
链接: Problem - G - Codeforces
题意:
- 给定一个 n ∗ m n*m n∗m的表格,编号都是从 1 1 1开始,一共有 q q q次操作,对于每次操作,会给你三个数据,即:饼干的位置 ( x , y ) (x,y) (x,y)以及一个字符串也就是饼干的形状 s h a p e shape shape。
- 放的饼干不能存在连续的三个饼干是一样的形状,即:横着、竖着、斜着,这三个方位上不能存在连续三个相同形状的饼干
- 对于第 q q q次放,输出第 q − 1 q-1 q−1次时,还有多少种放法
思路:
- 用 0 0 0表示圆形饼干, 1 1 1表示方形饼干
- 一共有8种放法,横着和竖着各四种,如下所示:
- 0、0、1、1
- 0、1、1、0
- 1、1、0、0
- 1、0、0、1
- 我们可以定义一个数组 f a g [ 8 ] fag[8] fag[8]来存放这8种放法, 0 0 0表示这种放法可以存在, 1 1 1表示这种放法不能。其中第 0 0 0种放法和第 4 4 4种只是 ( x , y ) (x,y) (x,y)的位置相反(后面以此类推),所以当 i > = 4 i>=4 i>=4的时候就调换 ( x , y ) (x,y) (x,y)的位置 i i i变成 i − 4 i-4 i−4
- 对于第 0 0 0种放法和第 1 1 1种放法其实就是 y y y往右移了 i i i个长度,所以定义了一个 g e t ( ) get() get()函数,来求第 i i i种放法,在第 0 0 0种放法下 y y y所在的位置,然后求出这个位置下所放的时 0 0 0这种饼干还是 1 1 1这种饼干
- 对于第 q q q次放,都用一个循环来看,是不是在第 i i i种放法下不能成功,不能成功的话就 a n s − − ans-- ans−−,并且把这种放法标记成不能放,防止重复减
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
int get(int x , int y , int i)//get函数用来判断该饼干放的位置与第i种放法的相同位置放的形状是不是相同
{
if(i >= 4) return get(y , x , i - 4);//如果放的放法数是大于等于4次的(就是按照列来放),就可以把他换成横着的
y += i;//表示在这种放发下,所在原图上的位置
return (x & 1) ^ (y % 4 == 1 || y % 4 == 2);
//(x & 1) 判断是奇数行还是偶数行,因为奇数行和偶数行刚好相反
//异或上后面的数,因为一开始1、2号位上的数都为0,如果y向前折叠之后在这两个位置上就是1,然后1^1就是0
//即:在(x , y)这个位置上放饼干在第i中放法的位置上应该是什么样饼干
}
void solve()
{
int n , m , q;
cin >> n >> m >> q;
int ans = 8;//不管n,m多大,最多的次数都是8次
cout << ans << endl;//什么都没放的时候就是8次
int fag[8] = {0};//定义一个数组,用来存放这8中放发,哪种不能放(0:表示能放,1:表示不能放)
while(q--)//一共放q次,输出第q次放了之后,还有多少种放法
{
int x , y;
string s;
cin >> x >> y >> s;
int k = s[0] == 'c';//k用来记录放入的数是圆形还是方形
for(int i = 0 ; i < 8 ; i++)//8中方法
{
if(!fag[i] && get(x , y , i) != k)//判断这种方法之前是不是能放,并且判断这个位置上的数是不是与这种放法的数不相同
{
ans--;//如果不相同,则总共的放的数减减
fag[i] = 1;//并且把这种放法标记成不能放,防止重复减
}
}
cout << ans << endl;//最后输出第q次放了之后还有多少中放法
}
}
signed main()
{
int t;
cin >> t;
while(t--) solve();
return 0;
}