1040 有几个PAT (25分)
字符串 APPAPT 中包含了两个单词 PAT,其中第一个 PAT 是第 2 位( P),第 4 位(A),第 6 位(T);第二个 PAT 是第 3 位( P),第 4 位(A),第 6 位(T)。
现给定字符串,问一共可以形成多少个 PAT?
输入格式:
输入只有一行,包含一个字符串,长度不超过10^5 ,只包含 P、A、T 三种字母。
输出格式:
在一行中输出给定字符串中包含多少个 PAT。由于结果可能比较大,只输出对 1000000007 取余数的结果。
输入样例:
APPAPT
输出样例:
2
原题链接
思考
- 直接用暴力会超时,所以……
- 换个角度思考问题,对一个确定位置的A来说,以它形成的PAT的个数等于它左边P的个数乘以它右边T的个数。
- 例如对字符串APPAPT的中间那个A来说,它左边有两个P,右边有一个T,因此这个A能形成的PAT的个数就是2x1=2。
- 于是问题就转换为,对字符串中的每个A,计算它左边P的个数与它右边T的个数的乘积,
- 然后把所有A的这个乘积相加就是答案。
- 所以,只需要设定一个数组left,记录每一位左边P的个数(含当前位,下同)。
- 接着从左到右遍历字符串,如果当前位i是P,那么left[i]就等于left[i-_1]加1:
- 如果当前位i不是P,那么left[i]就等于left[i-L1]1。
- 于是只需要O(len)的时间复杂度就能统计出left数组。
- 以同样的方法可以计算出每一位右边T的个数。
- 为了节省代码量,不妨在统计每一位右边T的个数的过程中直接计算答案sum,具体做法是:
- 定义一个变量right,记录当前累计右边T的个数。
- 从右往左遍历字符串,如果当前位i是T,那么令right加1;
- 否则,如果当前位1是A,那么令sum加上left[i]与right的乘积(注意取模)。
- 这样,当遍历完字符串时,就得到了答案sum。
注意点
- 采用分别遍历P、A、T的位置来统计的方法会超时。
- 记得取模。
- 本题与PATB1045/A1101的思路很像,注意认真体会这两道题的思想。
AC1
#include <stdio.h>
int main(){
char str[100010];
int i,left[100010]={0}; //每一位左边含P的个数
gets(str); //读入字符串
int len=strlen(str); //长度
for(i=0;i<len;i++){
if(i>0) left[i]=left[i-1]; //继承上一位的位置
if(str[i]=='P') left[i]++;
}
int sum=0,right=0; //sum为答案,right记录右边T的个数
for(i=len-1;i>=0;i--){
if(str[i]=='T'){
right++;
} else if(str[i]=='A'){
sum=(sum+left[i]*right)%1000000007; //累计乘积
}
}
printf("%d\n",sum);
return 0;
}
AC2
#include <stdio.h>
int main(){
int LIM=1000000007;
int P=0,PA=0,PAT=0;
char c;
while((c=getchar())!= '\n'){
if(c=='P') P++;
if(c=='A') PA=(PA+P)%LIM;
if(c=='T') PAT=(PAT+PA)%LIM;
}
printf("%d",PAT);
return 0;
}
1019 数字黑洞 (20分)
给定任一个各位数字不完全相同的 4 位正整数,如果我们先把 4 个数字按非递增排序,再按非递减排序,然后用第 1 个数字减第 2 个数字,将得到一个新的数字。一直重复这样做,我们很快会停在有“数字黑洞”之称的 6174,这个神奇的数字也叫 Kaprekar 常数。
例如,我们从6767开始,将得到
7766 - 6677 = 1089
9810 - 0189 = 9621
9621 - 1269 = 8352
8532 - 2358 = 6174
7641 - 1467 = 6174
... ...
现给定任意 4 位正整数,请编写程序演示到达黑洞的过程。
输入格式:
输入给出一个 (0,10^4) 区间内的正整数 N。
输出格式:
如果 N 的 4 位数字全相等,则在一行内输出 N - N = 0000;否则将计算的每一步在一行内输出,直到 6174 作为差出现,输出格式见样例。注意每个数字按 4 位数格式输出。
输入样例 1:
6767
输出样例 1:
7766 - 6677 = 1089
9810 - 0189 = 9621
9621 - 1269 = 8352
8532 - 2358 = 6174
输入样例 2:
2222
输出样例 2:
2222 - 2222 = 0000
原题链接
思考
- 步骤1:写两个函数:int型整数转换成int型数组的toarray函数(即把每一位都当成数组的一个元素)、int型数组转换成int型整数的tonumber函数。
- 步骤2,建立一个while循环,对每一层循环:
- 用to_array函数将n转换为数组并递增排序,再用to_number函数将递增排序完的数组转换为整数MIN。
- 将数组递减排序,再用tonumber函数将递减排序完的数组转换为整数MAX。
- 令n=MAX-MIN为下一个数,并输出当前层的信息。
- 如果得到的n为0或6174,退出循环。
注意点
- 如果采用其他写法,容易发生问题的是6174这个数据可能没有输出,实际上应该输出:7641-1467=6174
- 如果某步得到了不足4位的数,则视为在高位补0,如189即为0189。
AC1——C++
#include <cstdio>
#include <algorithm>
using namespace std;
int i;
bool cmp(int a,int b){ //递减排序
return a>b;
}
void to_array(int n,int num[]){ //将n的每一位存到num数组中
for(i=0;i<4;i++){
num[i]=n%10;
n/=10;
}
}
int to_number(int num[]){ //将num数组转换为数字
int sum=0;
for(i=0;i<4;i++){
sum=sum*10+num[i];
}
return sum;
}
int main(){
int n,MAX,MIN,str[5];
scanf("%d",&n);
while(1){
to_array(n,str); //将n转换为数组
sort(str,str+4); //对num数组中元素从小到大排序
MIN=to_number(str); //获取最小值
sort(str,str+4,cmp); //对num数组中元素从大到小排序
MAX=to_number(str); //获取最大值
n=MAX-MIN; //得到下一个数
printf("%04d - %04d = %04d\n",MAX,MIN,n); //注意格式
if(n==0||n==6174) break; //如果下个数是0或6174则退出
}
return 0;
}
AC2——C
#include <stdio.h>
int cap(const void *a, const void *b){
return *(int*)b - *(int*)a;
}
int stor(int n){
int zoo[4] = {n/1000, n%1000/100, n%100/10, n%10};
qsort(zoo, 4, sizeof(int), cap);
return zoo[0] * 1000 + zoo[1] * 100 + zoo[2] * 10 + zoo[3];
}
int rever(int n){
return n/1000 + n%1000/100 * 10 + n%100/10 * 100 + n%10 * 1000;
}
int main(){
int n;
scanf("%d", &n);
do{
n = stor(n);
printf("%04d - %04d = %04d\n", n, rever(n), n - rever(n));
n = n - rever(n);
}while(n != 0 && n != 6174) ;
return 0;
}
1013 数素数 (20分)
令 Pi表示第 i 个素数。现任给两个正整数 M≤N≤10^4,请输出PM到PN的所有素数。
输入格式:
输入在一行中给出 M 和 N,其间以空格分隔。
输出格式:
输出从 PM 到 PN的所有素数,每 10 个数字占 1 行,其间以空格分隔,但行末不得有多余空格。
输入样例:
5 27
输出样例:
11 13 17 19 23 29 31 37 41 43
47 53 59 61 67 71 73 79 83 89
97 101 103
原题链接
思考
- 先给出两个正整数 M,N,输出第 M 个素数到第 N 个素数之间的所有素数,M<=N<=10^4
- 把素数表打至第N个素数,按格式输出。
注意
- 用筛法或者非筛法都可以解决该题,在算法中只需要添加一句控制素数个数的语可:
这是由于题目只要求输出第m~n个素数,因此超过n个素数之后的就不用保存了。if(pNum>=n) break;
- 小技巧:由于空格在测试时肉眼看不出来,因此如果提交返回“格式错误“,读者可以把程序中的空格改成其他符号(比如#)来输出,看看是哪里多了空格。
- 考虑到不知道第10^4个素数有多大,不妨将测试上限maxn设置得大一些,反正在素数个数超过n时是会中断的,不影响复杂度.当然也可以先用程序测试下第10 ^4个素数是多少,然后再用这个数作为上限。
- 本题在素数表生成过程中其实就可以直接输出,不过看起来会显得比较冗乱,因此还是应先生成完整素数表,然后再按格式要求输出。
- Find_Prime0函数中要记得是i<maxn而不是i<=maxn,否则程序一运行就会崩溃;在main函数中要记得调用Find_Prime0)函数,否则不会出结果。
#include <stdio.h>
typedef int bool; //自定义bool
#define false 0
#define true 1
int prime[1000001],pNum=0; //prime数组存放数组,pNum为素数个数
bool p[1000001]={0}; //如果i为素数,p[i]为false;否则为true
void Find_Prime(int n){
int i,j;
for(i=2;i<1000001;i++){
if(p[i]==false){
prime[pNum++] = i;
if(pNum >= n) break; //只需要 n 个素数,超过即可结束 ,核心!!
for(j=i+i;j<1000001;j+=i){
p[j]=true;
}
}
}
}
int main(){
int M,N,i,count=0;
scanf("%d %d",&M,&N);
Find_Prime(N);
for(i=M;i<=N;i++){
printf("%d",prime[i-1]); //下标从 0 开始
count++;
if(count%10!=0 && i<N) printf(" "); //每行10个,行末无多余空格
else printf("\n");
}
return 0;
}
1037 在霍格沃茨找零钱 (20分)
如果你是哈利·波特迷,你会知道魔法世界有它自己的货币系统 —— 就如海格告诉哈利的:“十七个银西可(Sickle)兑一个加隆(Galleon),二十九个纳特(Knut)兑一个西可,很容易。”现在,给定哈利应付的价钱 P 和他实付的钱 A,你的任务是写一个程序来计算他应该被找的零钱。
输入格式:
输入在 1 行中分别给出 P 和 A,格式为 Galleon.Sickle.Knut,其间用 1 个空格分隔。这里 Galleon 是 [0, 10^7] 区间内的整数,Sickle 是 [0, 17) 区间内的整数,Knut 是 [0, 29) 区间内的整数。
输出格式:
在一行中用与输入同样的格式输出哈利应该被找的零钱。如果他没带够钱,那么输出的应该是负数。
输入样例 1:
10.16.27 14.1.28
输出样例 1:
3.2.1
输入样例 2:
14.1.28 10.16.27
输出样例 2:
-3.2.1
原题链接
样例解释
-
样例1
- 就Knut来说,需要找钱28-27=1;就Sickle来说,1-16不够减,于是兑换1个Galleon为17个Sickle,这样Sickle就变成1+17=18个,需要找钱18-16=2;就Galleon来说,由于之前被兑换了1个,因此只剩下13个,需要找钱13-10=3。
-
样例2
- 在样例1的基础上加个负号即可。
思考
- 根据题意可知,1个Galleon可以兑换17x29个Knut,1个Sickle可以兑换29个Knut,因此直接把货币全部转换成Knut来计算。
- 于是第二个减去第一个即可得到要找的钱,假设为K。
- 由于此时单位是Knut,因此若要转换为原来的格式,就有
- K/(17x29)个Galleon,K%(17x29)129个Sickle,K%29个Knut。
注意点
- 获得Knut为单位的找零的钱K后要将它取绝对值,不能直接把负数直接代入后面的运算。
AC
#include <stdio.h>
int main() {
int Galleon, Sickle, Knut, P, A, change;
scanf("%d.%d.%d", &Galleon, &Sickle, &Knut);
P = (Galleon * 17 + Sickle) * 29 + Knut; //价钱
scanf("%d.%d.%d", &Galleon, &Sickle, &Knut);
A = (Galleon * 17 + Sickle) * 29 + Knut; //实付
change = A - P;
if (change < 0) {//钱不够
printf("-");
change = -change;
}
printf("%d.%d.%d\n", change / 29 / 17, change / 29 % 17, change % 29);
return 0;
}
1006 换个格式输出整数 (15分)
让我们用字母 B 来表示“百”、字母 S 表示“十”,用 12…n 来表示不为零的个位数字 n(<10),换个格式来输出任一个不超过 3 位的正整数。例如 234 应该被输出为 BBSSS1234,因为它有 2 个“百”、3 个“十”、以及个位的 4。
输入格式:
每个测试输入包含 1 个测试用例,给出正整数 n(<1000)。
输出格式:
每个测试用例的输出占一行,用规定的格式输出 n。
输入样例 1:
234
输出样例 1:
BBSSS1234
输入样例 2:
23
输出样例 2:
SS123
原题链接
思考
- 步骤1:考虑到需要从高位到低位进行枚举,因此不妨先将n的每一位存到数组中。
- 令int型ans数组存放h的每一位,num表示n的位数。
- 步骤2:对ans数组从高位到低位枚举:
- 如果是百位,则输出该位数字的个数的‘B’。
- 如果是十位,则输出该位数守的个数的‘S’。
- 如果是个位,则输出从1到该位数字。
AC1
#include <stdio.h>
int main(){
int n,i,j;
scanf("%d",&n);
int num=0,ans[5];
while(n!=0){ //将n的每一位存入ans数组
ans[num]=n%10;
num++;
n=n/10;
}
for(i=num-1;i>=0;i--){ //从高位到低位输出
if(i==2){ //如果是百位
for(j=0;j<ans[i];j++){
printf("B"); //输出ans[i]个B
}
} else if(i==1){
for(j=0;j<ans[i];j++){
printf("S"); //输出ans[i]个S
}
} else{ //如果是个位
for(j=1;j<=ans[i];j++){
printf("%d",j);
}
}
}
return 0;
}
AC2
#include<stdio.h>
int main(){
int N,i;
scanf("%d",&N);
int B = N/100; //存储百位
if(B != 0) { //输出B个B
for(i = 0;i < B;i++) printf("B");
}
int S = N % 100 / 10; //存储十位
if(S != 0) {
for(i = 0;i < S;i++) printf("S");
}
int G = N % 10; //存储个位
for(i=1;i<=G;i++) printf("%d",i);
return 0;
}