week4——实验题解(csp模拟1)

题目1
咕咕东的奇遇
咕咕东是个贪玩的孩子,有一天,他从上古遗迹中得到了一个神奇的圆环。这个圆环由字母表组成首尾相接的环,环上有一个指针,最初指向字母a。咕咕东每次可以顺时针或者逆时针旋转一格。例如,a顺时针旋转到z,逆时针旋转到b。咕咕东手里有一个字符串,但是他太笨了,所以他来请求你的帮助,问最少需要转多少次。
在这里插入图片描述
输入
输入只有一行,是一个字符串。
输出
输出最少要转的次数。
解题思路
A-Z的一个环形转盘,要求转的次数最少的话每次从一个字母转到另一个字母时所转动的次数不应该超过半圈。因此,先求出两个字母差的绝对值,如果绝对值大于13,则应转动26-x,否则转动x。
代码

#include <cstdio>
#include <string>
#include <cmath>
using namespace std;
int main()
{
    char str[20000];
    scanf("%s",str);
    int i=0;
    int sum=0;
    char pointer='a';
    while(str[i]!=0)
    {
    	int t=abs((int)(pointer-str[i]));
    	if(t>13) sum+=(26-t);
    	else sum+=t;
    	pointer=str[i];
    	i++;
	}
	printf("%d",sum);
    return 0;
}

题目2
咕咕东想吃饭
咕咕东考试周开始了,考试周一共有n天。他不想考试周这么累,于是打算每天都吃顿好的。他决定每天都吃生煎,咕咕东每天需要买ai 个生煎。但是生煎店为了刺激消费,只有两种购买方式:①在某一天一次性买两个生煎。②今天买一个生煎,同时为明天买一个生煎,店家会给一个券,第二天用券来拿。没有其余的购买方式,这两种购买方式可以用无数次,但是咕咕东是个节俭的好孩子,他训练结束就走了,不允许训练结束时手里有券。咕咕东非常有钱,你不需要担心咕咕东没钱,但是咕咕东太笨了,他想问你他能否在考试周每天都能恰好买ai个生煎。
输入
输入两行,第一行输入一个正整数n(1<=n<=100000)(1<=n<=100000),表示考试周的天数。
第二行有n个数,第i个数ai(0<=ai<=10000)ai (0<=ai<=10000)表示第i天咕咕东要买的生煎的数量。
输出
如果可以满足咕咕东奇怪的要求,输出"YES",如果不能满足,输出“NO”。(输出不带引号)
解题思路
要求是满足在考试周的每天恰好能买够生煎,且在考试周之后不留下券。这样来说,我们要尽可能的在当天恰好买够生煎数并且尽可能少的影响明天所买的生煎数。观察购买策略,第一种方式是当天买两个,第二种方式是当天买1个,同时第二天少买1个。因此,我们尽可能使用第一种方式购买,实在不行再使用第二种方式。这样可以保证影响到第二天的购买数量最多为1(奇数)。即使用第二种策略去替换第一种策略,其对第二天的影响数的奇偶性仍然是不变的。因此可以证明此为最优策略。不能满足的条件为:中间的某一天购买数为负数或者最后一天的购买数为奇数。
代码

#include <cstdio>
#include <string>
#include <cmath>
using namespace std;
int main()
{
    int n;
    bool ok=true;
    scanf("%d",&n);
    int *shengjian;
    shengjian=new int[n];
    for(int i=0;i<n;i++)
    {
    	scanf("%d",&shengjian[i]);
	}
	for(int i=0;i<n-1;i++)
	{
		if(shengjian[i]<0)
		{
			ok=false;
			break;
		}
		if(shengjian[i]%2!=0) shengjian[i+1]=shengjian[i+1]-1;
	}
	if(shengjian[n-1]%2!=0||shengjian[n-1]<0) ok=false;
	if(ok) printf("YES");
	else printf("NO");
    return 0;
}

题目3
可怕的宇宙射线
众所周知,瑞神已经达到了CS本科生的天花板,但殊不知天外有天,人外有苟。在浩瀚的宇宙中,存在着一种叫做苟狗的生物,这种生物天 生就能达到人类研究生的知识水平,并且天生擅长CSP,甚至有全国第一的水平!但最可怕的是,它可以发出宇宙射线!宇宙射线可以摧毁 人的智商,进行降智打击! 宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的 左右45°方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂 次,每次分裂后会在分裂方向前进 个单位长度。 现在瑞神要带着他的小弟们挑战苟狗,但是瑞神不想让自己的智商降到普通本科生 那么菜的水平,所以瑞神来请求你帮他计算出共有多 少个位置会被"降智打击"
输入
输入第一行包含一个正整数 ,表示宇宙射线会分裂 次 第二行包含n个正整数 ,第 个数 表示第 次分裂的宇宙射线会在它原方向上继续走多少个单位长度。
输出
输出一个数 ,表示有多少个位置会被降智打击
示意图
注:这个题我在模拟的时候没做出来,这里重点说一下!
解题思路1:40pts
这个思路能得40分的原因是前四组测试点的数据规模相对较小,后六组必然会报TLE。(然而由于我在模拟考试时的zz操作,我前四个点全是WA,因为我在初始化的时候手贱给链表加了一个初始值,导致最后的结果弄出来比正确答案都少了一个。真的丢人啊)
思路就是暴力求解。怎么走的怎么来求。对于一个光线来说,它可能的方向只有8种,所以可以用常量数组来描述方向。之后就是对过程的模拟了。使用bfs搜索算法,利用哈希表实现的map记录到达的点的个数,最后遍历完成之后输出结果就好了。使用链表来储存每一层需要bfs 的点。使用尾结点指针使得插入操作的复杂度降到O(1)。
思路1代码

#include <cstdio>
#include <string>
#include <cmath>
#include <vector>
#include <deque>
#include <unordered_map>
class ray
{
public:
	float x;
	float y;
	int vec;
	ray* next;
	ray(float a,float b,int c)
	{
		x=a;
		y=b;
		vec=c;
		next=NULL;
	}
};

class point
{
public:
	float x;
	float y;
	point(float a,float b)
	{
		x=a;
		y=b;
	}
	bool operator ==(const point& p) const
	{
		if(x==p.x&&y==p.y) return true;
		else return false;
	}
};

struct hashpoint
{
	std::size_t operator() (const point& p) const
	{
		return int(p.x);
	}
};

using namespace std;
int main()
{
	float vector_x[8]={0,-1,-1,-1,0,1,1,1};
	float vector_y[8]={1,1,0,-1,-1,-1,0,1};
	unordered_map<point,bool,hashpoint> mp;
	ray* firstNode;
	ray* lastNode;
	firstNode=new ray(0,0,0);
	lastNode=firstNode;
	bool b=false;
	int count=0;
	int n;
	int length;
	int pointer;
	float tx;
	float ty;
	int size=1;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int temp=0;
		scanf("%d",&length);
        for(int k=1;k<=size;k++)
        {
        	//printf("%d\n",size);
			tx=firstNode->x;
			ty=firstNode->y;
			pointer=firstNode->vec;
			//printf("(%f,%f,%d)\n",tx,ty,pointer);
			for(int j=1;j<=length;j++)
			{
				point p(tx+j*vector_x[pointer],ty+j*vector_y[pointer]);
				if(mp.find(p)==mp.end())
				{
					//printf("insert(%f,%f)\n",p.x,p.y);
					count++;
					mp[p]=true;
				}
			}
			int tp=pointer-1;
			if(tp<0) tp=tp+8;
			ray *r1=new ray(tx+length*vector_x[pointer],ty+length*vector_y[pointer],tp);
			//printf("queue_insert(%f,%f,%d)\n",r1.x,r1.y,r1.vec);
			lastNode->next=r1;
			lastNode=lastNode->next;
			temp++;
			tp=pointer+1;
			if(tp>=8) tp=tp-8;
			ray *r2=new ray(tx+length*vector_x[pointer],ty+length*vector_y[pointer],tp);
			//printf("queue_insert(%f,%f,%d)\n",r2.x,r2.y,r2.vec);
			lastNode->next=r2;
			lastNode=lastNode->next;
			temp++;
			ray* deleteNode=firstNode;
			firstNode=firstNode->next;
			delete deleteNode;
		}
		size=temp;
	}
	printf("%d",count);
    return 0;
}
}

然而,即使一切的插入,删除操作的复杂度都是O(1),在爆炸的指数面前,O(1)这个英勇的骑士仍然显得弱小而无力。所以,舍弃这种方法。
解题思路2(据说可以100昏,然而我还没有写出来):
使用的思路是dfs 的记忆性剪枝。据群里yqy大佬说这个方法可以AC,然而我在考试后折腾了一晚上也没写出来AC的代码。。。最好的时候也是只过了4个点。这个有待更新。
思路2代码:
暂无,等待更新中。
解题思路3(100pts,这个我写出来了):
这个思路是我请教hnl大佬得到的。他给我说使用图形的对称性考虑一下这个题。我才突然想到我一直忽视了对称这个条件。具体的思路为:
由于光线的分裂是以原光线为轴,沿顺时针旋转45°产生一束分裂光,沿逆时针旋转45°产生一束分裂光。因此,左右光是以原光线为轴的对称光线。并且,在以后的光传播过程中,左右光经过的点也是关于原光线对称的。以此类推,之后光线分裂产生的光也是关于原光线对称的。因此,如果从迭代的最后一代,逆向的从后向前依次对称,就可以对称出所有点,如下图的示例所示:
在这里插入图片描述那么剩下的问题就是如何通过已知点的位置对称出其对称点的位置。很容易通过下面推导得出公式:
在这里插入图片描述本题中,代入k=1和k=-1就能求出对称点公式了。
最后,由于在对称过程中会出现两次或多次对称到同一点的情况,我们使用集合来储存对称点,利用集合种元素的唯一性去重即可。
思路3代码:

#include <cstdio>
#include <set>
#include <algorithm>

using namespace std;

int vector_x[8]={0,-1,-1,-1,0,1,1,1};
int vector_y[8]={1,1,0,-1,-1,-1,0,1}; 

class point
{
public:
	int x,y;
	point(int a,int b)
	{
		x=a;
		y=b;
	};
	bool operator <(const point& p) const
	{
		if(x!=p.x) return x<p.x;
		else return y<p.y;
	};
};

set<point> reach;
int length[40];

void dfs(point p,int sel_vec,int time,int deep)
{
	if(time>deep) return;
	dfs({p.x+length[time]*vector_x[sel_vec],p.y+length[time]*vector_y[sel_vec]},(sel_vec+1)%8,time+1,deep);
	set<point> sy;
	for(set<point>::iterator it=reach.begin();it!=reach.end();it++)
	{
		if(sel_vec==0||sel_vec==4) 
		{
			point sypoint(p.x*2-it->x,it->y);
			sy.insert(sypoint);
		}
		else if(sel_vec==1||sel_vec==5)
		{
			point sypoint(p.x+p.y-it->y,p.x+p.y-it->x);
			sy.insert(sypoint);
		}
		else if(sel_vec==2||sel_vec==6)
		{
			point sypoint(it->x,p.y*2-it->y);
			sy.insert(sypoint);
		}
		else 
		{
			point sypoint(it->y-p.y+p.x,it->x+p.y-p.x);
			sy.insert(sypoint);
		}
	}
	reach.insert(sy.begin(),sy.end());
	for(int j=1;j<=length[time];j++)
	{
		point addpoint(p.x+j*vector_x[sel_vec],p.y+j*vector_y[sel_vec]);
		reach.insert(addpoint);
	}
}

int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	    scanf("%d",&length[i]);
	point startpoint(0,0);
	dfs(startpoint,0,1,n);
	printf("%d",reach.size());
}

总结
三道题,前两道较为基础,第三道比较难想,很容易直接暴力求解。其实稍微一思考就能明白暴力求解是不可行的。这次模拟感觉做的不太好,做题拿过来就做,基本很少思考题目有什么巧妙的方法。我觉得这样是不行的。还有,各类算法还需更加熟悉,这次第三题的剪枝法到现在还没弄出来就是一个很好的警示。

发布了10 篇原创文章 · 获赞 1 · 访问量 174

猜你喜欢

转载自blog.csdn.net/qq_43715114/article/details/104820407
今日推荐