[csp模拟1]可怕的宇宙射线——(三)

2023-11-03 16:50
文章标签 csp 模拟 可怕 宇宙射线

本文主要是介绍[csp模拟1]可怕的宇宙射线——(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 题意
    • Input
    • Output
    • 输入样例
    • 输出样例
    • 提示
  • 分析
  • 总结
  • 代码

题意

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

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

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

Input

输入第一行包含一个正整数n(n<=30)),表示宇宙射线会分裂n次

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

Output

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

输入样例

4
4 2 2 3

输出样例

39

提示

每个测试点 1000ms 262144KB


分析

小菜鸡看到第三天的题目和图片就赶紧退出了【233】
但是仔细想想这道题也不是完全做不起的题【就是限时内交不了的233】

  • 题目理解

这个题目很好理解。可以看作是在一个二维坐标图上的任意一点开始发散分裂,问最后一共经过多少个坐标。

第一次分裂只有一次且一定朝上,其后的分裂都是从上次分裂的终点开始朝两侧的45度前进。显然,这是指数级的增长。


  • 题目分析

虽然我后来AC的做法和最开始的思路不一样,但是我想在这里记录和分析讨论一下我最开始的思路。

  1. 对称(没有做下去🔜)

在刚开始思考这道题的时候,我们都能很快发现样图中分裂的图形一定根据第一次分裂的直线对称。而指数级的增长如果暴力解算是必定会爆掉的。所以我想如果能找到一种办法只计算一半的路径,那么这个问题起码就能减少一倍吧。不过显然,图中最大的问题就在于那些会重复经过的点,而一点不论被路过几次,都只会算作一次。

那么问题关键就在于如何计算出这一半的路径,并且排除掉重复点

  • 类似bfs求路径

由于比较笨,所以最开始并没有直接想到用bfs和dfs去求路径,而是自己搞了个稀奇古怪的方法。

除去第一次分裂单独处理。只处理单侧分裂

循环分裂次数次,将每次分裂分为第奇或偶次。将奇偶两次分裂的起点分别装到不同的队列中,在奇偶次分裂时交换调用和存储,来保证区分以及及时停止。

当队列不为空时依次调出队列中的点进行分裂。每个起点都将其接下来的两个分类方向存在了一个pair类型的vector中。因此只需要在调出一个点时,两次调用分裂函数,传入分裂方向、分裂起点、分裂步数即可。

分裂函数中根据不同的分裂方向计算其经过的路径。将经过的每个点进行判定,若其未出现过,则表明已到达,总到达点数+1。否则忽略。在计算每个单边点的同时计算它的对称点,根据特征,对称点一定是高度相同,横向距离相反(将第一次分裂处作为(0,0)的话)。计算出其对称点后,在原点之后进行判定,同样处理。

  • 问题❗️

这个做法在分裂次数达到10的时候就会出现错误,分裂次数大于正确答案,也就是说存在冗余。

我之前一直在想是否会是标记是否到达出现了问题,但是只要出现过就不会二次存储,这应该也没有什么太大问题。

那么问题或许就是出现在求对称点上。但是根据图形特征,任何一个在单侧分裂中出现过的点在其对称处都一定会出现,不论是否重复。

所以,这种思路的问题仍在等待解决中🔄

  1. dfs(AC)

dfs就没有什么复杂的问题了。

  1. 第一个关键在于每次在一个方向中计算出下次分裂起点后,应该有两次不同方向的调用。
  2. 第二个关键在于边界的限制。由于是通过递归依次叠加读取当前点应分裂的步数,所以会有数组下标的限制,以防止当前递归的次数已经超过了题目给出数据分裂的次数。
  3. 第三个关键在于剪枝。这种搜索法将会走过指数级增长的所有点,因此如果不进行 剪枝,递归是一定会💥的。显然,如果一个点已经走过,那么他接下来的这条分叉路就不需要再重复遍历。这里就用到了记忆性剪枝。通过递归函数的三个参数的四个变量(方向、横坐标、纵坐标、第i次分裂)来唯一确定一个分叉口的状态。若当前分叉扣已经到达,则return,否则将该状态标记为已到达。
  • 代码实现

bfs代码书写难度不大,参照模版即可。

但在这个题目中有一些地方需要小小的注意。

  1. 标记状态数组

既然需要一个四维数组来唯一标记每一个状态,那么这四位数组的四个容量该如何确定呢?

方向共有8种,分裂次数最多为30次。因此这两个数组容量都很好控制。

可是该如何来记录所有点的横坐标和纵坐标呢?

根据题目可知,每次分裂最多走5步,最多分裂30次,也就是说一次分裂最多走出150步。而如果我们将分裂起点的坐标设置为(0,0)的话,就一定会出现负数,而普通的数组无法支持负数下标。

因此,我们需要将坐标起点设置在一个保守的中间值,保证不会出现负数。那么我们数组的长度只要能保证能包含该中间值的下界和上界即可。

比如大家普遍选择的(200,200)这个起始值,如果走150步都是负数方向,也不会出现负坐标。而四位数组中横坐标和竖坐标的数组长度都是400,就将200上增和下减150的幅度都包含在内了。

  1. 是否需要单独存储方向?

这不是一定的。如果在递归函数中进行if判定数字其实就已经足够。

又因为每个点的分裂方向都可以根据其分裂得到的方向推测出来,并且不会发生改变,即指存在固定情况。因此可以在每个if最后直接将其对应接下来的两个方向作为参数调用递归函数。

但是如果要存储也是可以的,模仿迷宫的坐标偏移量或许可以(?)。


总结

  1. bfs和dfs模版还是很重要
  2. 今后努力学着骗分!
  3. 心态很重要,还是不太习惯csp赛制,技术和心理都没有良好适应。
  4. 继续加油鸭🍟

代码

//
//  main.cpp
//  lab3
//
//#include <iostream>
#include <vector>
#include <map>
#include <queue>
using namespace std;vector<int> times;                      //分裂步数
//map<int,pair<int, int>,int,bool> op;     //标记已搜索节点
bool judge[8][400][400][35]={false};
map<pair<int, int>,bool> point;         //标记已到达点
//map<pair<int, int>,pair<int, int>> next;    //记录下一次分裂方向
//map<pair<int, int>,pair<int,int>> direction;     //记录方向(-1无,0上,1下,2右,3左,4左上,5右上,6左下,7右下)
pair<int, int> start(200,200);          //记录每一次分裂的起点,first为横坐标,second为竖坐标
//queue<pair<int, int>> begin1,begin2;        //记录每一次分裂的所有起点(单侧),begin1为偶次分裂起点(奇次终点),begin2为奇数分裂起点(偶次终点)
int sum = 0,n = 0;void move(int d,pair<int, int> p,int s)
{if( s >= n )                    //设置边界{
//        cout<<" ----- "<<endl;return;}if( judge[d][p.first][p.second][s] )        //记忆性剪枝{
//        cout<<" ----- "<<endl;return;}judge[d][p.first][p.second][s] = true;      //标记为已访问int step = times[s];
//    pair<int, int> next(-1,-1);
//    int d1 = -1,d2 = -1;if( d == 0 ){while( step-- ){p.second++;if( !point[p] )//point.count(p) == 0 )    //若当前点未被到达{
//                cout<<p.first<<" "<<p.second<<" ** "<<endl;point[p] = 1;sum++;}}move(4,p,s+1);move(5,p,s+1);}if( d == 1 ){while( step-- ){p.second--;if(!point[p] )//point.count(p) == 0 )     //若当前点未被到达{
//                  cout<<p.first<<" "<<p.second<<" ** "<<endl;point[p] = 1;sum++;}}move(6,p,s+1);move(7,p,s+1);}if( d == 2 ){while( step-- ){p.first++;if( !point[p] )//point.count(p) == 0 )     //若当前点未被到达{
//                  cout<<p.first<<" "<<p.second<<" ** "<<endl;point[p] = 1;sum++;}}move(5,p,s+1);move(7,p,s+1);}if( d == 3 ){while( step-- ){p.first--;if( !point[p] )//point.count(p) == 0 )   //若当前点未被到达{
//                  cout<<p.first<<" "<<p.second<<" ** "<<endl;point[p] = 1;sum++;}}move(4,p,s+1);move(6,p,s+1);}if( d == 4 ){while( step-- ){p.first--;p.second++;if( !point[p] )//point.count(p) == 0 )   //若当前点未被到达{
//              cout<<p.first<<" "<<p.second<<" ** "<<endl;point[p] = 1;sum++;}}move(0,p,s+1);move(3,p,s+1);}if( d == 5 ){while( step-- ){p.first++;p.second++;if( !point[p] )//point.count(p) == 0 )    //若当前点未被到达{
//                cout<<p.first<<" "<<p.second<<" ** "<<endl;point[p] = 1;sum++;}}move(0,p,s+1);move(2,p,s+1);}if( d == 6 ){while( step-- ){p.first--;p.second--;if( !point[p] )//point.count(p) == 0 )    //若当前点未被到达{
//                cout<<p.first<<" "<<p.second<<" ** "<<endl;point[p] = 1;sum++;}}move(3,p,s+1);move(1,p,s+1);}if( d == 7 ){while( step-- ){p.first++;p.second--;if( !point[p] )//point.count(p) == 0 )     //若当前点未被到达{
//                cout<<p.first<<" "<<p.second<<" ** "<<endl;point[p] = 1;sum++;}}move(2,p,s+1);move(1,p,s+1);}/*//分裂图形一定对称,即每个点都一定存在与其横坐标完全相反的对称点(除第一次分裂)
//        symmetry.second = p.second;
//        symmetry.first = -p.first;int x = p.first,y = p.second;if( point.count(p) == 0 )    //若当前点未被到达{cout<<p.first<<" "<<p.second<<" ** "<<endl;point[p] = 1;sum++;}if( point.count(symmetry) == 0 )  //若当前点的对称点未被到达{cout<<symmetry.first<<" "<<symmetry.second<<" ^^ "<<endl;point[symmetry] = 1;sum++;}*/}int main()
{int step = 0;cin>>n;for( int i = 1 ; i <= n ; i++ ){cin>>step;times.push_back(step);}move(0,start,0);cout<<sum<<endl;return 0;
}

这篇关于[csp模拟1]可怕的宇宙射线——(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/339551

相关文章

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

usaco 1.2 Transformations(模拟)

我的做法就是一个一个情况枚举出来 注意计算公式: ( 变换后的矩阵记为C) 顺时针旋转90°:C[i] [j]=A[n-j-1] [i] (旋转180°和270° 可以多转几个九十度来推) 对称:C[i] [n-j-1]=A[i] [j] 代码有点长 。。。 /*ID: who jayLANG: C++TASK: transform*/#include<

hdu4431麻将模拟

给13张牌。问增加哪些牌可以胡牌。 胡牌有以下几种情况: 1、一个对子 + 4组 3个相同的牌或者顺子。 2、7个不同的对子。 3、13幺 贪心的思想: 对于某张牌>=3个,先减去3个相同,再组合顺子。 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOExcepti

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

每日一题|牛客竞赛|四舍五入|字符串+贪心+模拟

每日一题|四舍五入 四舍五入 心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。 四舍五入 题目: 牛牛发明了一种新的四舍五入应用于整数,对个位四舍五入,规则如下 12345->12350 12399->12400 输入描述: 输入一个整数n(0<=n<=109 ) 输出描述: 输出一个整数

【算法专场】模拟(下)

目录 前言 38. 外观数列 算法分析 算法思路 算法代码 1419. 数青蛙 算法分析 算法思路 算法代码  2671. 频率跟踪器 算法分析 算法思路 算法代码 前言 在前面我们已经讲解了什么是模拟算法,这篇主要是讲解在leetcode上遇到的一些模拟题目~ 38. 外观数列 算法分析 这道题其实就是要将连续且相同的字符替换成字符重复的次数+

模拟实现vector中的常见接口

insert void insert(iterator pos, const T& x){if (_finish == _endofstorage){int n = pos - _start;size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);pos = _start + n;//防止迭代

PHP实现二叉树遍历(非递归方式,栈模拟实现)

二叉树定义是这样的:一棵非空的二叉树由根结点及左、右子树这三个基本部分组成,根据节点的访问位置不同有三种遍历方式: ① NLR:前序遍历(PreorderTraversal亦称(先序遍历)) ——访问结点的操作发生在遍历其左右子树之前。 ② LNR:中序遍历(InorderTraversal) ——访问结点的操作发生在遍历其左右子树之中(间)。 ③ LRN:后序遍历(PostorderT

CSP-J基础之数学基础 初等数论 一篇搞懂(一)

文章目录 前言声明初等数论是什么初等数论历史1. **古代时期**2. **中世纪时期**3. **文艺复兴与近代**4. **现代时期** 整数的整除性约数什么样的整数除什么样的整数才能得到整数?条件:举例说明:一般化: 判断两个数能否被整除 因数与倍数质数与复合数使用开根号法判定质数哥德巴赫猜想最大公因数与辗转相除法计算最大公因数的常用方法:举几个例子:例子 1: 计算 12 和 18