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; }
代码很短,我也被惊到了。