『NOIP赛前模拟题题解』

版权声明:随你转载,你开心就好(记得评论和注明出处哦) https://blog.csdn.net/Prasnip_/article/details/83618863

T1. CZYZNOIP普及组模拟赛1-1.小X与位运算

题目描述

自从上次小X 搞定了完美数之后,他最近在研究一项和计算机密切相关的黑科技。要知道在计算机的内部,数据都是以二进制的形式来进行存储的,而它使用的计算方法也和我们平时的加减乘除四则运算 有所不同,它使用的是位运算。那什么是位运算呢? 基础位运算有三种符号,分别是 and,or,xor(分别对应 pascal 中的 and,or,xor 三种运算符 号)。

以 and 为例,两个二进制数在做 and 运算时,分别对两个二进制数的每一位做 and 运算。而对每一 位做and 运算时,遵守以下规则:只有当两个数的这一位都是 1 时,运算结果才为 1,否则就是 0。例如 1101 和10101 做and 运算之后结果为101(高位不足用0 补齐,最后结果忽略前导0)。 通俗点讲 and 运算就是按位做乘法,即将两个二进制数从高位到低位依次对齐,然后每一位上对齐 的两个数相乘即得到这一位的结果 。我们可以列一个简单的例子来说明个and运算:01101 and 10101 = 00101。

而or,xor 的运算方法类似,唯一不同的是在对每一位做运算时遵循的方法不同。 or 运算遵守以下规则:只有当两个数的这一位都是 0 时,运算结果才为 0,否则就是 1。例如1101or10101=11101。

xor 运算遵守以下规则:只有当两个数的这一位相同时,运算结果才为0,否则就是1。例如1101xor10101=11000 。

小 X想知道两个很大很大的二进制数,在做完位运算之后, 最后的结果是什么。而小X 自己无法知道正确答案是什么,他只好求助于你来帮助他解决这个问题。
输入格式

输入数据第一行是一个字符串,由字符0 和1组成,表示一个二进制数。 第二行也是一个字符串,由字符0 和1组成,同样表示一个二进制数。 第三行还是一个字符串,一定是and,or,xor三个中一种,表示运算符号。 注意输入的二进制数没有前导零,字符个数可能会超过255 个。
输出格式

输出一行一个字符串,由字符0和1 组成,表示最后运算得到的二进制数。 注意输出的二进制数不能带有前导零,即输出的第一个字符不能为0。
样例数据

input

110100
11001
or

output

111101

数据规模与约定

用n 来表示输入的最长的二进制数的长度。

对于30% 的数据,1 ≤ n ≤ 20。

对于70% 的数据,1 ≤ n ≤ 1000。

对于100% 的数据,1 ≤ n ≤ 100000。

时间限制:1s1s

空间限制:256MB256MB

解析

根据题意进行标记,暴力模拟位运算,输出时注意去掉前导零即可。

#include<bits/stdc++.h>
using namespace std;
char num1[100080],num2[100080],op[10];
int Num1[100080]={},Num2[100080]={},OP,Maxlen,Ans[100080]={};
inline void input()
{
	scanf("%s",&num1);scanf("%s",&num2);
	scanf("%s",&op);
	int len1=strlen(num1),len2=strlen(num2);
	Maxlen=max(len1,len2);
	int t=Maxlen;
	for(int i=len1-1;i>=0;i--)Num1[t--]=num1[i]-'0';
	t=Maxlen;
	for(int i=len2-1;i>=0;i--)Num2[t--]=num2[i]-'0';
	if(op[0]=='a')OP=1;
	if(op[0]=='o')OP=2;
	if(op[0]=='x')OP=3;
}
inline void work()
{
	for(int i=1;i<=Maxlen;i++)
	{
		switch(OP)
		{
			case 1:Ans[i]=Num1[i]&&Num2[i];break;
			case 2:Ans[i]=Num1[i]||Num2[i];break;
			case 3:Ans[i]=Num1[i]==Num2[i]?0:1;break;
		}
	}	
	int flag=0;
	for(int i=1;i<=Maxlen;i++)
	{
		if(!flag&&!Ans[i])continue;
		if(Ans[i])flag=1;
		printf("%d",Ans[i]);
	}
	printf("\n");
}
int main()
{
	freopen("bignum.in","r",stdin);
	freopen("bignum.out","w",stdout);
	input();
	work();
	return 0;
}

更简单的做法是直接用STL自带的bitset二进制数来完成本题要求的位运算。

#include<bits/stdc++.h>
#define INF 100000
using namespace std;
bitset<INF> s1,s2;
string ys;
int now;
bool b[INF];
int main()
{
	freopen("bignum.in","r",stdin);
	freopen("bignum.out","w",stdout);
	cin>>s1>>s2>>ys;
	if(ys=="or")	s1=s1|s2;
	if(ys=="xor")	s1=s1^s2;
	if(ys=="and")	s1=s1&s2;
	while(s1.any())	b[now]=s1[now],s1[now]=0,now++;
	while(now--)	cout<<b[now];
	return 0;
}

T2. CZYZNOIP普及组模拟赛1-2.小 X 与机器人

题目描述

小 X 最近对战胜韩国围棋大神李世石的 AlphaGo 很感兴趣,所以小 X 自己写了一个叫做 BetaGo 的人工智能程序(简称 AI),这个 BetaGo 会做什么呢? 小 X 首先想要让 BetaGo 做到自己在棋盘上落子,这一点 AlphaGo 是由程序员来完成的。

小 X 的 设想是这样的:在棋盘的边框上放置一个小机器人,这个小机器人会沿着棋盘的边框移动到最接近落子点的位置,然后伸出它的机械臂将棋子放到棋盘上。这里面最关键的一步是如何让小机器人在棋盘的边框上沿着最短的路径移动,小 X 想请你帮他编个程序解决这个问题。众所周知,围棋棋盘大小为 19 × 19(如下图所示),图中加粗的一圈即为边框。我们用一对整数 (x, y) 来表示棋盘上第 x 条横线(从下往上数)与第 y 条竖线(从左往右数)的交叉点,如上图中边框上的 A 点用(6,1)表示,B 点用(10,19)表示,小机器人初始时放置在 (x1, y1) 这个位置上,它想要移动到 (x2, y2) 这个位置上。

(x1, y1)和(x2, y2) 一定是棋盘边框上的交叉 点 每一步小机器人可以从当前位置移动到相邻(上下左右)的某个位置上,即每次可以从 (x, y) 移 动到 (x - 1, y)、(x + 1, y)、(x, y - 1)、(x, y + 1) 四个位置中的一个,但是它不能走出或走进棋盘, 也就是说它只能沿着棋盘的边框移动到相邻位置,这就意味着任一时刻相邻位置都恰好只有两个。 BetaGo 会告诉小机器人最少需要走多少步,但小 X 还是很担心 BetaGo 有的时候会失控,从而告诉他一个错误值。为此小 X 只好求助你,希望你编一个程序计算从 (x1, y1) 沿着棋盘的边框移动到 (x2, y2) 最少需要走多少步。上图中从 A 点(6,1)移动到 B 点(10,19)最少需要走 32 步,移动路线是: (6,1)→(5,1)→(4,1)→(3,1)→(2,1)→(1,1)→(1,2)→(1,3)→…… →(1,19)→(2,19)→……→(10,19)。
输入格式

输入数据仅有一行包含四个用空格隔开的正整数表示 x1, y1, x2, y2。

数据保证 (x1, y1),(x2, y2) 一定是棋盘边框上的交叉点。
输出格式

输出一行包含一个整数 ans,表示小机器人从 (x1, y1) 移动到 (x2, y2) 的最少步数。
样例数据

input

6 1 10 19

output

32

数据规模与约定

对于 30% 的数据,(x1, y1),(x2, y2)在同一条边框上。

对于另外 30% 的数据,(x1, y1),(x2, y2)在相邻的两条边框上。

对于另外 40% 的数据,(x1, y1),(x2, y2)在相对的两条边框上。

时间限制:1s1s

空间限制:256MB

解析

根据题目的要求可知,起点和终点一点在边框上,那么就可以直接数学推导
算了,还是敲广搜吧。

#include<bits/stdc++.h>
using namespace std;
struct position{int x,y;}Begin,End;
queue<position>Q;int vis[25][25]={},dis[25][25]={};
int dx[5]={0,0,0,1,-1},dy[5]={0,1,-1,0,0};
inline void input()
{
	scanf("%d%d%d%d",&Begin.x,&Begin.y,&End.x,&End.y);
}
inline bool Check(int x,int y)
{
	return (x==1&&y>0&&y<20)||(y==1&&x>0&&x<20)||(x==19&&y>0&&y<20)||(y==19&&x>0&&x<20);
}
inline void bfs()
{
	vis[Begin.x][Begin.y]=true;dis[Begin.x][Begin.y]=0;
	Q.push(position{Begin.x,Begin.y});
	while(!Q.empty())
	{
		position temp=Q.front();Q.pop();
		int x=temp.x,y=temp.y;
		for(int i=1;i<=4;i++)
		{
			int tx=x+dx[i],ty=y+dy[i];
			if(Check(tx,ty)&&!vis[tx][ty])
			{
				dis[tx][ty]=dis[x][y]+1;
				vis[tx][ty]=true;
				Q.push(position{tx,ty});
			}
		}
	}
	printf("%d\n",dis[End.x][End.y]);
}
int main()
{
	freopen("betago.in","r",stdin);
	freopen("betago.out","w",stdout);
	input();
	bfs();
	return 0;
}

那么数学方法是这样子的,假设起点,终点坐标为 ( x 1 , x 2 ) (x_1,x_2) , ( y 1 , y 2 ) (y_1,y_2) ,我们分四种情况讨论即可。
1. x 1 = x 2 , d i s m i n = y 1 y 2 2. y 1 = y 2 , d i s m i n = x 1 x 2 3. x 1 x 2 = 18 , d i s m i n = m i n ( 16 + y 1 + y 2 , 56 y 1 y 2 ) ( ) 4. y 1 y 2 = 18 , d i s m i n = m i n ( 16 + x 1 + x 2 , 56 x 1 x 2 ) ( ) 1.x_1=x_2,dis_{min}=|y_1-y_2| \\2.y_1=y_2,dis_{min}=|x_1-x_2| \\3.|x_1-x_2|=18,dis_{min}=min(16+y_1+y_2,56-y_1-y_2)(两个方向) \\4.|y_1-y_2|=18,dis_{min}=min(16+x_1+x_2,56-x_1-x_2)(两个方向)
这个需要好好手推一下,实际上也不太难。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	freopen("betago.in","r",stdin);
	freopen("betago.out","w",stdout);	
	int x1,x2,y1,y2,ans;
	cin>>x1>>y1>>x2>>y2;
	if(x1==x2){cout<<abs(y1-y2);return 0;}
	if(y1==y2){cout<<abs(x1-x2);return 0;}
	if(abs(x1-x2)==18){cout<<min(16+y1+y2,56-y1-y2);return 0;}
	if(abs(y1-y2)==18){cout<<min(16+x1+x2,56-x1-x2);return 0;}
	cout<<abs(x1-x2)+abs(y1-y2);
	return 0;
}

T3. CZYZNOIP普及组模拟赛1-3.赛车

题目描述

小x为了平复自己悲愤的心情,参加了F7赛车决赛的解说工作。作为一位优秀的主持人,他想要了解一下参加决赛的N位选手的情况。经过一番努力,他找到了各位选手前几站比赛的成绩。

决赛就要开始了,比赛规定:第一个到达终点的得到N分,第二个到达终点的得到N-1分,以此类推…最后一个到达终点的得到1分。而且不会有两位选手同时到达终点。

小x非常忙,所以他想请你帮他统计一下有多少选手有可能成为总冠军(之前的成绩+决赛成绩=总成绩,总成绩最高者为总冠军,总冠军可能有多位)。
输入格式

第一行一个正整数N(3≤N≤300000),代表参加决赛的选手个数。

接下来N行,每行一个正整数Bi,代表第i位选手之前比赛的成绩。
输出格式

一行一个正整数,代表有可能成为总冠军的选手个数。
样例数据

input1

3
8
10
9

output1

3

input2

5
15
14
15
12
14

output2

4

数据规模与约定

时间限制:1s1s

空间限制:256MB256MB

解析

主要的方法是判定法,每一次对一个选手进行判断,判断他是否能够在最优情况下得冠军
为了方便我们处理,我们先将选手按照他们之前的分数排序。使
S c o r e 1 S c o r e 2 S c o r e 3 . . . S c o r e n Score_1≥Score_2≥Score_3≥...≥Score_n
先考虑之前分数最低的n号选手,贪心策略便是在这次比赛中,1号选手得1分,二号选手得2分,…,n号选手得n分,这样显然是所有得分方案中对n号选手最有利的方案。那么我们只要判断在这种方案下n号选手是否有可能的冠军即可。
如果 m a x 1 i n { S c o r e i + i } S c o r e n + n max_{1≤i<n}\{Score_i+i\}≤Score_n+n ,那么n号选手就有的冠军的可能,我们暴力求最大值 M a x = m a x 1 i n { S c o r e i + i } Max=max_{1≤i<n}\{Score_i+i\} 并判断。
接着考虑第n-1号选手,根据贪心策略,1号选手得1分,二号选手得2分,…,n-2号选手得n-2分,n-1号选手得n分,n号选手得n-1分。那么我们需要判断 m a x ( S c o r e n + n 1 , m a x 1 i n 1 { S c o r e i + i } ) S c o r e n + n max(Score_n+n-1,max_{1≤i<n-1}\{Score_i+i\})≤Score_n+n 是否成立,其实,仔细思考可以发现 S c o r e n &lt; S c o r e n 1 , n 1 &lt; n Score_n&lt;Score_{n-1},n-1&lt;n ,所以 S c o r e n + n 1 &lt; S c o r e n 1 + n Score_n+n-1&lt;Score_{n-1}+n 一定成立,我们就可以不考虑 S c o r e n Score_{n} 造成的影响,判断 m a x 1 i n 1 { S c o r e i + i } S c o r e n 1 + n max_{1≤i<n-1}\{Score_i+i\}≤Score_{n-1}+n 是否成立即可,而 m a x 1 i n 1 { S c o r e i + i } max_{1≤i<n-1}\{Score_i+i\} 取自
{ m a x 1 i n 2 { S c o r e i + i } S c o r e n 1 + n 1 \begin{cases} max_{1≤i<n-2}\{Score_i+i\} \\Score_{n-1}+n-1 \end{cases}
考虑到这里,其实我们可以直接取我们之前算过的Max,因为max里的取值在第二种情况时一定小于 S c o r e n 1 + n Score_{n-1}+n ,而取第一种情况时可以用Max代替,所以这次找 m a x 1 i n 1 { S c o r e i + i } max_{1≤i<n-1}\{Score_i+i\} 时直接用之前的Max判断,不用再找了。

是不是很奇妙,其实仔细讨论可以发现,每一次都可以用Max来判断,那么Max就成了我们预处理的对象,再O(n)判断每一个人就可以了,这就是本题所有的贪心及优化策略。

#include<bits/stdc++.h>
using namespace std;
int n,score[300080]={},ans=0,Max=-1;
inline void read(int &k)
{
	int w=0,x=0;char ch;
	while(!isdigit(ch))w|=ch=='-',ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	k=(w?-x:x);return;
}
inline void input()
{
	read(n);
	for(int i=1;i<=n;i++)
	{
		read(score[i]);
	}
	sort(score+1,score+n+1);
}
inline void work()
{
	for(int i=1;i<=n;i++)
	{
		score[i]+=(n-i+1);
		Max=max(Max,score[i]);
	}
	for(int i=1;i<=n;i++)
	{
		if(score[i]-(n-i+1)+n>=Max)ans++;
	}
	cout<<ans<<endl;
}
int main()
{
	freopen("racing.in","r",stdin);
	freopen("racing.out","w",stdout);
	input();
	work();
	return 0;
}

T4. CZYZNOIP普及组模拟赛1-4.小srf的游戏

题目描述

srf和qtc在一个规模为 1 × n 的棋盘中下棋。规则是:

第一个人可以下在第1 到 m 中的任意一个位置。接下来每一个人可以下在第 i + 1 到 i + m 的任意一个位置,其中i为上一个人下棋的位置。

每个格子里有一个数,如果一个人下棋在格子i,会得到a[i]的分值。

当不能继续操作时,结束。

小srf请你帮他算一下,当他和qtc都采取最优策略时,他的得分减去qtc的得分。
输入格式

第一行:两个正整数n和m,用空格隔开。

第二行:n 个数,表示棋盘上的数字。
输出格式

两行,每行各一个数, 第一个数为srf先手时的答案, 第二个数为qtc先手时的答案。
样例数据

input1

1 3
1

output1

1
-1

input2

2 100
2 2

output2

2
-2

数据规模与约定

对于30% 的数据,n ≤ 15;

对于60% 的数据,m ≤ 100;

对于100% 的数据,1 ≤ n ≤ 100000,1 ≤ m,a数组中的数保证在int范围内。

时间限制:1s1s

空间限制:256MB

解析

设置dp[i]代表下棋到第i个格子的最优差值,利用第n个格子一定会走到的特点,倒序转移状态。考虑到每一次转移状态需要用到之前的最优值,利用单调队列维护最大值。还有一个结论就是两个人互换先后手时,他们的最优操作一定是镜像的,所以两个答案互为相反数。
仔细理解代码其实没那么难。

#include<bits/stdc++.h>
using namespace std;
inline void read(int &k)
{
	int x=0,w=0;char ch;
	while(!isdigit(ch))w|=ch=='-',ch=getchar();
	while(isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	k=(w?-x:x);return;
}
int n,m,score[100080]={},f[100080]={};
inline void input()
{
	read(n),read(m);
	for(int i=1;i<=n;i++)read(score[i]);
}
inline void dp()
{
	deque< int >q;
	f[n]=score[n];
	q.push_back(n);
	for(int i=n-1;i>=0;i--)
	{
		while(!q.empty()&&q.front()>m+i)q.pop_front();
		f[i]=score[i]-f[q.front()];
		while(!q.empty()&&f[q.back()]<f[i])q.pop_back();
		q.push_back(i);
	}
}
int main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	input();
	dp();
	printf("%d\n%d\n",-f[0],f[0]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Prasnip_/article/details/83618863