【日常刷题】NOIP练习题题解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Ronaldo7_ZYB/article/details/83618584

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

题解:
读入两个字符串s1和s2,设长度分别是len1和len2,则:
1.len1=len2,直接进行运算
2.len1<len2,第0~len2-len1-1位是第二个字符串与0进行逻辑运算,接下来再逐渐配对。
3.len1>len2,与2相反。

CODE

#include<bits/stdc++.h>
using namespace std;
#define MAXN 200000

string s;
char s1[MAXN];
char s2[MAXN];
int ans[MAXN],len1,len2,Max;

void work_xor()
{
    for (int i=0;i<Max;++i)
    {
        int det=abs(len1-len2);
    	if (len1!=Max && len2==Max)
    	{
    		if (i<len2-len1) ans[i]=0^int(s2[i]-48);
    		else ans[i]=int(s1[i-det]-48)^int(s2[i]-48);
    	}
    	if (len1==Max && len2!=Max)
    	{
    		if (i<len1-len2) ans[i]=0^int(s1[i]-48);
    		else ans[i]=int(s1[i]-48)^int(s2[i-det]-48);
    	}
    	if (len1==Max && len2==Max)
    	    ans[i]=int(s1[i]-48)^int(s2[i]-48);
    }
}

void work_or()
{
    for (int i=0;i<Max;++i)
    {
        int det=abs(len1-len2);
    	if (len1!=Max && len2==Max)
    	{
    		if (i<len2-len1) ans[i]=0|int(s2[i]-48);
    		else ans[i]=int(s1[i-det]-48)|int(s2[i]-48);
    	}
    	if (len1==Max && len2!=Max)
    	{
    		if (i<len1-len2) ans[i]=0|int(s1[i]-48);
    		else ans[i]=int(s1[i]-48)|int(s2[i-det]-48);
    	}
    	if (len1==Max && len2==Max)
    	    ans[i]=int(s1[i]-48)|int(s2[i]-48);
    }
}

void work_and()
{
    for (int i=0;i<Max;++i)
    {
        int det=abs(len1-len2);
    	if (len1!=Max && len2==Max)
    	{
    		if (i<len2-len1) ans[i]=0&int(s2[i]-48);
    		else ans[i]=int(s1[i-det]-48)&int(s2[i]-48);
    	}
    	if (len1==Max && len2!=Max)
    	{
    		if (i<len1-len2) ans[i]=0&int(s1[i]-48);
    		else ans[i]=int(s1[i]-48)&int(s2[i-det]-48);
    	}
    	if (len1==Max && len2==Max)
    	    ans[i]=int(s1[i]-48)&int(s2[i]-48);
    }
}

int main()
{
	freopen("bignum.in","r",stdin);
	freopen("bignum.out","w",stdout);
	cin>>s1;
	cin>>s2;
	cin>>s;
	len1=strlen(s1);
	len2=strlen(s2);
	Max=max(len1,len2);
	if (s=="xor") work_xor();
	if (s=="and") work_and();
	if (s=="or") work_or();
	for (int i=0;i<Max;++i)
		if (ans[i]==1) 
		{
			for (int j=i;j<Max;++j)
			    cout<<ans[j];
			break;
		}
	fclose(stdin);
	fclose(stdout);
	return 0;
}

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

本题难点在于读懂题目:机器人只会在边框移动;最后的终点也只会在边框上出现。
用DFS+记忆化搜索预处理每一个点的最短路径,然后直接输出即可。

CODE

#include<bits/stdc++.h>
using namespace std;

int ans;
int x,y,fx,fy,ax,ay,st=1e9;
int f[25][25];
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};

inline int dis(int a,int b,int c,int d){
	return abs(a-c)+abs(b-d);
}

inline bool check(int tx,int ty){
	return (tx==1 || tx==19 || ty==1 || ty==19) && (tx>=1 && tx<=19 && ty>=1 && ty<=19);
}

inline bool canget(int tx,int ty){
	return tx==fx || ty==fy;
}
void dfs(int lx,int ly,int sum)
{
	int nx,ny;
	if (sum>=f[lx][ly]) return;
	f[lx][ly]=sum;
	if (canget(lx,ly) && dis(lx,ly,fx,fy)<ans) ans=dis(x,y,fx,fy),st=sum+dis(x,y,fx,fy); 
	if (canget(lx,ly) && dis(lx,ly,fx,fy)==ans) st=min(st,st=sum+dis(x,y,fx,fy));
	for (int i=0;i<4;++i)
	{
		nx=lx+dx[i];
		ny=ly+dy[i];
		if (check(nx,ny)==false) continue; 
		dfs(nx,ny,sum+1);
	}
	return;
}
int main()
{
	freopen("betago.in","r",stdin);
	freopen("betago.out","w",stdout);
	
	memset(f,127,sizeof(f));
	cin>>x>>y>>fx>>fy;
	ans=1e9;
	dfs(x,y,0);
	ans=1e9;
	cout<<f[fx][fy];
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

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

题解
对于暴力方法,我们可以这么做:
要使这个i为冠军,必须不惜一切代价凑足一种情况下使得其他数都小于这个数字,那么必然需要除了他以外的每一个选手,原来的值越大,加上的值越小。这是一个基于贪心的策略。
显然,枚举每一个人再加以此会超时,我们可以这么做:
设初始序列排序后有一个递减序列: a [ 1 ] , a [ 2 ] , a [ 3 ] . . . a [ n ] a[1],a[2],a[3]...a[n]
在保证这串序列是不上升的情况下,分别加上一个数: a [ 1 ] + 1 , a [ 2 ] + 2 , . . . , a [ n ] + n a[1]+1,a[2]+2,...,a[n]+n
用一个前缀和lefmax[i]记录1-i的最大值,用一个后缀和rightmax记录i-n的最大值。
对于每一个i,显然如果取n会出现这样的情况: a [ 1 ] + 1 , a [ 2 ] + 2 , . . . , a [ i ] + n , a [ i + 1 ] + i , a [ i + 2 ] + i + 1 , . . . , a [ n ] + n 1 a[1]+1,a[2]+2,...,a[i]+n,a[i+1]+i,a[i+2]+i+1,...,a[n]+n-1
那样直接用前缀和与后缀和-1与a[i]+n判断是否合法即可。

#include<bits/stdc++.h>
using namespace std;
#define MAXN 302000

int mark[MAXN],sum[MAXN];
int leftmax[MAXN],rightmax[MAXN];

inline void read(int &x)
{
	int s=0;char c=getchar();
	while (c<'0' || c>'9') c=getchar();
	while (c>='0' && c<='9') s=s*10+c-48,c=getchar();
	x=s;return;
}

inline bool cmp(int a,int b){
	return a>b;
}

int main()
{
	freopen("racing.in","r",stdin);
	freopen("racing.out","w",stdout);
	int n,ans=0;
	read(n);
	for (int i=1;i<=n;++i) read(mark[i]);
	sort(mark+1,mark+n+1,cmp);
	for (int i=1;i<=n;++i) 
	{
		sum[i]=i+mark[i];
		leftmax[i]=max(leftmax[i-1],sum[i]);
	}
	for (int i=n;i;--i) rightmax[i]=max(rightmax[i+1],sum[i]);
	for (int i=1;i<=n;++i) 
	    if (mark[i]+n>=leftmax[i] && mark[i]+n>=rightmax[i]-1) ans++;
	printf("%d",ans);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

4.whatbase

题目描述
Bessie
回想一下,一个数被写成B 进制,那么这个数从右到左每位表示1;B1;B2;B^3, 如此下去。例如,在我们所熟悉的十进制系统下,我们用每位数字分别表示1,10,100,1000,以此类推。如果数字1234 被理解成10 进制,那么它其实应该表示的是1(1000) + 2(100) + 3(10) + 4(1)。如果相同的数字被理解成5 进制,那么它意味着1(125) + 2(25) + 3(5) + 4(1),转换成十进制是19 。Bessie 注意到如果进制增加,相同数字串表示的值也增大,比如;1234 在7 进制下的值比1234 在6 进制下的值要大。
当在B 进制下写数字的时候,每位数字的范围都是0…B-1,就比如在十进制下每位数的范围是0…9,在5 进制下是0…4. 考虑进制数大于10 是完全可能的。计算机科学家们常常使用16 进制,A…F 分别表示值10…15. 例如,BEEF 在16 进制下对应11(4096) + 14(256) + 14(16) + 15,对应10 进制下的48879.
Bessie 被“进制数可以大于10”这个概念所深深地迷住。她拿过一个数字N 并且在X 进制和Y 进制两个进制下分别写出,X 和Y 的范围是10≤x,y≤15000。 有趣的是,在每种情况下,她拿到一个有3 个数字的数字串,每位数字只有0…9。
不幸的是,由于Bessie 记性很差,她现在已经忘记了N、X和Y。给两个3 位数字,请帮助她找出使用过的X 和Y。
输入格式
第一行一个整数K,表示有k个测试数据,k≤100。
接下来K 行,每行一个测试数据。每对测试数据包含两个3 位数字。第一个数字表示N 被写成X 进制,第二个数表示N被写成Y 进制。(N,X 和Y 对于每个测试数据都可能不同)。
输出格式
K 行,每行两个整数。和测试数据对应的X 和Y,两个数字用空格隔开。
对于每个测试数据,保证有唯一的答案。
样例数据
input

1
419 792
output
47 35

题解
枚举每一个进制,用MAP来判断是否出现两次即可。
至于如何转换成十进制,体面当说说的很清楚。

CODE

#include<bits/stdc++.h>
using namespace std;
#define MAXN 

inline void read(int &readnum)
{
	int s=0;char c=getchar();
	while (c<'0' || c>'9') c=getchar();
	while (c>='0' && c<='9') s=s*10+c-48,c=getchar();
	readnum=s;
}

void work()
{
	map<int,int>v;
    int n1;read(n1);
	int n2;read(n2);
	for (int i=10;i<=15000;++i)
	{
		int a=(n1%10)+(n1%100/10)*i+(n1%1000/100)*i*i;
		int b=(n2%10)+(n2%100/10)*i+(n2%1000/100)*i*i;
		if (v[a]) printf("%d %d\n",i,v[a]);
		if (v[b]) printf("%d %d\n",v[b],i);
		if (v[a] || v[b]) return;
		v[a]=v[b]=i;
	}
	return;
}

int main()
{
	freopen("whatbase.in","r",stdin);
    freopen("whatbase.out","w",stdout);
	int k;read(k);
	while (k--) work();
	fclose(stdin);
	fclose(stdout);
	return 0;
}

5.height

题目描述
奶牛们对围栏的高度非常敏感。奶牛希望围栏高度既不太矮以至于他们没有安全感,也不太高以至于她们看不见围栏外的天地。所以每头奶牛都有自己喜欢的高度。奶牛有N头,每个奶牛有一个自己喜欢的高度在[1…10,000]范围里。
这对John来说是一个头疼的问题。作为一个美学观点强烈的人,他希望围栏的高度基本相同,但作为一个有同情心的人,他希望能尽可能的满足奶牛们的需求。(或许他只是一个自私的人,希望奶牛能给他多产点奶)
作为妥协,他决定将围栏的柱子做成不同的高度以满足大多数奶牛的需求(必须严格超过半数)。一个满意的条件是在所有的柱子中有它喜欢的那个高度。但同时John希望最高的柱子和最矮的柱子之间的高度差最小。请你帮他解决这个问题。
围栏的高度仅仅由柱子的高度决定。
输入格式
第1行:一个整数N。(1<=n<=10000)
第2…N+1:一个整数ai表示这头牛喜欢的高度。
输出格式
仅一行即最高的柱子和最矮的柱子之间的高度差。
样例数据
input

3
1
10
3
output
2

题解:
贪心:
1.最有答案一定存在于最短区间内。证明:对于一个区间最小值a和最大值b,若新加入的值比a小或者比b大,一定不优;在a-b的区间内,则不会对结果产生影响。
2.如果从小到大排序,最大最小值一定是区间的左端点和右端点。证明:显然。

CODE

#include<bits/stdc++.h>
using namespace std;
#define MAXN 12000

int a[MAXN];

inline void read(int &readnum)
{
	int s=0;char c=getchar();
	while (c<'0' || c>'9') c=getchar();
	while (c>='0' && c<='9') s=s*10+c-48,c=getchar();
	readnum=s;
}


int main()
{
	freopen("height.in","r",stdin);
    freopen("height.out","w",stdout);
	int n;
	read(n);
	for (int i=1;i<=n;++i) read(a[i]);
	sort(a+1,a+n+1);
	int len;
	len=n/2+1;
	int ans=INT_MAX;
	for (int i=1;i+len-1<=n;++i) ans=min(ans,a[i+len-1]-a[i]);
	cout<<ans<<endl;
	fclose(stdin);
	fclose(stdout);
	return 0;
}

6.最优分解

题目描述
一道数学题:将一个正整数n分解成若干个互不相等的正整数的和,使得这些数的乘积最大,当主持人报出一个n后,请你立即将这个最大值报出来。
现请你帮你的好友编一个程序来解决这个问题。
输入格式
只有1个数n(其中1<=n<=1000)。
输出格式
输出文件best.out中也是一个数,是乘积的最大值
样例数据
input
7
output
12

显然,和相同,数字越接近,乘积越大。
一定可以得出一个结论:尽可能的选择连续的数字并且从2开始连续。
那有多出来的数字应该如何平局分配呢?显然从前往后分配每一次都会造成重复,这样就不会满足题目所给的互不相等的条件。因此应该从后往前每次进行平均分配才可以。
1.像9(2+3+4),14(2+3+4+5)这样的数直接想乘即可。
2.像10:2+3+4+1这样有多出来的,最后一个数从后往前一个一个分。若多出来的位置为n,则n-1分1,n-2分1…第一个位置分1;然后接着往n-1分,知道分完为止。
3.像3,4这样只能由两个数相加的,特判即可。
以为涉及到了乘法并且数据较大,我们选择用高精度解决。

#include<bits/stdc++.h>
using namespace std;
#define MAXANS 5000
#define MAXN 60000
#define MAXLEN 12000
#define upest 11000

int n,k,cnt=0;
int a[MAXN],ans[MAXANS],res[MAXLEN];

inline void read(int &readnum)
{
	int s=0;char c=getchar();
	while (c<'0' || c>'9') c=getchar();
	while (c>='0' && c<='9') s=s*10+c-48,c=getchar();
	readnum=s;
}

void work(int &n)
{
	int m=n;
	for (int i=2;;++i)
		if (n-i>=0) ans[++cnt]=i,n-=i;
		else break;//从2开始减 
	if (n) ans[++cnt]=n;//剩余数字 
	if (ans[cnt]-1==ans[cnt-1]) return;//递增 
	if (cnt==2&&a[cnt]<=a[cnt-1])
	{ 
	    ans[1]=1;
		ans[2]=m-1;
		return;
	}//不能继续累加的数 
	int dlt=ans[cnt]/(cnt-1);
	int oth=ans[cnt]-dlt*(cnt-1);
	for (int i=1;i<cnt;++i) ans[i]+=dlt;
	for (int i=cnt-1,num=1;num<=oth;i--,num++) ans[i]++;//平分最后一个数字 
	cnt--;
	return;
}

void plusNumber()
{
	res[1]=1;
	for (int i=1;i<=cnt;++i)
	{
		for (int j=1;j<=upest;++j) res[j]*=ans[i];
		for (int j=1;j<=upest;++j) res[j+1]+=res[j]/10,res[j]%=10;
	}
	return;
}//高精*单精 

void write()
{
	for (int i=upest;i;--i)
	    if (res[i]!=0)
	    {
	    	for (int j=i;j;--j) cout<<res[j];
	    	return;
	    }//去掉前导0 
    return;
}

int main()
{
	freopen("best.in","r",stdin);
    freopen("best.out","w",stdout);
	read(n);
	work(n);
	plusNumber();
	write();
	return 0;
}

7.angry

题目描述
贝茜这头奶牛设计了她认为将是下一个热门的视频游戏“愤怒的奶牛”。她认为这是她完全原创的:玩家将一个弹弓射到一个一维的场景中,该场景由位于数字线上各个点的一组干草包组成。每只奶牛都有足够的力量引爆其落地地点附近的干草包。爆炸后的干草包会引爆R范围内的干草包。我们的目的是使用一系列奶牛引爆所有的干草包。
有N捆干草包位于这一行的不同整数位置x1,x2,…,xN,如果一头奶牛以能量R着陆在了位置x,那么会引起半径为R(R-x…R+x)的爆炸,并摧毁范围内的所有干草包。
一共有K头奶牛允许被用来作为炮弹,每头奶牛的能量R都相同。请帮忙决定这个最小的能量,使得用这K头奶牛可以摧毁所有的干草包。
输入格式
第一行包含两个整数N,K(1<=N<=50,000,1<=K<=10)。
接下来N行,每行包含一个整数xi,表示每捆干草包的位置(0<=xi<=1,000,000,000)。
输出格式
一行一个整数,最少所需要的每头奶牛的能量值R。
样例数据
input

7 2
20
25
18
8
10
3
1
output
5

题解:
二分答案每一个半径的长度,
排序后从左到右不断进行枚举,累加,只当长度超过两个半径位置。
只要半径总数小雨或等于k就是合法的。

CODE

#include<bits/stdc++.h>
using namespace std;
#define MAXN 60000

int n,k;
int a[MAXN];

inline void read(int &readnum)
{
	int s=0;char c=getchar();
	while (c<'0' || c>'9') c=getchar();
	while (c>='0' && c<='9') s=s*10+c-48,c=getchar();
	readnum=s;
}

inline bool check(int x)
{
	int sum=0;
	for (int now=1;now<=n;)
	{
		int p=now++;
		sum++;
		while (a[now]-a[p]<=2*x && now<=n) now++;
	}
	return sum<=k;
}

int main()
{
	freopen("angry.in","r",stdin);
    freopen("angry.out","w",stdout);
	read(n);read(k);
	for (int i=1;i<=n;++i) read(a[i]);
	sort(a+1,a+n+1);
	int l=0,r=1e9,m;
	while (l+1<r)
	{
		m=l+r>>1;
		if (check(m)) r=m;
		else l=m;
	}
	if (check(l)) cout<<l<<endl;
	else cout<<r<<endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Ronaldo7_ZYB/article/details/83618584