49. 有理数计算
本题模拟两个分数的加减成熟计算,难度不大,代码比较繁琐,按照算法笔记的套路写就行。
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long LL;
struct Fraction{
LL up,down;
};
LL gcd(LL a,LL b){
if(b==0) return a;
else return gcd(b,a%b);
}
Fraction reduction(Fraction f){
if(f.down<0){
f.up = -f.up;
f.down = -f.down;
}
if(f.up==0){
f.down=1;
}
else {
LL d=gcd(abs(f.up),abs(f.down));//这里要传绝对值,否则可能得到负因数
f.up/=d;
f.down/=d;
}
return f;
}
void showFraction(Fraction f){
if(f.up<0) printf("(");
if(f.down==1) printf("%lld",f.up);
else if(abs(f.up)>f.down){
printf("%lld %lld/%lld",f.up/f.down,abs(f.up)%f.down,f.down);
}
else {
printf("%lld/%lld",f.up,f.down);
}
if(f.up<0) printf(")");
}
Fraction add(Fraction f1,Fraction f2){
Fraction sum;
sum.up = f1.up*f2.down + f2.up*f1.down;
sum.down = f1.down*f2.down;
return reduction(sum);
}
Fraction reduce(Fraction f1,Fraction f2){
Fraction sum;
sum.up = f1.up*f2.down - f2.up*f1.down;
sum.down = f1.down*f2.down;
return reduction(sum);
}
Fraction multi(Fraction f1,Fraction f2){
Fraction product;
product.up = f1.up*f2.up;
product.down = f1.down*f2.down;
return reduction(product);
}
Fraction divide(Fraction f1,Fraction f2){
Fraction quotient;
quotient.up = f1.up*f2.down;
quotient.down = f1.down*f2.up;
return reduction(quotient);
}
int main(){
Fraction f1,f2;
scanf("%lld/%lld %lld/%lld",&f1.up,&f1.down,&f2.up,&f2.down);
f1 = reduction(f1);
f2 = reduction(f2);
Fraction result;
//计算并输出加法
result = add(f1,f2);
showFraction(f1);
printf(" + ");
showFraction(f2);
printf(" = ");
showFraction(result);
printf("\n");
//计算输出减法
result = reduce(f1,f2);
showFraction(f1);
printf(" - ");
showFraction(f2);
printf(" = ");
showFraction(result);
printf("\n");
//计算并输出乘法
result = multi(f1,f2);
showFraction(f1);
printf(" * ");
showFraction(f2);
printf(" = ");
showFraction(result);
printf("\n");
//计算输出触发
showFraction(f1);
printf(" / ");
showFraction(f2);
printf(" = ");
if(f2.up==0) printf("Inf");
else {
result = divide(f1,f2);
showFraction(result);
}
return 0;
}
素数
51. 反转质数(1015)
本题是判断给定数字及其在给定进制下的反转数字是否都是质数。核心就是两点:一是实现数字反转,二是质数判断。
数字反转只要先按进制提取数字,再反向乘上权重得到结果即可。
#include<iostream>
#include<cstdio>
#include<math.h>
using namespace std;
int radixTrans(int n,int radix){
//求该进制下的反转数,再以10进制返回
int a[40];
int num=0;
while(n!=0){
a[num++]=n%radix;
n/=radix;
}
int sum=0;
for(int i=0;i<num;i++){
sum=sum*radix+a[i];
}
//printf("%d的翻转数是%d\n",n,sum);
return sum;
}
bool isPrime(int n){
if(n<=1) return false;
int sqt =(int)sqrt(1.0*n);
for(int i=2;i<=sqt;i++){
if(n%i==0) return false;
}
return true;
}
int main(){
int n,radix;
int flag=0;
while(scanf("%d",&n)!=EOF){
if(n<0) break;
scanf("%d",&radix);
if(flag!=0) printf("\n");
flag=1;
int revN = radixTrans(n,radix);
if(!isPrime(n)||!isPrime(revN)){
printf("No");
}
else {
printf("Yes");
}
}
return 0;
}
语法:(1)sqrt()函数需要头文件<math.h>
52. 哈希(1078)
本题是模拟二次探查的哈希表,主要任务如下:
(1)找到与Msize最相近的质数作为哈希表大小,由于Msize规模不大,所以可以递增检验;
(2)二次探查,这里的探查规则应该是pos = (t+a*a)%Tsize,a从0开始直到Tsize-1(这里,我开始错误地当成pos = t%Tsize+a*a,并认为pos一旦超过Tsize就探查失败结束了,这也导致我最后一个测试点错误)。
#include<iostream>
#include<cstdio>
#include<math.h>
using namespace std;
int hashTable[10010]={0};
bool isPrime(int n){
if(n==1) return false;
int sqt = (int)sqrt(1.0*n);
for(int i=2;i<=sqt;i++){
if(n%i==0) return false;
}
return true;
}
int main(){
int n,m;
scanf("%d %d",&n,&m);
while(!isPrime(n)){
n++;
}
//printf("%d\n",n);
int t;
for(int i=0;i<m;i++){
scanf("%d",&t);
if(i>0) printf(" ");
int quare;
int pos;
for( quare=0;quare<n;quare++){
pos=(t+quare*quare)%n;
if(hashTable[pos]==0){//当前位置为空
printf("%d",pos);
hashTable[pos]=1;
break;
}
}
if(quare>=n) printf("-");
}
return 0;
}
53. 连续因子(1096)
本题要求找出给定数字的最长连续因子,如果有多个则输出较小的。要判断因子不难,难点在于如何找连续因子,我们可以设置中间变量存储连续因子乘积,如果它能被n整除就表示这一连串因子符合要求。
注意点:(1)N不能被除N以外的其它大于sqrt(N)的数整除,因此只需要在2-sqt内寻找,如果找不到因子,说明其是质数,直接输出N本身。
#include<iostream>
#include<cstdio>
#include<math.h>
using namespace std;
int main(){
int n;
scanf("%d",&n);
int sqt=(int)sqrt(1.0*n);
int len=0,num=0,pos;
int factor[100];
for(int i=2;i<=sqt;i++){
int tem=1;
int j=0;
while(1){
tem*=(i+j);
if(n%tem==0){
//可以整除,连续积长度加一
//printf("temp=%d,factor=%d\n",tem,i+j);
factor[num++] = i+j;//存储因子
j++;
}
else{
break;
}
}
//更新连续积最大长度
if(len<j){
len=j;
pos=num-j;//保存当前连续积存储起始位置
}
//i=i+j;
//这里不能跳跃,因为如180,2*3*4不是它的连续积,但3*4*5=60却是,一旦直接跳到5就会错误
}
if(len==0){//2-sqt中没有因子是质数
printf("1\n");
printf("%d",n);
}
else {
printf("%d\n",len);
for(int i=pos;i<pos+len;i++){
if(i>pos) printf("*");
printf("%d",factor[i]);
}
}
return 0;
}
54. 质因数分解(1059)
由于质因数分解需要使用大量质数进行判断,因此先用筛法计算出质数表备用。对于输入的数字,用质数表中的数字逐个进行测试,若是因子,则存储该因子并个数+1,n/=该质数。
对于1,特别输出1=1;对于合数,循环终止条件为n变为1;对于质数,当质数表中数字已经测试到大于其平方根就说明其是质数,可以结束循环了。
#include<iostream>
#include<iostream>
#include<cstdio>
#include<math.h>
using namespace std;
typedef long long LL;
struct factor {
LL x, cnt = 0;//x质因子,cnt为数量
}fac[20];
const int maxn = 100010;
LL prime[maxn], pNum = 0;
bool p[maxn] = { 0 };//i为素数则
void FindPrime() {//筛法找质数
for (int i = 2; i < maxn; i++) {
if (p[i] == false) {//说明是质数
prime[pNum++] = i;
//printf("%d\n", i);
for (int j = i ; j < maxn; j += i) {
//筛去当前质数倍数
p[j] = true;
}
}
}
}
int main() {
FindPrime();
LL n;
scanf("%lld", &n);
LL sqt = (LL)sqrt(1.0 * n);
int num = 0, i = 0;//质数总个数和种数
printf("%lld=", n);
if(n==1) {
printf("1");
return 0;
}
LL temp = 0;
while (n != 1 || sqt > prime[num]) {
if (n % prime[num] == 0) {
if (temp != prime[num]) i++;
fac[i].x = prime[num];
fac[i].cnt++;
n /= prime[num];
//printf("%lld,%lld,%d\n", fac[i].x,fac[i].cnt,i);
temp = prime[num];
}
else {//换下一个质数检测
num++;
}
}
for (int j = 1; j <=i; j++) {
if(j>1) printf("*");
if (fac[j].cnt != 1) {
printf("%lld^%lld", fac[j].x, fac[j].cnt);
}
else {
printf("%lld", fac[j].x);
}
}
return 0;
}
大整数运算
55. 双倍大整数(1023)
本题的任务点有二:一是求大整数乘2得结果,二是比较这两个数的数字出现频率是否完全一致。
先用字符串读入输入数字,再将其转化为大整数结构体,同时统计其数字频率,再对其执行乘2频率,并统计频率。
#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
int num1[10]={0},num2[10]={0};//统计0-9出现次数
struct bign{//大整数结构体
int d[100];
int len;
bign(){//构造函数
memset(d,0,sizeof(d));
len = 0;
}
};
bign change(char str[]){//把整数转化为bign
bign a;
a.len=strlen(str);
for(int i=0;i<a.len;i++){
a.d[i]=str[a.len-i-1]-'0';
num1[a.d[i]]++;
}
return a;
}
bign multi(bign a,int b){//本题仅需b=2,这里考虑一般情况
bign c;
int carry=0;//进位
for(int i=0;i<a.len;i++){
int temp =a.d[i]*b+carry;
c.d[c.len++]=temp%10;//有构造函数,c.len为0
num2[temp%10]++;
carry=temp/10;
}
while(carry!=0){//乘法的进位可能不止一位
c.d[c.len++]=carry%10;
num2[carry%10]++;
carry/=10;
}
return c;
}
void showBign(bign a){
//由于大整数是高位存在高位,应该反向输出
for(int i=a.len-1;i>=0;i--){
printf("%d",a.d[i]);
}
}
int main(){
char str[25];
scanf("%s",str);
bign BN = change(str);
bign doubleN = multi(BN,2);
int flag=0;
for(int i=0;i<9;i++){
if(num1[i]!=num2[i]){
flag=1;
break;
}
}
if(flag==0){
printf("Yes\n");
}
else{
printf("No\n");
}
showBign(doubleN);
return 0;
}
56. 回文数
本题要求判断给定数字在规定次数内,与其反转数相加能否得到回分数。因此核心问题是:实现回文数的判断和与反转数相加。虽然本题的数字并未超过long long的范围,但涉及大量位数的操作,适合采用大整数结构体。
#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
struct bign{
char d[100];
int len;
bign(){
memset(d,0,sizeof(d));
len=0;
}
};
bign change(char str[]){
bign a;
a.len=strlen(str);
for(int i=0;i<a.len;i++){
a.d[a.len-i-1]=str[i]-'0';//注意字符数字和真实数字转化-'0'
}
return a;
}
bool isPalindromic(bign a){
//if(a.len==1) return true;不需特判
int mid=a.len/2;
for(int i=0;i<mid;i++){
if(a.d[i]!=a.d[a.len-i-1]) return false;
}
return true;
}
void showBign(bign a){
for(int i=a.len-1;i>=0;i--){
printf("%d",a.d[i]);
}
}
bign addRev(bign a){//大整数与其反转数相加
bign c;
int carry=0;
for(int i=0;i<a.len;i++){
int temp=a.d[i]+a.d[a.len-i-1]+carry;
c.d[c.len++]=temp%10;
carry=temp/10;
}
if(carry!=0){
c.d[c.len++]=carry;
}
return c;
}
int main(){
char str[20];
int k;
scanf("%s%d",str,&k);
bign num=change(str);
int i=0;
while(!isPalindromic(num)&&i<k){
num=addRev(num);
i++;
;
}
showBign(num);
printf("\n%d",i);
}
A+BandC (1065)
这道题网上普遍的标答都是在数据修改之前的答案,之前数据没有涉及到2^63,实际最大只取到2^63-1,因此用long long读入不会溢出,现在修改数据后就不能通过了,因此改用通用的大整数按数组处理。
思路是:按字符串读入a,b,c后转化为大整数,本题存在负数,因此加设一个flag表示符号位,之后做a+b:
(1)a和b同号时,直接对绝对值做加法符号不变;
(2 )a和b异号时,用大的绝对值减去小的绝对值,结果符号与绝对值大的相同。
最后,比较a+b和c,按有符号数比较。
#include<cstdio>
#include<iostream>
#include<string.h>
using namespace std;
struct bign{
char d[100];
int len;
int flag;//1为正数或者0,-1为负,
bign(){
memset(d,0,sizeof(d));
len=0;
flag=1;
}
}bigA,bigB,bigC;
bign change(char str[]){
bign a;
a.len = strlen(str);
int len =a.len;
if(str[0]=='-') {
a.flag=-1;
a.len--;
}
for(int i=0;i<a.len;i++){
a.d[i]=str[len-1-i]-'0';
}
return a;
}
int compare(bign a,bign b){
//比较a和b的大小,a大、a等于,a小分别返回1、0、-1
if(a.flag!=b.flag){//a\b异号
return a.flag;
}
//同号情况
if(a.len>b.len) return a.flag;
else if(a.len<b.len) return -a.flag;
else{//长度相同时,自高位比较
for(int i=a.len-1;i>=0;i--){
if(a.d[i]>b.d[i]) return a.flag;
else if(a.d[i]<b.d[i]) return -a.flag;
}
}
return 0;
}
int cmpAbs(bign a,bign b){
//比较a和b的j绝对值大小,a大、a等于,a小分别返回1、0、-1
if(a.len>b.len) return 1;
else if(a.len<b.len) return -1;
else{//长度相同时,自高位比较
for(int i=a.len-1;i>=0;i--){
if(a.d[i]>b.d[i]) return 1;
else if(a.d[i]<b.d[i]) return -1;
}
}
return 0;
}
bign add(bign a,bign b){//这里的加法只考虑同号相加的情况
bign c;
c.flag=a.flag;
int carry=0;
for(int i=0;i<a.len||i<b.len;i++){
int temp=a.d[i]+b.d[i]+carry;
c.d[c.len++]=temp%10;
carry= temp/10;
}
if(carry!=0){
c.d[c.len++]=carry;
}
return c;
}
bign sub(bign a,bign b){
//这里的加法只考虑绝对值做减法的情况,且a>b
bign c;
for(int i=0;i<a.len||i<b.len;i++){
if(a.d[i]<b.d[i]){//不够减,需要借位
a.d[i+1]--;
a.d[i]+=10;
}
c.d[c.len++]=a.d[i]-b.d[i];
}
while(c.len-1>=1 && c.d[c.len]==0){
c.len--;//去除高位0
}
return c;
}
/*
void showBign(bign a) {
if(a.flag==-1) printf("-");
for (int i = a.len - 1; i >= 0; i--) {
printf("%d", a.d[i]);
}
printf("\n");
}*/
int main(){
int T;
scanf("%d",&T);
char a[100],b[100],c[100];
for(int i=0;i<T;i++){
scanf("%s%s%s",a,b,c);
bigA = change(a);
bigB = change(b);
bigC = change(c);
//先对a,b做加法
bign sumAB;
if(bigA.flag==bigB.flag){
sumAB=add(bigA,bigB);
}
else {//异号时做a,b绝对值的减法
int cmp=cmpAbs(bigA,bigB);
if(cmp==-1){//a<b时,交换a,b绝对值做减法
sumAB=sub(bigB,bigA);
sumAB.flag=bigB.flag;
}
else if(cmp==1){
sumAB=sub(bigA,bigB);
sumAB.flag=bigA.flag;
}
else{//互为相反数,单独讨论是为了避免负0
sumAB.len=1;
}
}
int cmpABC=compare(sumAB,bigC);
printf("Case #%d: ",i+1);
if(cmpABC==1){
printf("true\n");
}
else{
printf("false\n");
}
}
return 0;
}
语法:(1)注意形如for(int i=0;i<a.len||i<b.len;i++)中,有两个上界不要遗漏第二个i<,否则会导致导致循环条件错乱,无法输出。
vector使用
57. 学生的课程表(1039)
由于查询是按学生编号查询,因此存储时按其编号存储。根据学号的特性(前3位是大写字母,第4位是数字)想到散列,将学号读入转化为对应的数字存储,(这题如果用map映射会超时)。注意由于字母和数字在不会同时出现在同一位置,因此所需哈希表大小为26*26*26*10,不需要36^4。每名同学的课程数不确定,采用vector存储。
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int M=26*26*26*10+1;//学号大小上界
vector<int> stu[M];//学生维大小固定为M,课程数量不固定
int hashMap(char str[]){
int id=0;
for(int i=0;i<3;i++){
id = id*26 + (str[i]-'A');
}
id = id*10+str[3]-'0';//最后一位是数字,所以进制为10,
return id;
}
int main(){
int n,k;
scanf("%d%d",&n,&k);
char name[5];
for(int i=0;i<k;i++){
int course,numStu;
scanf("%d %d",&course,&numStu);
for(int j=0;j<numStu;j++){
scanf("%s",name);
int id=hashMap(name);
stu[id].push_back(course);//根据学号插入课程序号
}
}
for(int i=0;i<n;i++){
scanf("%s",name);
printf("%s ",name);
int id=hashMap(name);
sort(stu[id].begin(),stu[id].end());
int len=stu[id].size();
printf("%d",len);
for(int j=0;j<len;j++){
printf(" %d",stu[id][j]);
}
printf("\n");
}
return 0;
}
58. 上课的学生名单(1047)
本题是根据课程查询上课的学生名单,因此存储时按课程存储。不过本题不需要涉及姓名和数字间的转化,因为我们可以把名字存在字符数组中,只存储其字符数组中的序号即可。输出时先将姓名按其字母顺序排序。
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=40010,maxc=2510;
char name[maxn][5];
vector<int> course[maxc];
bool cmp(int a,int b){
return strcmp(name[a],name[b])<0;
}
int main(){
int n,k;
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++){
int numC,t;
scanf("%s%d",name[i],&numC);
for(int j=0;j<numC;j++){
scanf("%d",&t);
course[t].push_back(i);
}
}
for(int i=1;i<=k;i++){
int len=course[i].size();
printf("%d %d\n",i,len);
sort(course[i].begin(),course[i].end(),cmp);
for(int j=0;j<len;j++){
printf("%s\n",name[course[i][j]]);
}
}
return 0;
}
59. 集合重复率(1063)
本题是想确定两个集合中相同元素占总元素的比例。由于集合本身要求互异性,因此相到用set存储每个集合元素,查重只需要用遍历一个集合,看另一个集合是否有相同元素即可。
#include<iostream>
#include<cstdio>
#include<set>
using namespace std;
const int maxm=10010;
set<int> num[52];
void similarity(int x,int y){//输入查重set序号
int total=num[x].size(),same=0;
//遍历y的元素
for(set<int>::iterator it=num[y].begin();it!=num[y].end();it++){
if(num[x].find(*it)!=num[x].end()){//在x中找到与y相同的元素
same++;
}
else{
total++;
}
}
double rate=100.0*same/total;
printf("%.1f%\n",rate);
}
int main(){
int n,m,k;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&m);
int t;
for(int j=0;j<m;j++){
scanf("%d",&t);
num[i].insert(t);
}
}
scanf("%d",&k);
int t1,t2;
for(int i=0;i<k;i++){
scanf("%d%d",&t1,&t2);
similarity(t1,t2);
}
return 0;
}
语法:set的访问和常用函数:
(1)set的遍历访问需要迭代器,不能像vector、string那样方便用下标访问。
(2)num[x].find(*it)!=num[x].end(),find返回值不等于end(),则表示查找成功了,返回了其下标。
60. 它们相等吗
本题要求判断给定两个数据在给定精度下是否相等,并转化为科学计数法表示。确定一个0.a1a2a3...*10^e的科学计数法表示的数字,只需要确定a1a2a3的本体部分和e的指数部分。即核心任务如下:
(1)去除数据前面无效的0,如0000,00123.5
(2)确定输出的正确字符,不足的补0,过长的舍去
(3)确定正确的指数
而处理过程中发现又分为两种情况讨论:
(1)0.00...akak+1ak+2...。提取本体时,只需要ak开始的部分,而小数点
后面的0个数与指数e恰好为相反数。如0.002345 = 0.2345*10^-2
(2)(000)b1b2b3...bm.a1a2a3...。提取本体时先把先导0去除,只要b1b2..bma1a2...
部分,且m个数等于指数位e。这里有个特例,对0000这种,特例取e=0。
本题难点在于要分清楚各种情况,并恰当地对字符串进行处理。
#include<iostream>
#include<cstdio>
#include<string>
#include<string.h>
using namespace std;
int n;
string deal(string s,int &e){
//s是待处理字符串,e是指数位
//先去除先导0
string ans;
while(s.length()>0&&s[0]=='0'){
s.erase(s.begin());
}
if(s[0]=='.'){//处理形如0.001243的情况
s.erase(s.begin());
while(s.length()>0&&s[0]=='0'){
s.erase(s.begin());
e--;
}
}
else{//处理形如00123.45的情况
int i=0;
for( i=0;i<s.length();i++){
if(s[i]=='.') {
s.erase(s.begin()+i);
break;
}
}
e=i;//e=小数点前有效位数,若形如1234没有小数点,则在最后一位
}
if(s.length()==0){//对0000或000.00的e变回0
e=0;
}
int ansLen=0,k=0;
while(ansLen<n){//长度还未达到精度要求
if(k<s.length()){
ans+=s[k++];
}
else{
ans+='0';
}
ansLen++;
}
return ans;
}
int main(){
int e1=0,e2=0;
string s1,s2;
scanf("%d",&n);
cin>>s1>>s2;
s1 = deal(s1,e1);
s2 = deal(s2,e2);
if(s1==s2&&e1==e2){
cout<<"YES 0."<<s1<<"*10^"<<e1;
}
else {
cout<<"NO 0."<<s1<<"*10^"<<e1;
cout<<" 0."<<s2<<"*10^"<<e2;
}
return 0;
}