算法入门经典题目

第一章

  • const double pi = atan(-1.0);,尽量使用const而不是# define定义常量。
  • %03d,长度为3,不足的用0填充。
  • 变量值的交换:
// 引入中间变量
t = a;
a = b;
b = t;

// 不实用中间变量,只适用于数值型
a = a+b;
b = a-b;
a = a-b;
  • c/c++中使用的逻辑运算符均为短路运算符。
  • \n, \\, \', \", %%
  • 闰年:year%4==0&&year%100!=0 || year%400==0
  • 标准框架:
#include <bits/stdc++.h>
using namespace std;

const int maxn = 1000+10;
int haha[maxn];

int main()
{
    freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    //cout<<"time used:"<<(double)clock()/CLOCKS_PER_SEC;
    return 0;
}

第二章

  • 数据溢出:
    • int:32位,范围为-21亿~21亿左右。
    • long long:64为,范围最大。
  • 浮点数相等比较:由于浮点数存储有误差,所以不能直接利用比较运算符==进行比较,应该使用abs(a-b)<=1e-6来表示二者接近相等。
  • 结果取模:一般是由于计算的结果巨大无法直接表示。在计算中,模运算对+、-、*是等效的,对/是乘以它的逆元。
  • 输出运算的时间://cout<<"time used:"<<(double)clock()/CLOCKS_PER_SEC;
  • 变量在定义之后未赋初值,其值是随机的。特别地,它不一定为0。
  • 末行或末尾没有空行或换行:引入中间变量判断之。
  • 输入输出框架:
// 重定向
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);

// 文件读写
FILE *fin, *fout;
fin = fopen("in.txt", "rb");
fout = fopen("out.txt", "wb");
fscanf(fin, "%d", &a);
fprintf(fout, "%d\n", a);
fclose(fin);
fclose(fout);

// 字符数组读写
char haha[100];
sprintf(haha, "%d%d", a,b);
sscanf(haha, "%d%d", &a,&b);

习题

分数化小数

输入正整数a, b, c,输出a/b的小数形式,精确到小数点后c位。

  • 格式化输出的特殊用法:printf("%.*f\n", c, a/b)

排列

用1-9组成3个三位数abc,def和ghi,每个数字恰好用一次且abc:def:ghi=1:2:3。输出所有解。

  • 常量数组的运用。sprintf将内容输出到数组
#include <bits/stdc++.h>
using namespace std;

int main()
{
    char a[]="123456789";
    for(int i=123;i<=329;i++){
        int x=i,y=2*i,z=3*i;
        char buf[20];
        sprintf(buf,"%d%d%d",x,y,z);
        sort(buf,buf+9);
        if(strcmp(buf,a)==0) printf("%d %d %d\n",x,y,z);
    }
    return 0;
}

第三章

  • 定义大数组:比较大的数组的定义尽量声明在全局,这是为了防止栈溢出。
  • 数组的拷贝和赋值:由于数组初始定义时,里面的值不一定全为0,必要时需要赋初值和归零。
    • 拷贝:memcpy(b, a, sizeof(a))memcpy(b, a, sizeof(int))等。
    • 赋值:int a[100] = {0}memset(a, 0, sizeof(a))等。
  • ASCII表SP:32, '0':48, 'A':65, 'a':97
  • 字符数组常用函数:
    • strlen(), strcpy, strcmp, strcat, strchr(s, c), strstr(s1, s2)。其中,strchr, strstr返回的均是指针,不是位置数值。
    • isalpha, isdigit, isalnum, isupper, islower, toupper, tolower
  • 移位运算符:左移乘以2,右移除以2。
  • 黑盒测试与OJ:
    • WA(wrong answer), PE(presentation error), TLE(time limit exceeded), RE(runtime error), AC(accept)
    • 除0,栈溢出,数组越界,mian返回值不为0等常见错误。
  • UVA,一个辣鸡的OJ网站,严格要求输出格式也是醉了。下面的题目基本基于该OJ。

例题

开灯问题

有n个灯编号为1~n,开始时灯都是关着的。第i个人把编号为i的倍数的灯的开关按下(开着的灯熄灭,关着的灯打开)。一共有k个人,问最后有哪些灯开着。输入n, k,输出所有开着灯的编号。

  • 两个状态的交替变化:a = !a
  • 控制输出格式,收尾不能有空格或者收尾没有空行等:利用一个first变量控制。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100+10;
bool light[maxn];

int main()
{
    //freopen("in.txt", "r", stdin);
    int n,k;
    cin>>n>>k;
    memset(light, 0, sizeof(light));
    for(int i=1;i<=k;i++){
        for(int j=i;j<=n;j+=i) light[j] = !light[j];
    }
    bool first=true;
    for(int i=1;i<=n;i++){
        if(light[i]){
            if(first) first=false;
            else cout<<" ";
            cout<<i;
        }
    }
    //cout<<"time used:"<<(double)clock()/CLOCKS_PER_SEC<<endl;
    return 0;
}

蛇形填数

在nxn的矩阵中填入1, 2,…, nxn,要求填成蛇形。输入n,输出填充后的矩阵。例如,当n=4时,填入如下:

10 11 12 1
9 16 13 2
8 15 14 3
7 6 5 4
  • 采用先判断下一个空格是否满足填入条件再去,而不是先到空格上再判断是否满足填入条件。
  • 事前判断比事后判断所处理的逻辑更为简单,也更符合常理
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100+10;
int ans[maxn][maxn];

int main()
{
    //freopen("in.txt", "r", stdin);
    int n;
    cin>>n;
    memset(ans, 0, sizeof(ans));
    int pos=1, all=n*n;
    int i=0, j=n-1;
    ans[i][j]=pos;
    //当前位置已经填好,查看下一个位置是否满足条件
    //如果采取已经站在当前位置再去判断位置是否满足,这样逻辑就比较复杂
    while(pos<all){
        while(i<n-1 && !ans[i+1][j]) ans[++i][j]=++pos;
        while(j>0 && !ans[i][j-1]) ans[i][--j]=++pos;
        while(i>0 && !ans[i-1][j]) ans[--i][j]=++pos;
        while(j<n-1 && !ans[i][j+1]) ans[i][++j]=++pos;
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++)
            cout<<ans[i][j]<<" ";
        cout<<endl;
    }
    //cout<<"time used:"<<(double)clock()/CLOCKS_PER_SEC<<endl;
    return 0;
}

竖式问题

找出所有形如abc*de(三位数乘以两位数)的算式,使得在标准的小学列竖式中出现的所有数字均在给定的集合中。输入数字集合(没有空格),输出所有可能的abc和de。

  • 使用sprintf(buf, "%d", x)将内容输出到数组中,这样可以很方便的处理数据。
  • 在字符串中查找字符:strchr(s, c), s.find(c)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100+10;
char buf[maxn];

int main()
{
    //freopen("in.txt", "r", stdin);
    string s;
    cin>>s;
    for(int i=111;i<=999;i++){
        for(int j=11;j<=99;j++){
            int x=i*(j%10), y=i*(j/10), z=i*j;
            memset(buf, 0, sizeof(buf));
            sprintf(buf, "%d%d%d%d%d", i,j,x,y,z);
            int flag=1;
            for(int i=strlen(buf)-1;i>=0;i--){
                if(s.find(buf[i])==-1) {flag=0;break;}
            }
            if(flag) printf("%d %d %d\n", i,j,z);
        }
    }
    //cout<<"time used:"<<(double)clock()/CLOCKS_PER_SEC<<endl;
    return 0;
}

Tex中的引号

在Tex中,左双引号是“``”,右双引号是“’’”。输入一个正常英文文本,将其引号变换成Tex格式。

  • 观察题目类型,是否可以边输入边处理,或者边输入边判断等。
  • fgetc(fin)
    • fgetc(stdin)为从标准输入输出中读入单个字符,等同于getchar()
  • fgets(buf, maxn, fin)
    • fgets(buf, maxn, stdin)为从标准输入输出中读入完整一行。但是并不等同于gets(buf)
    • gets(buf)由于没有长度限制,会造成内存溢出。在新标准中已经弃用。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100+10;
char buf[maxn];

int main()
{
    freopen("in.txt", "r", stdin);
    char c;
    bool left=true;
    while((c=fgetc(stdin)) != EOF){
        if(c == '\"'){
            if(left) cout<<"``";
            else cout<<"\'\'";
            left=!left;
        }
        else cout<<c;
    }
    //cout<<"time used:"<<(double)clock()/CLOCKS_PER_SEC<<endl;
    return 0;
}

错位字符串(打表技巧)

在键盘上,手指稍不注意就会往右错一位。这样Q变成W,J变成K等。输入一个错位后的字符串,输出正确字符串。注意,输入的一定全为错位后的字符,比如就不会出现A。

  • 善于利用常量数组来减少逻辑判断等,可以使得条例清晰,代码简洁。
  • 将经常使用的数据保存在常量数组中,方便程序的查找。俗称打表
  • 打表是一种典型的用空间换时间的技巧,一般指将所有可能需要用到的结果事先计算出来,这样后面需要用到时就可以直接查表获得。打表常见的用法有如下几种:
    • 在程序中一次性计算出所有需要用到的结果,之后的查询直接取这些结果。这个是最常用到的用法,例如在一个需要查询大量Fibonacci数F(n)的问题中,显然每次从头开始计算是非常耗时的,对Q次查询会产生O(nQ)的时间复杂度;而如果进行预处理,即把所有Fibonacci数预先计算并存在数组中,那么每次查询就只需O(1)的时间复杂度,对Q次查询就值需要O(n+Q)的时间复杂度(其中O(n)是预处理的时间)。
    • 在程序B中分一次或多次计算出所有需要用到的结果,手工把结果写在程序A的数组中,然后在程序A中就可以直接使用这些结果。这种用法一般是当程序的一部分过程小号的时间过多,或是没有想到好的算法,因此在另一个程序中使用暴力算法去i出结果,这样就能直接在源程序中使用这些结果。例如对n皇后问题来说,如果使用的算法不够好,就容易超时,而可以在本地用程序计算付出对所有n来说n皇后问题的方案数,然后把算出的结果直接卸载数组中,就可以根据题目输入的n来直接输出结果。
    • 对一些感觉不会做的题目,先用暴力程序计算小范围数据的结果,然后找规律,或许就能发现一些“蛛丝马迹”。这种用法在数据范围非常大时候容易用到,因为这样的题目可能不是用直接能想到的算法来解决的,而需要寻找一些规律才能得到结果。
#include <bits/stdc++.h>
using namespace std;

char s[] = "`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./";

int main()
{
    freopen("in.txt", "r", stdin);
    char c;
    while((c=fgetc(stdin)) != EOF){
        if(c != ' ') cout<<s[strchr(s, c)-s-1];
        else cout<<c;
    }
    return 0;
}

回文串和镜像串

一个字符串:如果翻转后与原串相同则为回文串,例如abba, madam等;如果左右镜像后与原串相同则为镜像串,例如2s, 3AIAE。输入一个字符串,判断其是否为回文串以及镜像串。

所有可能的字符串如下:AA, B , C , D , E3, F , G , HH, II, JL, K , LJ, MM, N , OO, P , Q , R , S2, TT, UU, VV, WW, XX, YY, Z5, 11, 2S, 3E, 4 , 5Z, 6 , 7 , 88, 9

  • 需要两个步骤,判断回文和镜像。
  • 判断是否为回文串:利用首尾两个指针,依次判断是否相等,知道两个指针重合。
  • 判断是否为镜像串:根据已知的条件,构造两个字符数组或者一个字典。
  • 多重条件的答案输出,答案常量数组:答案有4种,分别为都不是、仅回文、仅镜像、回文和镜像
    • 设回文为m,镜像为n。0表示不是,1表示是。
    • 所以可以巧妙的根据排列组合和二进制关系可以推出。
    • 都不是、仅回文、仅镜像、回文和镜像 -> 00, 01, 10, 11
#include <bits/stdc++.h>
using namespace std;

char yuan[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
char xiang[] = "A---3--HIL-JM-O---2TUVWXY51SE-Z--8-";

// 答案常量数组
string ans[] = {"not a palindrome", "a regular palindrome", "a mirrored string", "a mirrored palindrome"};

int main()
{
    freopen("in.txt", "r", stdin);
    string s;
    while(cin>>s){
        short huiwen=1, jingxiang=1;
        for(int pos1=0, pos2=s.size()-1;pos1<=pos2;pos1++,pos2--){
            if(huiwen && s[pos1]!=s[pos2]) huiwen = 0;
            if(jingxiang && xiang[strchr(yuan, s[pos1])-yuan]!=s[pos2]) jingxiang = 0;
        }
        // 根据条件,选择一个答案输出
        cout<<ans[jingxiang*2+huiwen]<<endl;
    }
    return 0;
}

猜数字游戏的提示

给定答案序列和用户猜测序列,统计有多少数字位置正确(A),有多少数字在两个序列都出现过但位置不对(B)。输入包含多组数据。每组输入第一行为序列长度n,第二行是答案序列,接下来是用户猜测序列。猜测序列全0时表示改组数据结束。n=0时表示输入结束。

  • A中位置正确的数字不会再在B中统计。
  • 第一种思路就是双重循环遍历,第一遍遍历统计A,并把正确的位置数字排除;第二遍遍历统计B。但是这种方式在数据量较大时容易超时。
  • 第二种思路。对于A可以直接统计,但是B却不能直接遍历得到,容易超时。所以对于每个数字(1~9),统计它在答案序列和猜测序列中出现的次数c1和c2,则min(c1, c2)就是该数字对B的贡献,表示该数字在两个序列中都出现的次数,然后减去位置正确的A,剩下的就是都出现了但是位置不正确的次数B。
#include <bits/stdc++.h>
using namespace std;

const int maxn = 10000+100;
int standard[maxn];
int haha[maxn];
int a[10], b[10];

int main()
{
    freopen("in.txt", "r", stdin);
    int n, cnt=0;
    while(cin>>n && n){
        printf("Game %d:\n", ++cnt);
        // 答案序列
        memset(a, 0, sizeof(a));
        for(int i=0;i<n;i++){
            cin>>standard[i];
            a[standard[i]]++;
        }
        // 用户猜测序列
        // bool first=true;
        while(1){
            memset(b, 0, sizeof(b));
            int right[10]={0}, A=0, B=0, sum=0;
            for(int i=0;i<n;i++){
                cin>>haha[i];
                b[haha[i]]++;
                if(haha[i] == standard[i]) right[standard[i]]++;
                sum += haha[i];
            }
            if(sum == 0) break;
            for(int i=0;i<10;i++){
                A += right[i];
                B += min(a[i], b[i])-right[i];
            }
            printf("\t(%d, %d)\n", A,B);
        }
    }
    return 0;
}

生成元

如果x加上x的各个数字之和等于y,则x为y的生成元。输入包含T组数据。每组输入n(1<n<100000),输出n的最小元。无解时输出0。

  • 假设需要求n的生成元,显然需要遍历n-1个数。但是这样重复计算量过大,打表。
  • 打表的方法只需要一次性枚举100000内所有正整数m,标记以m为生成元的数为y,最后直接查表即可。速度可以大大提升。
#include <bits/stdc++.h>
using namespace std;

const int maxn = 100000+100;
int haha[maxn]={0};

int main()
{
    freopen("in.txt", "r", stdin);
    for(int i=0;i<=100000;i++){
        int x=i, y=i;
        while(x){
            y += x%10;
            x /= 10;
        }
        if(y<maxn && !haha[y]) haha[y] = i;
    }
    int t;cin>>t;
    while(t--){
        int n;cin>>n;
        cout<<haha[n]<<endl;
    }
    return 0;
}

循环序列

给定一个长度为n的环状串,分别从别个位置开始读串,找出字典序最小的表示方式。输入包含T组数据。每组输入一个长度为n的环状DNA串(只包含A, C, T, G)的一种表示方法,输出字典序最小的表示方法。

  • 对于环状序列,可以用i = (i+1)%n遍历。
  • 类比于寻找最小值,将当前最小的和遍历的一次比较即可。
#include <bits/stdc++.h>
using namespace std;

const int maxn = 100+100;
char s[maxn];

bool xiaoyu(int pos1, int pos2, int len)
{
    for(int i=0;i<len;i++){
        if(s[pos1] != s[pos2])
            return s[pos2] < s[pos1];
        pos1 = (pos1+1)%len;
        pos2 = (pos2+1)%len;
    }
    return false;
}

int main()
{
    freopen("in.txt", "r", stdin);
    int t;cin>>t;
    while(t--){
        cin>>s;
        int len=strlen(s), min_pos=0;
        for(int pos=1;pos<len;pos++){
            if(xiaoyu(min_pos, pos, len))
                min_pos = pos;
        }
        for(int i=0;i<len;i++){
            cout<<s[min_pos];
            min_pos = (min_pos+1)%len;
        }
        cout<<endl;
    }
    return 0;
}

习题

得分

给出一个由O和X组成的字符串(长度为1~80),统计得分。每个O的得分为目前连续O的个数,每个X得分为0。

  • 定义一个变量,记录当前连续O的个数,当碰到X时,其值归零。
#include <bits/stdc++.h>
using namespace std;

const int maxn = 80+100;
char haha[maxn];

int main()
{
    freopen("in.txt", "r", stdin);
    int t;cin>>t;
    while(t--){
        cin>>haha;
        int len=strlen(haha), cur=0, sum=0;
        for(int i=0;i<len;i++){
            if(haha[i] == 'O'){
                cur++;
                sum += cur;
            }
            else cur = 0;
        }
        cout<<sum<<endl;
    }
    return 0;
}

分子量

给出分子式(不带括号),求分子量。输入中只包含4中原子(C:12.01, H:1.008, O:16.00, N:14.01,单位g/mol)。例如C6H5OH为94.108g/mol。

  • 有点绕弯子,需要先到好的思路才行。
  • 根据分子式的特点,每一个原子后面都会有其对应的个数,只不过当个数为1时省略了。
  • 所以每次都以一个原子为标准,判断其后面的情况即可:是数字或者是字母与边界。
#include <bits/stdc++.h>
using namespace std;

map<char, float> atom;

int main()
{
    freopen("in.txt", "r", stdin);
    atom['C'] = 12.01;atom['H'] = 1.008;atom['O'] = 16.00;atom['N'] = 14.01;
    int n;cin>>n;
    while(n--){
        string fenzishi;cin>>fenzishi; // 保存分子式
        float sum=0; // 分子量
        // 每一个分子,后面都为其个数,只不过当个数为1时,1被省略了
        // 每次都以一个原子为标准,判断其后面的情况
        for(int pos=0;pos<fenzishi.size();pos++){
            char yuansu = fenzishi[pos]; // 当前原子
            if(pos+1==fenzishi.size() || isalpha(fenzishi[pos+1])){ // 原子为最后一个或者下一个仍然为原子,故该原子下标为1
                sum += atom[yuansu];
            }
            else{ // 下一个为数字,则表示该原子个数不为1
                int num=0;
                while(pos+1!=fenzishi.size() && isdigit(fenzishi[pos+1])){
                    num = num*10+(fenzishi[pos+1]-'0');
                    pos++;
                }
                sum += atom[yuansu]*num;
            }
        }
        printf("%.3f\n", sum);
    }
    return 0;
}

数数字

把前n(n<=10000)个正整数写在一起,数一数0-9各出现了多少次。输入n,输出10个数,分别是0~9的个数。

  • 思路很简单,这里用到了sprintf将数字输出到字符数组中,避免取各个位的计算。
  • 行末不包含多余空格:for(int i=0;i<=9;i++) printf("%d%c",ans[i],i==9?'\n':' ');
  • 坑爹的OJ,居然判断末行有无空格,格式要求严格。
#include <bits/stdc++.h>
using namespace std;

int main()
{
    freopen("in.txt", "r", stdin);
    int ans[10]={0};
    char temp[10];
    int t;cin>>t;
    while(t--){
        int n;cin>>n;
        memset(ans, 0, sizeof(ans));
        for(int i=1;i<=n;i++){
            sprintf(temp, "%d", i);
            for(int j=strlen(temp)-1;j>=0;j--) ans[temp[j]-'0']++;
        }
        for(int i=0;i<=9;i++) printf("%d%c",ans[i],i==9?'\n':' ');
    }
    return 0;
}

周期串

如果一个字符串可以由某个长度k的子串重复多次得到,则称该串为周期串。给定一个字符串(长度不超过80),输出其最小周期。

  • 总长度为80,所以可以枚举所有周期。
  • 假设当前周期为k,则k|len,并且s[i] == s[i%k]。前者用于优化常数,否则超时。
#include <bits/stdc++.h>
using namespace std;

int main()
{
    freopen("in.txt", "r", stdin);
    char s[100];
    int n;cin>>n;
    while(n--){
        cin>>s;
        int len = strlen(s);
        for(int k=1;k<=len;k++){ // 假设周期为k
            if(len%k == 0){ // k如果是周期,就要满足k|len,要不然超时。
                bool flag=true;
                for(int i=0;i<len;i++){
                    if(s[i] != s[i%k]) {flag = false;break;}
                }
                if(flag) {cout<<k<<endl;break;}
            }
        }
        if(n) cout<<endl;
    }
    return 0;
}

谜题

有一个5*5的网格,其中恰好有一个格子是空的,其他格子各有一个字母。一共有4中指令:A,B,L,R,分别表示把空格上、下、左、右的相邻字母移到空格中。输入初始网格和指令序列(以数字0结束),输出指令执行完毕后的网格。如果有非法指令,应输出This puzzle has no final configuration.例如,左图中执行ARRBBL0后,效果如右图所示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值