2017 南海区区赛

2017区赛 解题报告

 

第一题元音字母(vowel)

【问题描述】

给你一个所有字符都是字母的字符串,请输出其中元音字母的个数。(提示:二十六个字母中的五个元音字母是a,e,i,o,u; 所有字符有大小写区别。)

【输入格式】

仅一行,包括一个字符串。

【输出格式】

输出一个整数,如题所述。

【输入样例】

helloworld

【输出样例】

3

【数据规模】

对于100%的数据,字符串长度小于等于10^6。

     这题比较水,就是看一个字符串里面有多少个元音字母。学过字符串都能够做出来,我就不多说了。不过要注意的一点是,有大小写的区分,意思是不仅仅是小写,大写字母中的元音字母也是要计算的。后来我在检查的时候发现了这一点。时间复杂度O(10^6)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
string s;
int ans;
int main()
{
	freopen("vowel.in","r",stdin);
	freopen("vowel.out","w",stdout);
	cin>>s;
	for(int i=0;i<s.size();i++)
	{
		if(s[i]=='A'||s[i]=='E'||s[i]=='I'||s[i]=='O'||s[i]=='U'||s[i]=='a'||s[i]=='e'||s[i]=='o'||s[i]=='u'||s[i]=='i') ans++;
	}
	cout<<ans<<endl;
	return 0;
}

  

第二题直角坐标系(coordinates)

【问题描述】

给你n 个平面上的点,请你绘制出一个直角坐标系。对于原点,用' + '表示;对于y 坐标轴,用' |'表示(除去原点和n 个点的位置);对于x 坐标轴,用' - '表示(除去原点和n 个点的位置);对于n 个平面上的点,用' * '表示;所有其他点,用' . '表示。为了更好地理解,请参照样例。

【输入格式】

第一行包括一个正整数n。

接下来n 行,每行两个整数x, y,表示点的坐标。

【输出格式】

一个直角坐标系。其中,第一行的y 坐标为所有点的y 坐标和0 中的最大值;最后一行的y 坐标为所有点的y 坐标和0 中的最小值;第一列的x 坐标为所有点的x 坐标和0 中的最小值;最后一列x 坐标为所有点的x 坐标和0 中的最大值。详见样例。

【输入样例1】

8

-105

-73

-42

-94

01

6-1

30

8-3

【输出样例1】

*. . . . . . . . . | . . . . . . . .

.* . . . . . . . . | . . . . . . . .

.. . * . . . . . . | . . . . . . . .

.. . . . . * . . . | . . . . . . . .

.. . . . . . . . . * . . . . . . . .

----------+ -- * -----

.. . . . . . . . . | . . . . . * . .

.. . . . . . . . . | . . . . . . . .

.. . . . . . . . . | . . . . . . . *

【输入样例2】

5

12

53

21

55

33

【输出样例2】

|. . . .*

|. . . . .

|. . * . *

|* . . . .

|. * . . .

+-----

【数据规模】

对于30%的数据,1<=x<=100,1<=y<=100

对于100%的数据,1<=n<=250, 且x,y 的绝对值都不超过100,所有的点两两不同。

 

    这题是一个比较简单的模拟,在输入时标记哪个地方有点,用v数组标记。但是,因为有负半轴的缘故,所以不能直接标记v[x][y],要把x和y都加一个偏移量,因为他们的绝对值都不超过一百,所以加100就够了。

    同时在输入时记录下最小和最大的x、y分别是多少。

    但是,通过第二个样例可以得知,必须要画出一个坐标轴,不然怎么能算是直角坐标系呢。所以,最小和最大的x、y都要与0取max(min)

    然后按一定的次序输出即可。时间复杂度O(x*y)

第三题折纸(folding)

【问题描述】

现有一个W*H的矩形纸张,求至少要折多少次才能使矩形纸张变成w*h的矩形纸张。注意,每次的折痕都要平行于纸张的某一条边。

【输入格式】

第一行包括两个整数W,H。

第二行包括两个整数w,h。

【输出格式】

输出一个整数,表示至少需要折的次数。若无解,则输出-1。

【输入样例1】

27

22

【输出样例1】

2

【输入样例2】

55

16

【输出样例2】

-1

【输入样例3】

106

48

【输出样例3】

2

【数据规模】

对于20%的数据,W=w且H,h<=3

对于100%的数据,1<=W,H,w,h<=10^9

    从这题开始,请注意:前方高能!

    比赛的时候,我在纸上画了几下,发现当边长是W时,把它对折一下为W/2,如果期望达到的w在W和W/2的范围之内,那么就是可以的,否则,就要折成W/2。

    但是看到了最后一个样例,他们的边长需要反过来才能够折,于是自认为很“机智”地就用swap把大小调整好,大对大,小对小。事实证明是个大错误。

错误点get

    1、 不能直接变成W/2,因为当是7的时候,7/2=3,但是并不能折到3,应该最小是4。所以应该是(W+1)/2,同样的,用偶数实验也是可行的。

    2、 至于大对大,小对小的思路显然是想少了。赛后同学给我举了个例子,5 7折成4 5。如果是用我的方法,那么5折成4要折一次,7折成5也要折一次,一共就是两次。

    但如果是5对5,那么根本不用折,7对4,这一次就可以了。最优解应该是一次。

    我看似很有道理的理论就over了。

    正解其实和这个差不了多少,就是分别算一下两种对应的情况,取一个min为最优解即可。

        时间复杂度 O(log(10^9))

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
using namespace std;
const int oo=10000000;
int W,H,w,h,lans,ans2=oo;
int update(int x,int y)
{
	int ans=0;
	int x2=x;
	while(x2>y)
	{
		ans++;
		x2=(x2+1)/2;
	}
	return ans;
}
int main()
{
	scanf("%d%d",&W,&H);
	scanf("%d%d",&w,&h);
	if((w>W&&w>H)||(h>W&&h>H)) 
	{
		cout<<-1<<endl;
		return 0;
	}
	if(W>=w&&H>=h) 
	{
		lans+=update(W,w);
		lans+=update(H,h);
		ans2=min(lans,ans2);
	}
	lans=0;
	swap(W,H);//交换一下 再算一次
	if(W>=w&&H>=h) 
	{
		lans+=update(W,w);
		lans+=update(H,h);
		ans2=min(ans2,lans);
	}
	printf("%d",ans2);
	return 0;
}

  

第四题两个数(twonum)

【问题描述】

现有两个人, 若第一个人当前手中的数为w1,则下一秒他手上的数将会变成(X1 *W1+Y1 ) mod m;若第二个人当前手中的数为 w2,则下一秒他手上的数将会变为(X2*W2+Y2 )mod m(a mod b 表示a 除以b 的余数)。第0 秒,两个人手上的数分别为h1, h2; 请求出最快在第几秒,第一个人手上的数为a1,且第二个人手上的数为a2。若不可能,则输出-1。

【输入格式】

第一行为一个正整数T,表示数据组数。

对于接下来的每一组数据, 第一行为一个正整数m,第二行包括两个整数h1, a1,第三行包括两个整数x1, y1,第四行包括两个整数h2, a2,第五行包括两个整数x2, y2。

【输出格式】

对于每一组数据,输出一行,一个整数,如题所述。

【输入样例】

2

5

42

11

01

23

1023

12

10

12

11

【输出样例】

3

-1

【数据规模】

对于30%的数据, m<=1000

对于100%的数据,

T<=5,  h1不等于 a1且 h2不等于 a2,

2<=m<=106 , 0<=h1,a2, x1, y1, h2, a2, x2, y2<m

    这题在所有题目里面,是最复杂的,虽然看上去好像就是一个简单的模拟,然而事实证明,我想少了。

    首先,要找到循环节。可是循环节怎么找呢?用while循环找。分别用va和vb记录第一和第二个人出现每一个数字的秒数。

一开始初始化为-1,如果已经出现过,那么说明出现了循环,break。循环节的长度cir1则为当前的i-va[],同样的另外一个人出现循环节的长度cir2也为j-vb[]

如果两个va[a1]和va[a2]都为-1,说明不在循环以内出现,那么就输出-1。如果va[a1]==va[a2]都相等,那就最好不过了,直接输出。

接下来,问题就来了,我们分为几种情况。

    1、如果va[a1]在循环节里面并且vb[a2]在循环节外面,那么vb[a2]要大于等于va[a1]不然它们是无法在同一秒内出现的。再看一下vb[a2]-va[a1]能否被第一个人的循环的长度cir1整除。如果符合条件输出vb[a2]。

    2、同理,vb[a2]在循环节里面并且va[a1]在循环节外面的情况也是一样的。

    3、最后一种情况也是最复杂的(我还是一脸蒙,尝试解释)。va[a1]在循环节里面并且vb[a2]在循环节里面。

    有解:分别计算出两个循环节的最大公因数cd,如果他们两个的差除以cd除不尽,那么无解。枚举第一个人,判断是否符合va[a1]>=vb[a2],并且他们的差值是否能被cir2(vb[a2]循环的长度)整除。

    听老师讲完这题以后,我只能说,有点厉害

int gcd(int a,int b)
{
	while(b)
	{
		int c=a%b;
		a=b;
		b=c;
	}
	return a;
}
int main()
{
	long long t,a1,b,mod,h1,x1,y1,h2,a2,x2,y2;
	cin>>t;
	while(t--)
	{
		cin>>mod>>h1>>a1>>x1>>y1>>h2>>a2>>x2>>y2;
		memset(va,-1,sizeof(va));memset(vb,-1,sizeof(vb));
		va[h1%mod]=0;vb[h2%mod]=0;
		long long i,j;i=j=1;
		while(1)
		{
			h1=(x1*h1+y1)%mod;
			if(va[h1]==-1)
			{
				va[h1]=i;
				i++;
			}
			else break;
		}//分别算出在哪个地方循环
		while(1)
		{
			h2=(x2*h2+y2)%mod;
			if(vb[h2]==-1)
			vb[h2]=j;
				j++;
			}
			else break;
		}
		if(va[a1]==-1||vb[a2]==-1) cout<<-1<<endl;
		else if(va[a1]==vb[a2]) cout<<va[a1]<<endl;
			else{
			long long cir1=i-va[h1];
			long long cir2=j-vb[h2];
			if(va[h1]>va[a1])
			{
				if(vb[h2]>vb[a2]) cout<<-1<<endl;
				else{
					long long cnt2=vb[a2];
					if((va[a1]-cnt2>=0)&&((va[a1]-cnt2)%cir2==0))
						cout<<va[a1]<<endl;
						else
						cout<<-1<<endl;
				}
			}
			else{
				if(vb[h2]>vb[a2])
				{
					long long cnt1=va[a1];
					if((vb[a2]-cnt1>=0)&&((vb[a2]-cnt1)%cir1==0))
						cout<<vb[a2]<<endl;
						else
						cout<<-1<<endl;
				}
				else{
					
					int cnt=vb[a2]-va[a1];
					int cd=gcd(cir1,cir2);
					if(cnt%cd!=0) cout<<-1<<endl;
					else{
						long long cnt1=va[a1];
						long long cnt2=vb[a2];
						while(1)
						{
							if((cnt1>=cnt2)&&((cnt1-cnt2)%cir2==0)) 
							{
								cout<<cnt1<<endl;
								break;
							}
						else cnt1+=cir1;
						(此处省略6个括号)
	return 0;
}

  

第五题取值(numbers)

【问题描述】

现给你两个正整数n, m。请问有多少种对整数x1,x2,…xn的取值,使得等式x1+x2+x3+xn=m成立。你的赋值必须满足0 ≤ x1 ≤ x2 ≤ ... ≤ xn 。例如,当 m =3, n = 2 时, 共有 2 种取法,分别为( x1,x2 )=( 0,3 )或( 1,2 )。请输出答案除以10^8 + 7的余数。

【输入格式】

第一行为一个正整数T,表示数据组数。

接下来T 行,每行两个正整数,分别为m 和n。

【输出格式】

输出T 行,分别表示对每一组数据的答案除以10^8 + 7的余数。

【输入样例】

2

3 2

7 3

【输出样例】

2

8

【数据规模】

对于10%的数据,1<=n<=m<=10

对于30%的数据,1<=n<=m<=50

对于50%的数据,1<=n<=m<=100

对于100%的数据,T<=20,1<=n<=m<=300

    这题在当时我没有做出来,用了深度搜索,我没怎么考虑时间复杂度,而且也没有什么好的想法,于是草草地就交了,严重超时,估计只有五分。

    老师教我们用递归的方法。首先,把分配数字的问题形象化为分配小球。

 

    ans[7][5]为把7(m)个小球放入5(n)个篮子里的方案数,注意题目已经说到x1≤x2≤x3,也就是说,每一个篮子里面的球的数量都是递增的。

    按经验来说,放球这种问题都会有两种情况,分别是放或者不放。

    1、不放,那么就分为把7个球放到5-1=4个篮子里的子问题;

    2、放,那么后面的每一个篮子至少都要有一个球(因为是递增的关系),所以,就要把7-5=2个球,放进5个篮子里 的方案数了。

    最后的方案数就是把这两种情况都加起来即可。

    不过要注意边界问题,如果没有球,那么无论放进几个篮子里面的方案数都只有1。那么如果只有一个篮子,不管往里面放几个球的方案数也是1。

        同时还要注意一种情况,当球的数量(m)比篮子的数量(n)小的时候,是直接为m个球放入m个篮子的方案数,因为它的个数要从小到大,所以,如果你前面摆不满,后面不能摆。

       为了节省时间,还要用到记忆化搜索。

时间复杂度 O(n^2)

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring> 
using namespace std;
const int mod=100000007;
int r,m,n,ans[305][305];
void init()
{
	memset(ans,0,sizeof(ans));//每次都要初始化清零
} 
int f(int m,int n)
{
	if(m==0||n==1) return 1;//边界处理
	if(m<n) return f(m,m)%mod;	
	if(ans[m][n]!=0) return ans[m][n]%mod;//记忆化搜索
	return ans[m][n]= (f(m,n-1) % mod + f(m-n,n) %mod ) % mod;
}
int main()
{
	cin>>r;
	while(r)
	{
		r--;
		init();
		cin>>m>>n;
		ans[m][n]=f(m,n);
		cout<<ans[m][n]<<endl;
	}
	return 0;
}

  

第六题数对(pairs)

【问题描述】

给定一个正整数n。现在有一个有数对(a, b)组成的序列,其中1<=a<=n,|b|<=n。|b|表示b的绝对值。该序列称为优美的序列,当且仅当以下条件同时满足:

1. 所有的数对都不相同;

2. 对于每一个数对(a,b),a 和|b|不相同。

3. 对于每一个数对(a, b),若b>0,则它之前一定存在一个数对(a',b')满足a'=b 且b'=0;

4. 对于每一个数对(a, b),若b<0,则它之前一定不存在一个数对(a',b')满足a'=-b 且b'=0;

5. 对于所有相邻的数对(a1,b1),(a2,b2),满足b1 和b2 不同时为正数且不同时为负数且不同时为0;

请你求出最长的优美的序列的长度。

例如,当n=2 时,其中一个最长的优美的序列为(2 ,-1 ),(1 ,0 ),(1 ,-2 ),(2 ,1 ),(2 ,0 ),(1 ,2 ),长度为6。

【输入格式】

仅一行,一个正整数n。

【输出格式】

输出一个整数,如题所述。

【输入样例】

2

【输出样例】

6

【数据规模】

对于20%的数据,n<=4

对于80%的数据,n<=10^6

对于100%的数据,n<=10^8

    一开始看这题的时候,瞬间就蒙掉了,限制条件比较多,而且我把第二个不存在看成了存在,也耗了不少时间,审题要仔细啊。

    这题的条件比较复杂。我们不妨把每一组数对看成一个平面直角坐标系上面的点。比如样例中的(2,-1)则为横坐标为2,纵坐标为-1的点。

    那么在坐标系上,要求分别为

    1、不能为(1,-1)(1,1)(2,-2)(2,2),因此可得,不能在两个角的平分线上。

    2、如果纵坐标y大于0,那么在x轴上一定要存在一个x=y的点。

    3、如果纵坐标y小于0,那么在x轴上x=y的点不存在。

    4、不能一直区正半轴或x轴 或下半轴

    我们先从样例给出的2开始看起。

(颜色没搭好)

    红点表示对角线,也就是条件1,是不可以有点的位置。因为2的范围太小,不容易观察出规律,再考虑一下3。



          通过上图可以发现,应该先确定下面的点,尽量把下面哪那一行区满,然后再取x轴上的点,并且只有取了x轴上的点以后再能取上半轴的点。

         我们会发现,只有下半轴第一行和上半轴最上面一行的有一个点,并且x坐标轴上有n个点,其余的都只有n-1个点。

         因此很容易可以推出:

         ans=2+n+(n-1)*(n-1)*2=2*n*n-3*n+4

         但是有特例,当n=1时,ans=1

         所以,貌似这就是一个找规律的题目,但是能否做出来的关键是,能否从数对转移到坐标轴。

时间复杂度:O(1)

代码如下:

#include<iostream>
#include<cstdio>
using namespace std;
long long n;
long long ans;
int main()
{
	cin>>n;
	if(n==1) 
	{
		cout<<1<<endl;
		return 0;
	}//判断特例
	ans=2*n*n-3*n+4;
	cout<<ans<<endl;
	return 0;
}

  代码很短,我也被惊到了。

posted @ 2017-08-19 15:39  yiyiyizqy  阅读(1173)  评论(2编辑  收藏  举报