NewOJ周赛于2022年3月19日正式开始,比赛时间为每周六晚19:00-22:00。
比赛链接:http://oj.ecustacm.cn/contest.php?cid=1018
A 最大余数
题意: 给定一个长度为 n n n的数组 a a a,求最大余数 a [ i ] % a [ j ] a[i]\%a[j] a[i]%a[j],要求 i ≠ j i≠j i=j。
Tag: 思维题
难度: ☆
来源: C o d e C h e f E a s y CodeChef\ Easy CodeChef Easy
思路:
如果 a [ i ] > a [ j ] a[i]>a[j] a[i]>a[j], a [ i ] % a [ j ] < a [ j ] = m i n ( a [ i ] , a [ j ] ) a[i]\%a[j]<a[j]=min(a[i],a[j]) a[i]%a[j]<a[j]=min(a[i],a[j]);
如果 a [ i ] < a [ j ] a[i]<a[j] a[i]<a[j], a [ i ] % a [ j ] = a [ i ] = m i n ( a [ i ] , a [ j ] ) a[i]\%a[j]=a[i]=min(a[i],a[j]) a[i]%a[j]=a[i]=min(a[i],a[j]);
所以最优情况肯定要让 a [ i ] < a [ j ] a[i]<a[j] a[i]<a[j],这样 a [ i ] % a [ j ] a[i]\%a[j] a[i]%a[j]就可以取到最大值 a [ i ] a[i] a[i]。
所以最大余数 a [ i ] a[i] a[i]相当于 a a a数组中第二大的数字(注意去重)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int n, a[maxn];
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i++)
cin >> a[i];
sort(a + 1, a + 1 + n); //排序
for(int i = n; i >= 0; i--) //找去重后第二大的数字
{
if(a[i] != a[n])
{
cout<<a[i]<<endl;
break;
}
}
return 0;
}
B 减一
题意: 给定长度为 n n n的数组 a a a,每次可以选择相邻的两个数字进行减 1 1 1。最少执行多少次上述操作,使得所有数字都相同,不可以为负数。
Tag: 二分、数学
难度: ☆☆
来源: U S A C O 2022 J a n USACO\ 2022\ Jan USACO 2022 Jan
思路: 本题可以分为奇数长度和偶数长度分类思考。
对于偶数长度: 比较简单,如果最终可以变成 x , x , x , . . . , x x,x,x,...,x x,x,x,...,x,那么肯定可以变成 x − 1 , x − 1 , . . . , x − 1 x-1,x-1,...,x-1 x−1,x−1,...,x−1。因为是偶数长度,只需要继续每相邻两个减 1 1 1即可。这说明满足单调性,直接二分 x x x,每次判断是否能变成 x x x即可,然后找一个最大的 x x x。
对于奇数长度: 其实也满足二分的性质,我们假设最终全部变成 x x x,然后从左往右依次变成 x x x,模拟相邻两个数字减法的过程:
-
如果在模拟全部变成 x x x的过程中有数字小于 x x x,说明 x x x大了,需要调小。
-
如果前 n − 1 n-1 n−1个数字都可以减小到 x x x,那么就看最后一个数字:
-
最后一个数字等于 x x x,说明找到答案;
-
最后一个数字大于 x x x,说明 x x x小了;
-
最后一个数字小于 x x x,说明 x x x大了;
-
当然,本题也有 O ( n ) O(n) O(n)做法,留给读者思考。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int a[maxn], b[maxn], n;
bool judge(int x)///将数组a全部变成x
{
for(int i = 1; i <= n; i++)
b[i] = a[i];
for(int i = 1; i <= n; i++)
{
if(b[i] < x)return false;
//b[i]和b[i+1]都减去b[i]-x,这样b[i]就变成了x
if(i != n)b[i + 1] -= (b[i] - x), b[i] = x;
}
return true;
}
int main()
{
int T;
cin >> T;
while(T--)
{
cin >> n;
for(int i = 1; i <= n; i++)
cin >> a[i];
//二分最终的x
int left = 0, right = 1000000000, x = -1;
while(left <= right)
{
int mid = (left + right) / 2;
if(judge(mid))
{
//只有最终等于mid,才是合法的,更新答案
if(b[n] == mid)x = mid;
left = mid + 1;
}
else
{
right = mid - 1;
}
}
if(x != -1)
{
long long ans = 0;
for(int i = 1; i <= n; i++)
ans += a[i] - x;
cout<<ans / 2<<endl;
}
else
cout<<-1<<endl;
}
return 0;
}
C 数字消除
题意: 对于自然数序列 1 , 2 , 3 , 4 , 5 , . . . , n 1,2,3,4,5,...,n 1,2,3,4,5,...,n,重复进行以下操作:
- 从左往右删除奇数位上的数字
- 从右往左删除奇数位上的数字
直到只剩下 1 1 1个数字停止。给定数字 n n n,输出最终的数字。
Tag: 模拟、暴力、数学
难度: ☆☆
来源: 原创
思路: 这是一个经典的数学问题—— t h e s p e c t a t o r − f i r s t T a n t a l i z e r p r o b l e m the\ spectator-first\ Tantalizer\ problem the spectator−first Tantalizer problem, 21 21 21年还有一篇论文专门讨论这个问题的通解。
Chuang, Wei-Tung, Hong-Bin Chen, and Fu-Yuen Hsiao. “General solution to the spectator-first Tantalizer problem.” Discrete Mathematics 344.10 (2021): 112515.
言归正传,编程求解就不需要考虑很复杂的公式,假定 n = 10 n=10 n=10
初始状态: 1 , 2 , 3 , 4 , 5 , . . . , 10 1,2,3,4,5,...,10 1,2,3,4,5,...,10(首项为 1 1 1,公差为 1 1 1,项数为 10 10 10)
第一步: 2 , 4 , 6 , 8 , 10 , 2,4,6,8,10, 2,4,6,8,10,(从左往右删除:首项为 2 2 2,公差为 2 2 2,项数为 5 5 5)
第二步: 4 , 8 4,8 4,8(从右往左删除:首项为 4 4 4,公差为 4 4 4,项数为 2 2 2)
第三步: 8 8 8(从左往右删除:首项为 8 8 8,公差为 8 8 8,项数为 1 1 1)
规律:
- 每次操作完,公差乘以 2 2 2,项数除以 2 2 2
- 从左往右删:首项变成第二项
- 从右往左删:奇数长度则首项变成第二项,偶数长度首项不变
按照规律,维护等差数列就可以啦。
当然,也可以用递推式 f [ n ] = 2 ∗ ( n 2 + 1 − f [ n 2 ] ) f[n]=2*(\frac{n}{2}+1-f[\frac{n}{2}]) f[n]=2∗(2n+1−f[2n]),此处不展开。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f(ll n)
{
bool flag = true;
///维护等差数列:首项为start,长度为n、公差为d
ll length = n, start = 1, d = 1;
while(length != 1)
{
if(flag)///从左往右删除奇数位
start = start + d;
else if(length % 2 == 1)///从右往左删除奇数位,并且是奇数长度
start = start + d;
d = d * 2;
length /= 2;
flag = !flag;
}
return start;
}
ll f2(ll n)
{
if(n <= 1)return 1;
return 2 * (n / 2 + 1 - f2(n / 2));
}
int main()
{
int T;
cin >> T;
while(T--)
{
ll n;
cin >> n;
cout<<f(n)<<endl;
}
return 0;
}
D 拥挤点
题意: 给定 N N N个不同的坐标整点 ( x , y ) (x,y) (x,y),按照顺序逐步加入到二维平面上。拥挤点:一个点的水平方向或者竖直方向恰好和 3 3 3个点相邻。每次往平面中加入一个点,输出目前总共有多少个拥挤点。
Tag: 模拟
难度: ☆☆
来源: U S A C O 2021 F e b USACO\ 2021\ Feb USACO 2021 Feb
思路: 直接按照题意模拟即可。利用数组 v i s [ x ] [ y ] vis[x][y] vis[x][y]表示点 ( x , y ) (x,y) (x,y)已经加入平面。
每次添加 ( x , y ) (x,y) (x,y),需要将 v i s [ x ] [ y ] vis[x][y] vis[x][y]置为 1 1 1。加入的点会改变自己和周围的 4 4 4个点的状态。
直接暴力判断 ( x , y ) (x,y) (x,y)以及四周的点在加入 ( x , y ) (x,y) (x,y)之后是否属于拥挤点。
使用 o k [ x ] [ y ] ok[x][y] ok[x][y]来维护点 ( x , y ) (x,y) (x,y)是否属于拥挤点。每次判断的时候更新一下 o k ok ok数组和 a n s ans ans即可。
#include<bits/stdc++.h>
using namespace std;
bool vis[1010][1010];
bool ok[1010][1010];
int dir[5][2] = {1,0, 0,1, -1,0, 0,-1, 0,0};
int ans;
//判断(x,y)是否为拥挤点
void check(int x, int y)
{
if(!vis[x][y])return;//首先得是个点
int now = 0;
for(int i = 0; i < 4; i++)
{
int xx = x + dir[i][0];
int yy = y + dir[i][1];
now += vis[xx][yy];
}
if(now == 3 && ok[x][y] == false)
{
ans++;
ok[x][y] = true;
}
else if(now != 3 && ok[x][y] == true)
{
ans--;
ok[x][y] = false;
}
}
int main()
{
int n;
cin >> n;
while(n--)
{
int x, y;
cin >> x >> y;
x++, y++; //下标从1开始
vis[x][y] = 1;
for(int i = 0; i < 5; i++)
check(x + dir[i][0], y + dir[i][1]);
cout<<ans<<endl;
}
return 0;
}
E 超级骑士
题意: 现在在一个无限大的平面上,给你一个超级骑士。超级骑士有 N N N种走法,请问这个超级骑士能否到达平面上的所有点。每种走法输入两个数字 x x xx xx和 y y yy yy,表示超级骑士可以从任意一点 ( x , y ) (x,y) (x,y)走到 ( x + x x , y + y y ) (x+xx,y+yy) (x+xx,y+yy)。
Tag: d f s dfs dfs、 b f s bfs bfs
难度: ☆☆☆
来源: B Z O J 2954 BZOJ\ 2954 BZOJ 2954
思路: 如何判断一个骑士可以走遍整个空间?
只要这个骑士能够从 ( x , y ) (x,y) (x,y)走到 ( x + 1 , y ) (x+1,y) (x+1,y), ( x − 1 , y ) (x-1,y) (x−1,y), ( x , y + 1 ) (x,y+1) (x,y+1), ( x , y − 1 ) (x,y-1) (x,y−1),则说明骑士可以走遍平面。
那么直接暴力使用 d f s dfs dfs或者 b f s bfs bfs的方法,在平面上尽可能的走,看看能不能走到 4 4 4联通的点。
由于每步坐标不超过 100 100 100,那么可以从 ( 100 , 100 ) (100,100) (100,100)出发,暴力走 [ 1 − 200 ] [ 1 − 200 ] [1-200][1-200] [1−200][1−200]这个二维平面,最终检查一下 ( 100 , 100 ) (100,100) (100,100)的四周是不是可以打上标记。
#include<bits/stdc++.h>
using namespace std;
int n, dirx[110], diry[110];
bool vis[210][210];
//从(x,y)出发,把可以到达的点全部打上标记
void dfs(int x, int y)
{
///cout<<x<<" "<<y<<endl;
vis[x][y] = true;//把当前点打标记
for(int i = 1; i <= n; i++)//遍历n个方向
{
//新坐标(xx, yy)
int xx = x + dirx[i];
int yy = y + diry[i];
//判断越界、已标记
if(xx < 1 || xx > 200 || yy < 1 || yy > 200)
continue;
if(vis[xx][yy])continue;
dfs(xx, yy);
}
}
int main()
{
int T;
cin >> T;
while(T--)
{
cin >> n;
for(int i = 1; i <= n; i++)
cin >> dirx[i] >> diry[i];
memset(vis, 0, sizeof(vis));
int x = 100, y = 100;
dfs(x, y);
if(vis[x - 1][y] && vis[x + 1][y] && vis[x][y - 1] && vis[x][y + 1])
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
return 0;
}