NewOJ Week 4题解

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 x1,x1,...,x1。因为是偶数长度,只需要继续每相邻两个减 1 1 1即可。这说明满足单调性,直接二分 x x x,每次判断是否能变成 x x x即可,然后找一个最大的 x x x

对于奇数长度: 其实也满足二分的性质,我们假设最终全部变成 x x x,然后从左往右依次变成 x x x,模拟相邻两个数字减法的过程:

  1. 如果在模拟全部变成 x x x的过程中有数字小于 x x x,说明 x x x大了,需要调小。

  2. 如果前 n − 1 n-1 n1个数字都可以减小到 x x x,那么就看最后一个数字:

    1. 最后一个数字等于 x x x,说明找到答案;

    2. 最后一个数字大于 x x x,说明 x x x小了;

    3. 最后一个数字小于 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. 从左往右删除奇数位上的数字
  2. 从右往左删除奇数位上的数字

直到只剩下 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 spectatorfirst 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+1f[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) (x1,y) ( x , y + 1 ) (x,y+1) (x,y+1) ( x , y − 1 ) (x,y-1) (x,y1),则说明骑士可以走遍平面。

那么直接暴力使用 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] [1200][1200]这个二维平面,最终检查一下 ( 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

傅志凌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值