程序设计思维与实践 Week4 CSP模测

A 咕咕东的奇遇

1. 题目大意

咕咕东是个贪玩的孩子,有一天,他从上古遗迹中得到了一个神奇的圆环。这个圆环由字母表组成首尾相接的环,环上有一个指针,最初指向字母a。咕咕东每次可以顺时针或者逆时针旋转一格。例如,a顺时针旋转到z,逆时针旋转到b。咕咕东手里有一个字符串,但是他太笨了,所以他来请求你的帮助,问最少需要转多少次。

在这里插入图片描述

输入:
输入只有一行,是一个字符串。

输出:
输出最少要转的次数。

样例:

zeus
18

2. 思路历程

  • 这道题就是计算字符串中每两个相邻字符间的最小距离和,因此要把字母都转换成数字。
  • 圆环旋转方向可顺时针可逆时针,因此每次取两个方向的最小距离即可。

3. 具体实现

  • a用于记录输入的 字符串,pre用于记录上一个字符,now用于记录当前的字符,res用于记录距离和。
  • 遍历字符串中所有字符,更新pre为now,now为当前处理的a[i](转换为数字是a[i] - ‘a’)。
  • 两个方向的距离分别是abs(pre-now)和26-abs(pre-now),二者取最小。

4. 代码

#include <iostream>
#include <algorithm>
#include <string.h>
#include <cmath>
using namespace std;

int main()
{
    string a;
    cin >> a;
    int pre = 0, now = 0, res = 0;
    for (int i = 0; i < a.size(); i++)
    {
        pre = now;
        now = a[i] - 'a';
        res += min(abs(pre-now), 26-abs(pre-now));
    }
    cout << res;
    
    return 0;
}

B 咕咕东想吃饭

1. 题目大意

咕咕东考试周开始了,考试周一共有n天。他决定每天都吃生煎,咕咕东每天需要买 ai 个生煎。但是生煎店为了刺激消费,只有两种购买方式:①在某一天一次性买两个生煎。②今天买一个生煎,同时为明天买一个生煎,店家会给一个券,第二天用券来拿。没有其余的购买方式,这两种购买方式可以用无数次,但是咕咕东是个节俭的好孩子,他训练结束就走了,不允许训练结束时手里有券。咕咕东非常有钱,你不需要担心咕咕东没钱,但是咕咕东太笨了,他想问你他能否在考试周每天都能恰好买 ai 个生煎。

输入:
输入两行,第一行输入一个正整数n(1<=n<=100000)表示考试周的天数。
第二行有n个数,第 i 个数 ai(0<=ai<=10000)表示第 i 天咕咕东要买的生煎的数量。

输出:
如果可以满足咕咕东奇怪的要求,输出"YES",如果不能满足,输出“NO”。(输出不带引号)

样例:

4
1 2 1 2
YES  

2. 思路历程

  • 刚开始我把题目想得太复杂了,对于每天的购买方式是一个一个考虑的,用了dfs。
  1. 每次搜索只进行一次购买,即只产生对今天可购买数-2或今明两天可购买数各-1的变化,只有当搜索到最后一天且可购买数刚好为0时成功。
  2. 这种方法对于100000天就要搜索100000的不知道几次方次,时间复杂度实在可怕,但是我比赛的两小时里竟然还在剪枝硬啃…
  • 后来想到其实无需考虑中间那些天具体的购买方法,只需考虑最后一天是否有券剩下即可。
  1. 两种方法在本质上每天购买的数量都是2,需要偶数个的天数直接采用方案1(对后面无影响),需要奇数个的天数则用一次方案2(对后一天的个数-1)
  2. 最后判断最后一天需要的个数,为偶数则可行(全部采用方案1),为奇数则不可行(采用一次方案2但是剩下券)
  • 其实刚开始有想到奇偶数,但是在比赛中害怕临时换掉整个思路会很耗时,现在知道坚持错误的方向才是最耗时的(还很难对),对于复杂度高到离谱的方法要先反思自己的思路而不是马上去剪枝。

3. 具体实现

  • 数组a[i]用于记录每天的购买数量。
  • 遍历数组
  1. 若a[i]<0,说明今天用不完明天的券,直接跳出循环
  2. 若a[i]>=0且为奇数时,采用方案2,明天中有一个用券购买,a[i+1]-1
  • 循环结束判断最后一天所需个数的奇偶性。

4. 代码

#include <iostream>
#include <algorithm>
#include <string.h>
#include <cmath>
using namespace std;

int n;
int a[100111];

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];

    for (int i = 0; i < n - 1; i++)
    {
        if (a[i] < 0)
            break;
        
        if (a[i] % 2)
            a[i+1]--;
    }
    
    if (a[n-1] % 2) cout << "NO";
    else cout << "YES";
    
    return 0;
}

C 可怕的宇宙射线

1. 题目大意

众所周知,瑞神已经达到了CS本科生的天花板,但殊不知天外有天,人外有苟。在浩瀚的宇宙中,存在着一种叫做苟狗的生物,这种生物天生就能达到人类研究生的知识水平,并且天生擅长CSP,甚至有全国第一的水平!但最可怕的是,它可以发出宇宙射线!宇宙射线可以摧毁人的智商,进行降智打击!

宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右45°方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂n 次,每次分裂后会在分裂方向前进 ai个单位长度。

现在瑞神要带着他的小弟们挑战苟狗,但是瑞神不想让自己的智商降到普通本科生那么菜的水平,所以瑞神来请求你帮他计算出共有多少个位置会被"降智打击"。
在这里插入图片描述

输入:
输入第一行包含一个正整数n(n<=30) ,表示宇宙射线会分裂n次。
第二行包含n个正整数a1,a2…an,第 i 个数 ai 表示第 i 次分裂的宇宙射线会在它原方向上继续走多少个单位长度。

输出:
输出一个数 ans,表示有多少个位置会被降智打击。

4
4 2 2 3
39

2. 思路历程

  • 刚开始思路错误,看到路径由一点层层扩散出去,先考虑了bfs
  1. 每一级搜索时,在队列中加入该级路径所有的终点,并将路径上的点加入集合,最后集合容量为路径长度
  2. 这个方法需要遍历每一条路径,时间复杂度高,而且bfs需使用队列,而防止已到达处重复计算需使用集合,相当于每条路径的终点存储两遍,空间复杂度也高,是非常不合适的做法。
  • 后来换了个方向看路径的变化,即把扩散改为用延伸理解,考虑到了dfs
  1. 每次递归将路径上所有的点加入set(免去判断是否到达),但是这样进行指数级递归时间太长,需要优化。

  2. 由于这道题的扩散是重复的延伸,“每次向该方向的左右45°方向分裂”,因此可以只对最右的那条路进行dfs,其左边的那条以当前的主方向为轴进行对称

  3. 八个方向有四种对称方式,其中两个只改变x坐标或只改变y坐标的很简单,因此考虑两种斜线的对称 (还是很复杂的,我特地笔算了)
    在这里插入图片描述

  4. 每次递归加入对称点后还要加入主路上的点。

3. 具体实现

  • 所有路径的延伸有八个方向,用move_x和move_y数组分别表示x,y在八个方向延伸的坐标变化。
  • 创建Point结构体用于记录位置,并进行操作符重载用于插入set。
  • 初始点设为(0, -1)进入dfs(方格的坐标和点的坐标还是有点不一样的,用-1是为了和点坐标同步,对称的时候不糊涂)
  • 函数dfs(int now, int i, int move)
  1. now用于记录当前起点(上一层递归的终点),i用于记录递归的层数,move用于记录当前路径延伸的方向
  2. 若未到达底层,则先进入下一层递归,传入起点next(now.x + move_x[move] * a[i], now.y + move_y[move] * a[I]),层数i+1, 方向(move + 1) % 8
  3. 对于每个已经在set中的点都要进行对称再加入set,因此用迭代器遍历set,对八个不同方向进行四种不同的对称方法
  4. 最后把当前主路径上的所有点加入set,这些点的坐标可以通过1到a[i]长度乘以当前方向在move_x和move_y上的取值得到sym(now.x + move_x[move] * j, now.y + move_y[move] * j)
  • 最后set的容量即为走过的路径长度。

4. 代码

#include <iostream>
#include <algorithm>
#include <string.h>
#include <cmath>
#include <set>
using namespace std;

int move_x[] = {0, 1, 1, 1, 0, -1, -1, -1};
int move_y[] = {1, 1, 0, -1, -1, -1, 0, 1};
int n, a[33];

struct Point
{
    int x, y;
    Point() {}
    Point(int _x, int _y)
    {
        x = _x;
        y = _y;
    }
    bool operator < (const Point& point) const
    {
        if (x != point.x) return x < point.x;
        return y < point.y;
    }

};

set<Point> points;

void dfs(Point now, int i, int move)
{
    if (i == n)
        return;
    
    Point next(now.x + move_x[move] * a[i], now.y + move_y[move] * a[i]);
    dfs(next, i + 1, (move + 1) % 8);
    
    for (set<Point>::iterator it = points.begin(); it != points.end(); it++)
    {
        if (move == 0 || move == 4)
        {
            Point sym(now.x - (it->x - now.x), it->y);
            points.insert(sym);
        }
        else if (move == 1 || move == 5)
        {
            Point sym(it->y - now.y + now.x, it->x + now.y - now.x);
            points.insert(sym);
        }
        else if (move == 2 || move == 6)
        {
            Point sym(it->x, now.y + (now.y - it->y));
            points.insert(sym);
        }
        else if (move == 3 || move == 7)
        {
            Point sym(now.x + now.y - it->y, now.x + now.y - it->x);
            points.insert(sym);
        }
    }
    for (int j = 1; j <= a[i]; j++)
    {
        Point sym(now.x + move_x[move] * j, now.y + move_y[move] * j);
        points.insert(sym);
    }
    
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    
    Point now(0, -1);
    dfs(now, 0, 0);
    cout << points.size();
    
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值