算法竞赛入门

蛇形填数

在这里插入图片描述
蛇形填数解决方案为暴力填数,从左上角的坐标开始按下、左、上、右填数,直至填满n*n个数。
技巧:初始化填数数组,在填数时根据值判断该坐标是否被填数。

#include <stdio.h>
#include <string.h>
const int MAXN = 10;
int a[MAXN][MAXN];
int main()
{
    int n;
    scanf("%d", &n);
    memset(a, 0, sizeof(a));//初始化
    int x = 0, y = n - 1;
    int temp = 1;
    a[x][y] = temp;
    while (temp < n * n)
    {
        //按照下、左、上、右填数
        //所有格子初始化为0,判断是否填入数字
        while (x+1 < n && !a[x+1][y])
            a[++x][y] = ++temp;
        while (y-1>=0 && !a[x][y-1])
            a[x][--y] = ++temp;
        while (x-1>=0 && !a[x-1][y])
            a[--x][y] = ++temp;
        while (y+1 < n && !a[x][y+1])
            a[x][++y] = ++temp;
    }
    for (int i = 0; i < n; ++i)
    {
        for (int j = 0; j < n; ++j)
        {
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }
    return 0;
}

竖式问题

在这里插入图片描述
该问题的核心是如何判断中间结果是否由指定集合元素组成?使用sprintf函数将所有中间结果存入字符串,检查该字符串的所有字符是否在给定集合出现过即可。已告知3位数*2位数,循环遍历检查即可。

#include <stdio.h>
#include <string.h>
int main()
{
    char buf[99], s[20];
    scanf("%s", s);
    int x, y, z;
    int count = 0;//计数器
    for (int abc = 111; abc <= 999; ++abc)
    {
        for (int de = 11; de <= 99; ++de)
        {
            x = abc * (de % 10);
            y = abc * (de / 10);
            z = abc * de;
            sprintf(buf, "%d%d%d%d%d", abc, de, x, y, z);//将中间结果全部存储到字符串中用于比较
            int ok = 1;//判断变量,1为符合要求,0为不符合要求
            for (int i = 0; i < strlen(buf); ++i)
            {
                if (strchr(s, buf[i]) == NULL)//检查buf[i]在指定串s中是否存在
                {
                    ok = 0;
                    break;
                }
            }
            if (ok)
            {
            	//输出格式
                printf("<%d>\n", ++count);
                printf("%5d\nX%4d\n-----\n%5d\n%4d\n-----\n%5d\n", abc, de, x, y, z);
            }
        }
    }
    printf("The number of solutions = %d\n", count);
    return 0;
}

sprintf函数

sprintf指的是字符串格式化命令,函数声明为 int sprintf(char *string, char *format [,argument,...]);,主要功能是把格式化的数据写入某个字符串中,即发送格式化输出到 string 所指向的字符串。sprintf 是个变参函数。使用sprintf 对于写入buffer的字符数是没有限制的,这就存在了buffer溢出的可能性。
返回值:
如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。

strchr函数

strchr函数功能为在一个串中查找给定字符的第一个匹配之处。函数原型为:char *strchr(const char *str, int c);,即在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。strchr函数包含在C 标准库 <string.h>中。
返回值:
该函数返回在字符串 str 中第一次出现字符 c 的位置,如果未找到该字符则返回 NULL。

最长回文子串

在这里插入图片描述
该问题的难点:

  1. 如何将字符串去符号,大小写?
  2. 如何检查是否回文?
  3. 原字符串和回文串的位置对应关系?

采用isalpha遍历检查原串去标点符号转换为统一的大小写toupper/tolower。回文检查遍历即可。原串和新串的位置对应关系,在新串生成时,便多使用一个数组记录字符在原串中相应的位置。

#include <stdio.h>
#include <string.h>
#include <ctype.h>
const int MAXN = 5000 + 10;
char buf[MAXN], s[MAXN];
int p[MAXN];
int main()
{
    fgets(buf, sizeof(s), stdin);
    int len = strlen(buf);
    int i, j, k, m = 0, maxlen = 0, x, y;
    for (i = 0; i < len; ++i)
    {
        if(isalpha(buf[i])) //如果是字母字符存入s数组
        {
            p[m] = i;//记录字符在原数组的位置
            s[m++] = toupper(buf[i]);//全部转化为大写,m为字母字符的个数
        }
    }
    for (i = 0; i < m; ++i)
    {
        for (j = i; j < m; ++j)
        {
            //遍历子串
            int ok = 1;
            for (k = i; k <= (i+j)/2; ++k)  //判断是否回文
            {
                if (s[k] != s[i+j-k])
                {
                    ok = 0;
                    break;
                }
            }
            if (ok && j-i+1 > maxlen) //如果回文且长度大于maxlen
            {
                maxlen = j-i+1;
                x = p[i];//x, y记录原串中起始位置
                y = p[j];
            }
        }
    }
    for (i = x; i <= y; ++i)
    {
        printf("%c", buf[i]);
    }
    return 0;
}

fgets函数

fgets函数功能为从指定的流中读取数据,每次读取一行。其原型为:char *fgets(char *str, int n, FILE *stream);从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

isalpha函数

一种函数:判断字符ch是否为英文字母,若为英文字母,返回非0(小写字母为2,大写字母为1)。若不是字母,返回0。在标准c中相当于使用“ isupper( ch ) || islower( ch ) ”做测试。

toupper, tolower函数

toupper,是一种计算机用语,用来将字符c转换为大写英文字母。

阶乘的精确值

在这里插入图片描述
简单的大数乘法,模拟手算即可,注意进位和前导0。

//1000!约等于4*10e2567, 用一个3000的数组f存储
//模拟手算,f[0]是个位,依次类推
#include <stdio.h>
#include <string.h>
const int MAXN = 3000;
int a[MAXN];
int main()
{
    int n;
    scanf("%d", &n);
    a[0] = 1;//个位为0
    int i, j;
    for (i = 2; i <= n; ++i)
    {
        int c = 0;//进位
        for (j = 0; j < MAXN; ++j)//模拟手算
        {
            int s = a[j] * i + c;
            a[j] = s % 10;
            c = s / 10;
        }
    }
    for (i = MAXN-1; i >= 0; --i)
    {
        if (a[i])//忽略前导0
        {
            break;
        }
    }
    for (j = i; j >= 0; --j)
    {   //逆序输出
        printf("%d", a[j]);
    }
    printf("\n");
    return 0;
}

排序与检索

6174

在这里插入图片描述
问题1如何获得下一个数(原数重排后的最大数-最小数)?数字转化为字符串排序再转换为数字
问题2如何检查这个数是否出现过?遍历即可

#include <stdio.h>
#include <string.h>
int a[1000];
int get(int n)
{
    int num2 , num1;
    char buf[10];
    sprintf(buf, "%d", n);//转换成字符串
    int len = strlen(buf);
    for (int i = 0; i < len-1; ++i) //冒泡排序
    {
        for (int j = 0; j < len-1-i; ++j)
        {
            if (buf[j] > buf[j+1])
            {
                int t = buf[j];
                buf[j] = buf[j+1];
                buf[j+1] = t;
            }
        }
    }
    sscanf(buf, "%d", &num1);//转化为数字
    for (int i = 0; i < len / 2; ++i)//逆序
    {
        int t = buf[i];
        buf[i] = buf[len-i-1];
        buf[len-i-1] = t;
    }
    sscanf(buf, "%d", &num2);
    return num2 - num1;//大数减小数
}
int main()
{
    scanf("%d", &a[0]);
    printf("%d", a[0]);
    int count = 0;
    int found = 0;
    while (1)
    {
        a[++count] = get(a[count-1]);//生成并输出下一个数
        printf("->%d", a[count]);
        for (int i = 0; i < count; ++i)//遍历寻找
        {
            if (a[i] == a[count])
            {
                found = 1;
                break;
            }
        }
        if (found)
        {
            break;
        }
    }
    return 0;
}

字母重排

在这里插入图片描述
该问题首先需要用一个二维数组存储单词组,然后对单词排序,用一个新二维数组存储重排单词,读入查询单词后重排与重排单词比较,输出原对应单词。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char a[1000][10], b[1000][10];
int cmp_string(const void *a, const void *b)
{
    return strcmp((char*)a, (char*)b);
}
int cmp_char(const void *a, const void *b)
{
    return *(char*)a - *(char*)b;
}
int main()
{
    int cnt = 0;//单词计数器
    while (1)
    {
        scanf("%s", a[cnt]);
        if (a[cnt][0] == '*')
        {
            break;
        }
        cnt++;
    }
    qsort(a, cnt, sizeof(a[0]), cmp_string);//所有单词排序
    for (int i = 0; i < cnt; ++i)
    {
        strcpy(b[i], a[i]);
        qsort(b[i], strlen(b[i]), sizeof(b[i][0]), cmp_char);//单词重排
    }
    char s[10];
    while (1)
    {
        scanf("%s", s);
        qsort(s, strlen(s), sizeof(s[0]), cmp_char);//识别单词重排
        int ok = 0;
        for (int i = 0; i < cnt; ++i)
        {
            if (strcmp(s, b[i]) == 0)
            {
                ok = 1;
                printf("%s ", a[i]);//输出原始单词
            }
        }
        if (!ok)
        {
            printf(":(");
        }
        printf("\n");
    }
    return 0;
}

数学基础

在这里插入图片描述
设三角形p0p1p2,三角形的有向面积为(x0*y1+x2*y0+x1*y2-x2*y1-x0*y2-x1*y0)/2,p0p1p2的3个顶点呈逆时针排列,那么有向面积为正;如果是顺时针排列,则有向面积为负;3点共线时,有向面积为零。

#include <stdio.h>
#include <string.h>
#include <math.h>
const double EXP = 1e-9;
double area2(double x0, double y0, double x1, double y1, double x2, double y2)
{
    return x0*y1+x2*y0+x1*y2-x2*y1-x0*y2-x1*y0;
}
int main()
{
    int cnt = 0;
    double x0, y0, x1, y1, x2, y2;
    scanf("%lf%lf%lf%lf%lf%lf", &x0, &y0, &x1, &y1, &x2, &y2);
    double s = area2(x0,y0,x1,y1,x2,y2)/2;
    for (int i = 1; i <= 99; ++i)
    {
        for (int j = 1; j <= 99; ++j)
        {
            double x = (double)i;
            double y = (double)j;
            double s1 = area2(x,y,x0,y0,x1,y1)/2;
            double s2 = area2(x,y,x1,y1,x2,y2)/2;
            double s3 = area2(x,y,x0,y0,x2,y2)/2;
            if (fabs(fabs(s)-fabs(s1)-fabs(s2)-fabs(s3)) <= EXP)//浮点数运算需要注意精度
            {
                cnt++;
            }
        }
    }
    printf("%d\n", cnt);
}

栈的使用

在这里插入图片描述
在这里插入图片描述
根据序列模拟栈,检查是否满足。

#include <cstdio>
#include <stack>
using namespace std;
const int MAXN = 1000+10;
int a[MAXN];
int main()
{
    int n;
    while (~scanf("%d", &n))
    {
        stack<int> s;
        for (int i = 1; i <= n; ++i)//注意下标从1开始
        {
            scanf("%d", &a[i]);
        }
        int ok = 1;
        int A = 1, B = 1;//A为A站元素,B为待检查序列位置
        while (B <= n)
        {
            if (A == a[B]) {A++;B++;}//表示该元素进栈后立即出栈
            else if (!s.empty() && s.top() == a[B]){s.pop();B++;}//栈不为空,当前位置元素满足栈顶元素,出栈
            else if (A <= n) s.push(A++);//元素压栈等待
            else {ok = 0; break;}//不满足条件
        }
        printf("%s\n", ok ? "Yes": "No");
    }
    return 0;
}

二叉树

在这里插入图片描述
在这里插入图片描述
直接模拟过程

#include <stdio.h>
#include <string.h>
const int MAXN = 20;//最大层数
int a[1<<MAXN];//最大结点数(1<<MAXN) - 1
int main()
{
    int D, I;
    while (~scanf("%d%d", &D, &I))
    {
        memset(a, 0, sizeof(a));
        int k;
        int m = (1 << D) - 1;//最大结点编号
        for (int i = 0; i < I; ++i)
        {
            k = 1;//从根结点模拟
            while (k <= m)
            {
                a[k] = !a[k];
                if (!a[k])
                {
                    k = 2 * k + 1;
                }
                else
                {
                    k = 2 * k;
                }
            }
        }
        printf("%d\n", k/2);
    }
}

每个球都会落在根结点,对于前两个球,必有一个进入左子加粗样式树,一个进入右子树。只要判断小球编号的奇偶性,就知道它最终在哪一棵子树中。
推而广之,对于每一个结点,只需要判断小球是第几个落在该结点的,就可以判断小球进入哪一棵子树。

#include <stdio.h>
int main()
{
    int d, n;
    while (~scanf("%d%d", &D, &I))
    {
        int k = 1;//小球落在结点的编号
        for (int i = 0; i < D - 1; ++i)//模拟掉落d-1次
        {
            if (I % 2)//I表示落在该结点的第I个小球
            {
                k = 2 * k;//落在左子树
                I = (I + 1) / 2;
            }
            else
            {
                k = 2 * k + 1;//落在右子树
                I /= 2;
            }
        }
        printf("%d\n", k);
    }
}

发布了51 篇原创文章 · 获赞 19 · 访问量 8306

猜你喜欢

转载自blog.csdn.net/WxqHUT/article/details/97407265