湘南学院第一届程序设计大赛题解(XNPC—2022)

题解目录

A、你的马蹄铁是在另一只蹄子上吗?

题目大意:

题解:

代码:

B、判断奇因子

题目大意:

题解:

代码:

C、死亡祝福

题目大意:

题解:

代码:

D、奇怪的方程式

题目大意:

题解:

代码:

E、隐藏多余线

题目大意:

题解:

代码:

F、电量计算——大型LED屏

原题大意:

题解:

代码:

G、乐于助人的会长

题目大意:

题解:

代码:

H、矩阵字符串

题目大意:

题解:

代码:

I、拯救杂志

题目大意:

题解:

代码:

J、阶乘的可分性

题目大意:

题解:

代码:

K、寻找配对

题目大意:

注意:

解一(C语言版):

   C语言代码:

解二(C++版):

C++代码:

L、七对子

题目大意:

题解:

代码:

C++代码:

结语:


A、你的马蹄铁是在另一只蹄子上吗?

题目大意:

        给定四个数字(不一定不同),请你求  若要拥有四个不同的数字,还需要加入几个数字?(原题链接)

题解:

        这题的做法很多,你可以开一个很大的数组a 初始数组中的数全为0,然后出现一个数字i就令a[i] = 1;然后在再次遇到这个数字就令答案ans + 1,然后输出答案即可(可惜每个每个数字的范围都是1~1000000000的,开一个1e9的数组肯定会爆,所以还是不行)

        开个玩笑,进入正题:1.可以一直用if判断(只是代码量比较大,而且容易把自己绕晕)。2.首先将四个数存到一个数组里面,可以先用一个自己擅长的排序将数组中的数排好序,然后从左至右遍历一边,相同的数字肯定是相邻的,所以当a[i] = a[i + 1]时,答案ans就 + 1;

        3.其实也可以用两重循环将数组中的每个数与除了自己以外每个数做比较,遇到相同的数就令让这个数变为0,这样的话就可以避免排序,最后在看数组中有几个0,答案就是几(当然,会C++的同学也可以用C++中一些比较好用的函数,会更加简单)最后贴上代码:

代码:

#include <stdio.h>

int main()
{
    int a[4], ans = 0;;
    for (int i = 0; i < 4; i ++)    scanf("%d", &a[i]);
    
    for (int i = 0; i < 4; i ++)
        for (int j = 0; j < 4; j ++)
        {
            if(i == j)  continue;
            if (a[i] == a[j])   a[j] = 0;
        }
    for (int i = 0; i < 4; i ++)
        if (a[i] == 0) ans ++;
    printf("%d", ans);
    return 0;
}

B、判断奇因子

题目大意:

        给你一个数n让你判断n是否存在一个大于1的基数因子。存在就输出YES,否则输出NO(原题链接)​​​​​​​

题解:

        首先这题n的范围是2~10^14的,所以暴力肯定过不了,但是我们可以肯定的是当n是一个奇数,可以被自己整除,所以一定是YES的。那现在我们来看下面集中情况:

奇数 x 奇数 = 奇数
偶数 x 奇数 = 偶数
偶数 x 偶数 = 偶数

        ​​​​​​​所以当n是偶数时 也可能存在奇数因子的,所以我们只知道当一个偶数除以一个偶数存在一个大于1的奇数的时候,也是YES的。我们再来看看偶数的定义,一个能被2整除的数,是偶数。所以当n可以被2整除时,我们就除2,重复操作到n不能被整除的时候,若n > 1则为YES,否则为NO。(注意n很大,所以要用long long)

代码:

#include <stdio.h>

int main(void)
{
    int t = 1;  scanf ("%d", &t);
    while (t --)
    {
        long long  n;   scanf("%lld", &n);
        
        while (n % 2 == 0)  n /= 2;
        
        if(n > 1)   printf ("YES\n");
        else    printf ("NO\n");
    } return 0;
}

C、死亡祝福

题目大意:

        在你面前有n只怪物,第i个的血量为ai,同时也有bi的魔法值,当你杀死第i只怪物的时候,它会释放魔法值,令自己的邻居第i - 1和i + 1怪物的血量加bi,第一只和最后一只怪物的邻居只有一个,每杀死一个怪物,后面的怪物都会补上来形成一个新的排序。杀死血量为h的怪物需要耗时h秒,问你最快多少秒能杀死所有怪物。(原题链接)

题解:

        我们可以知道,要通关就必须杀死所有的怪物,所以对于每个怪物的血量我们是必须要加上的,我们需要做的就是如何让每个怪物的释放的魔法值最小呢,显然魔法值最大的我们肯定是要留到最后面,这样它就不会给自己的邻居加血了。

        又看题目可以知道,第一只和最后一只怪物只有邻居,相比于其他怪物题目所释放的魔法值只有bi,而其他怪物是2*bi,所以我们其实只需要从让讲魔法值最大的怪物留到最后,然后从最左边或者从最右边开始杀,直到只剩下魔法值最大的怪物,并杀死他,这样它的魔法值就会作废且这样的方案肯定是最优的。(注意答案需要用long long类型,因为ai和bi最大是10^9,n最大可以是200000)

代码:

#include <stdio.h>

int main(void)
{
    int t = 1;  scanf ("%d", &t);
    while (t --)
    {
        int n;  scanf("%d", &n);
        long long ans = 0, maxv = -1;
        
        for (int i = 1; i <= n; i ++) {
            int xx; scanf("%d", &xx);
            ans += xx;            // 将怪物的血量全部加上,这是不可避免的
        }
        for (int i = 1; i <= n; i ++) {
            int xx; scanf ("%d", &xx);
            if(xx > maxv)   maxv = xx;
            ans += xx;            // 先将所有的魔法值加上,到最后输出的时候减去最大的即可
        } printf ("%lld\n", ans - maxv);
    } return 0;
}

D、奇怪的方程式

题目大意:

        给定a, b, c求满足公式 x = b\cdot s(x)^{a}+c 的个数以x(0 < x < 10^9),其中s(x)是x的所有位数的和,例如s(123) = 1 + 2 + 3 = 6。(原题链接)

题解:

        因为数据范围很大(0 < x < 10^9),所以这题暴力肯定过不了,但是我们可以看最极限的情况:[1, 999999999],所以在这个区间内s(x)的范围是[1, 81],那我们就反向求。

        设i为s(x),所以我们就循环81次,算出 b\cdot i^{a}+c;设为xx,再判断s(xx) == i,若成立则xx为我们所求的解。(注意x的范围是(0, 0^9),当xx超出这个范围,则不满足条件)

代码:

#include <stdio.h>
#define inf 1e9                // 先定义好边界值

int ans[10000000], a, b, c;

void solve()
{
    int pos = 0;    
    scanf ("%d%d%d", &a, &b, &c);
    
    for (int i = 1; i <= 81; i ++) {
        long long xx = i;
        for (int j = 1; j < a; j ++)    xx = xx * i;        // 求s(x)的a次方
        
        xx = xx * b + c;
        if (xx >= inf || xx < 1)   continue;          // 算出来的答案超过x的范围了就舍弃
        
        long long res = xx, st = 0;
        while (res) {
            st += (res % 10);
            res /= 10;
        } if(st == i)   ans[++ pos] = xx;
    }
    if (pos == 0) {  printf("0\n"); return ;    }
    
    printf("%d\n", pos);
    for (int i = 1; i <= pos; i ++)    printf("%d " ,ans[i]);
    printf("\n");
    return ;
}
 
int main(void)
{
    int t = 1;  scanf ("%d", &t);
    while (t --)
        solve();

    return 0;
}

E、隐藏多余线

题目大意:

        你讲得到n个三元组,(Li, Hi, Ri)其中 Li 和 Ri 分别是建筑物 i 的左右边缘坐标,Hi 是建筑物 i 的高度。三元组可能会重叠,你要做的就是消除坐标系中的所有矩形图多余的部分,使其变成一个整体的图形,然后以坐标的形式输出。不太好简单描述,大家还看看原题吧👉👈。      (原题链接)

题解:

        因为坐标轴不会很大(≤ 1000),并且所给的矩形个数也不多(≤ 50);所以不要多想,暴力 直接暴力。

        我们建立一个数组ans,ans[i]表示这个点的高度;则初始高度全为0。每得到一组(Li, Hi, Ri)就从Li遍历到Ri,若ans[Li] ~ ans[Ri]中有小于Hi的坐标,就将其更新为Hi。最后再遍历ans数组,每个不同地方就是我们的分割点,将其i输出再输出ans[i]即可。

(注意:遍历Li到Ri的范围应该是[Li, Ri),因为输出时,遇到不同的高度时,当前点就算是与前面的高度相同,但也算做是下一个高度的开始部分。)

代码:

#include <stdio.h>
#define inf  1e9

int n, st[1010];

void solve()
{
    scanf ("%d", &n);
    int maxv = -inf, minv = inf;
    
    for(int i = 1; i <= n; i ++)
    {
        int l, h, r;
        scanf ("%d%d%d", &l, &h, &r);
        if(maxv < r)    maxv = r;
        if(minv > l)    minv = l;
        
        for(int j = l; j < r; j ++)            // 对高度进行更新
            if(st[j] < h)   st[j] = h;
    }
    
    int flag = -1;
    for(int i = minv; i <= maxv; i ++) {
        if(flag != st[i])
        {
            printf("%d %d ", i, st[i]);        // 输出高度改变的点以及从这里开始后面的高度
            flag = st[i];
        }
    } printf("\n");
    return ;
}

int main(void)
{
    int t = 1;  // scanf("%d", &t);
    while (t --)
        solve();
    return 0;
}

F、电量计算——大型LED屏

原题大意:

        不太好简单描述  直接上原题......(原题链接)        

题解:

        纯纯模拟,直接按照题目的意思写就可以了,没有什么需要注意的。我们可以将这个题看成一个多组输入,直接处理到文件结尾。

        我们可以将输入分为三个部分:START、SCORE和END;可以分别写三个自定义函数,当遇到END的时候就直接输出答案,并将之前所有的变量初始化。

用long long)

代码:

#include <stdio.h>
#include <string.h>
#define ll long long
#define N 1000

ll ans, start_time, now, end_time;      // 分别为总耗电、上个时间、现在的时间、结束时间
int home, guest, pos = 1;           // 分别为主场得分、客场得分以及输出时 这是Case pos;
char str[N];              // 自定义函数调用的标志;
int st[10] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};        // 预处理每个数字所需要的电量

void calc_Q(ll sum, int tt)                 // 计算得上次得分距这次得分  中间所消耗的电量;
{
    if(tt == 0)   ans += sum * st[0];
    else {
        while (tt)
        {
            int xx = tt % 10;
            ans += sum * st[xx];            // 计算出电量并加入到总电量中
            tt /= 10;
        }
    }
    return ;
}

ll calc_time(char s[])                  // 计算时间,将字符型的时间转换为整形
{
    int tt = 0;
    ll res = 0;
    for(int i = 0; i < strlen(s); i ++)
    {
        if(s[i] == ':') {
            res = (res + tt) * 60;          // 将时、分转换为秒
            tt = 0;
            continue;
        }
        tt = tt * 10 + (s[i] - '0');        // 将字符型时间转换为整型
    }
    return res + tt;    // 因为到时间中只有两个':'所以计算完小时和分钟后,秒数还没加,所以返回的时候加一下。
}

void start()                // 开始函数
{
    char s[N];  scanf("%s", s);
    start_time = calc_time(s);      // 计算开始时的时间
    
    return ;
}

void end()                  // 结束函数
{
    char s[N];   scanf("%s", s);
    
    now = calc_time(s);
    ll sum = now - start_time;
    calc_Q(sum, home);
    calc_Q(sum, guest);                 // 最后再计算一下耗电
    
    printf ("Case %d: %lld\n", pos ++, ans);
    
    home = guest = start_time = now = ans = 0;          // 将所有的变量归零,来进行下一个样例
    return ;
}

void score()                // 过程函数
{
    char s[N], ss[N];   int res;                // 分别为现在的时间、主场or客场以及得分
    scanf ("%s", s);    scanf ("%s", ss);
    scanf ("%d", &res);
    
    now = calc_time(s);                 // 计算现在时间,将其转换为秒制
    
    ll sum = now - start_time;          // 计算现在距离上次得分(或比赛开始)过去了多少秒
    calc_Q(sum, home);
    calc_Q(sum, guest);             // 分别计算主场客场所消耗的电量
    
    start_time = now;   now = 0;                // 更新时间
    if(ss[0] == 'h')    home += res;    // 主场得分
    else    guest += res;           // 客场得分
    return ;
}

void solve()
{
    if(str[0] == 'E')   end();
    else if(str[1] == 'C')  score();
    else    start();
    return ;
}

int main(void)
{
    int t = 1;  // scanf("%d", &t);
    while(~scanf("%s",str))
        solve();
    return 0;
}

G、乐于助人的会长

题目大意:

        你有字符串和m个查询,字符串s = s1 s2 s3 ...... Sn (n为字符串长度),仅由字符"."和"#"组层;每个查询都用一对整数li, ri(1 ≤ li < ri ≤ n)来描述。查询li, ri的答案是这样的整数i (li ≤ i < ri)的个数,即s[i] = s[i + 1]。        (原题链接)

题解:

        这题数据范围比较大,所以用暴力肯定过不了的。我们可以看题目,当s[i] = s[i + 1]时我们才有对答案的一个贡献。则当s = "...."时,答案为0 + 1 + 1 + 1 = 3;当s = "..#."时,答案为0 + 1 + 0 + 0 = 1;那我们再加长一点当s = "...#..#.###"时;答案为0 + 1 + 1 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + 1 = 5;

        所以我们我们对一个长度为n的字符串,只需要把字符串遍历一边若前后相同则答案 + 1,否则答案不变,那对于字符串s的子串,我们可以建立一个数组ans[i]就是第1到i个字符中对于答案的贡献,那么l~r则可以用前缀和的思想,答案则是ans[l] - ans[r];

代码:

#include <stdio.h>
#include <string.h>
#define N 100010

int ans[N], m;
char str[N];

void solve()
{
    scanf ("%s", str);   scanf ("%d", &m);
    
    ans[1] = 0;
    for (int i = 1; i < strlen(str); i ++)
        if(str[i] == str[i - 1])    ans[i + 1] = ans[i] + 1;
        else    ans[i + 1] = ans[i];
    
    while (m --)
    {
        int l, r;   scanf ("%d%d", &l, &r);
        printf("%d\n", ans[r] - ans[l]);
    }
    return ;
}

int main()
{
    int t = 1;  // scanf("%d", &t);
    while (t --)
        solve();
    return 0;
}

H、矩阵字符串

题目大意:

        在一个给定的二维字符阵列中寻找指定字符串,字符串出现的形式可能是水平、竖直、向前、向后和斜向。如果某个字符串在字符阵列中出现多次,则输出描述起点、终点坐标四个数字依次最小的一个。(原题链接)

题解:

        先看数据范围,矩阵的长宽都是小于等于80的,指定字符串数也是小于10的,给定字符串长度也是不会超过20的,所以可以暴力。

        但是如何暴力需要重新思考,因为题目说了,如果某个字符串在字符阵列中出现多次,则输出描述起点、终点坐标四个数字依次最小的一个。那么暴力的时候就要遵循这个条件了,所以在当前字符的八个方向中,寻找顺序应该是:左上、左、左下、下、上、右下、右、右上。这样的查找顺序,并且是一列一列地查找。

(注意:输出时也是线输出列号,再输出行号;输出的时候也要注意将空格和多余的换行移除)

代码:

#include <stdio.h>
#include <string.h>
#define N 85

char a[N][N], str[15][25];
int h, w, n;
int x, y, l;
int dx[8] = {-1, 0, 1, -1, 1, -1, 0, 1}, dy[8] = {-1, -1, -1, 0, 0, 1, 1, 1};       // 八联通:八个方向

int find(int t1, int t2, int i, int j, int u)       // t1 t2控制查找的方向;u表示查找给定字符串的第u个字符串
{
    x = i;  y = j;          // 从i, j开始查找
    int t = 0;                      
    while(a[x][y] == str[u][t])         // 若过相同就一直查找
    {
        t ++;           // 相同的字符数 +1
        if(t == l)  return 1;       // 若相同字符的个数已经字符串的长度,说明查找到了,返回1
        
        x += t1;  y += t2;      // 沿着这个方向继续查找
        
        if(x > h || x < 1 || y > w || y < 1)
            return 0;                           // 查找到了边界了还没查找到直接返回0
    }
    
    if(t < l) return 0;         // 若相同字符的个数不足字符串的长度,则不相同 返回0       
    
    return 1;
}

void solve(int u)
{
    l = strlen(str[u]);
    
    for (int j = 1; j <= w; j ++)
        for(int i = 1; i <= h; i ++)        // 一列一列地查找
            if(a[i][j] == str[u][0])                // 如果该字母是指定字符串的第一个字母,就可以开始查找
            {
                for(int k = 0; k < 8; k ++)             // 依次查找八个方向
                    if(find(dx[k], dy[k], i, j, u))
                    {
                        printf("(%d,%d)->(%d,%d)\n", j, i, y, x);
                        return;             // 若查到了,那一定是最优的解,直接输出并退出函数,查找下个字符串
                    }
            }
    return ;
}

int main()
{
    scanf ("%d%d", &w, &h);
    getchar();                      // 因为下面要输入字符,scanf输入字符可以识别换行,所以先把上次输出的换行移除
    for(int i = 1; i <= h; i ++) {
        for(int j = 1; j <= w; j ++)
        {
            char c = getchar();
            if (c == ' ' || c == '\n') {    j --;   continue;   }
                                                // 移除空格和回车
            a[i][j] = c;
        }
    }
    
    scanf ("%d", &n);
    for(int i = 1; i <= n; i ++)        scanf ("%s", str[i]);         
                                                    // 初始化字符矩阵 以及需要查找的字符串
    for(int i = 1; i <= n; i ++)
        solve(i);
        
    return 0;
}

I、拯救杂志

题目大意:

        在Duiaao面前有n个盒子,第i个盒子里面有ai本杂志,现在外面下起了雨,而有的盒子有盖子有的盒子没盖子,没有盖子的盒子里面的数就会被淋湿。第i个盖子(如果第i个盒子存在盖子的话)能且仅能移动到i - 1个盒子上,问你最多能保存几本杂志。(原题链接)

题解:

        本题如果你单纯的判断a[i] > a[i+1]并且a[i + 1]有盖子而a[i]没有就转移盖子的话,那你很快就会得到一个WA,一个很简单的例子当盖子编号为" 01110",杂志数为"10 20 30 5 50"时,应该将第二个移到第一个,第三个移到第二个,第四个移到第三个。而这是普通的比较前后两个盒子是做不到的。

        那到底怎么做呢,通过上面的一个样例我们可以看出来,一个一个移盖子是不可行的,因为有时候我们当前的盒子杂志数虽然没有后面的一个多,但是可能比i + 1 + 1 + 1 + 1的盒子中的杂志多,这个时候我们需要的就是将这个连续的盖子整体下移,那如何判断呢。其实,题目虽然说只能移动到i-1个位置上,但是当盖子编号为"01110"是,我们将最后一个盖子放到第一个就变成了"11100",这和整体下移没什么区别,只是理解上的差异。所以我们就可以这样写:

        当当前盒子没有盖子时,我们标记一下,当遇到连续的盖子时,连续盖子中但凡有一个盒子中的杂志数没有当前盒子中的多,那我们就将盖子下移。再将标记标到刚被一走的盒子上。

代码:

#include <stdio.h>
#define N 200010
#define ll long long

int n, a[N];
char st[N];

void solve()
{
    ll ans = 0;
    
    scanf("%d", &n);
    scanf("%s", st);
    for(int i = 0; i < n; i ++)     scanf("%d", &a[i]);
    
    int idx = -1;
    for(int i = 0; i < n; i ++)
    {
        if(st[i] == '0')    idx = i;
        else if(a[idx] < a[i] || idx == -1)  ans += a[i];
        else {
            ans += a[idx];
            idx = i;
        }
    } printf("%lld\n", ans);
    return;
}

int main()
{
    int t = 1;    // scanf("%d", &t);
    while (t --)
        solve();
    return 0;
}

J、阶乘的可分性

题目大意:

        给定一个整数x和一个整数数组a1, a2, …, an。你必须确定a1! + a2! + … + an!是否能被x!整除

(原题链接)

题解:

        将数组a中的每个数的阶乘相加,问是否能被x的阶乘整除。首先我们可以想一下:1! = 1那么1! + 1! = 2 * 1 = 2;    同理3! = 3 * 2 * 1, 则3! + 3! + 3! + 3! = 4 * 3! = 4 * 3 * 2 * 1 = 4!;  所以我们可以得到结论:xx * (xx - 1)! = xx!。这个结论有什么用呢,转换一下就是有xx个(xx - 1)的阶乘的和就是xx的阶乘。

        那么我们要想知道a数组中所有数的阶乘的和能否被x的阶乘整除,就要看数组中所有 ≤ x的数的和能否通过xx * (xx - 1)! = xx!来转换成x的倍数。看一眼数据范围ai ≤ 500000,就可以开一个500000的数组记录每个数出现的次数,就更加坚定了这个想法,直接上代码。

代码:

#include <stdio.h>
#define ll long long

ll n, x, cnt[500005];

void solve(){
    scanf("%d%d", &n, &x);
    for (int i = 1; i <= n; ++i) {
        int a;  scanf ("%d", &a);
        cnt[a] ++;
    }
    
    for (int i = 1; i < x; ++i) {           // 因为题目中ai是≤ x的,所以for只需要枚举到x - 1
        int j = i + 1;
        cnt[j] = cnt[j] + (cnt[i] / j);
        if (cnt[i] = cnt[i] % j) {
            printf("No\n");
            return ;                            // 这里知道无法被x!整除了,直接输出No并跳出
        }
    }printf("Yes");
    return ;
}

int main() {
    solve();
    return 0;
}

K、寻找配对

题目大意:

        给你一个包含n个不一定都不同整数的数组,那么其中(ai, aj)这样类似的对子就会有n*n个,对这些对子按ai数为第一关键字,aj为第二关键字来进行从小到大排序。例如{3, 1, 5},那么这些对子按照从小打大排序就会是:(1, 1)、(1, 3)、(1, 5)、(3, 1)、(3, 3)、(3, 5)、(5, 1)、(5, 3)、(5, 5)。现在问你在这些已排序的对子中,第k对对子是哪个。(原题链接)

注意:

        想必会有好多人会对数组进行模除操作,但是题目已经说过了,数组中的数是可能存在相同元素的。所以对于有些重复对子  是可以“插队”的。

解一(C语言版):

        分类讨论:

        1、数组没有重复元素:因为没有重复元素,那么数字对(x,y)就会按照x排序

例:
a=[1,2,3]
1, 1),(1,2),(1,3), (2, 1),(2,2),(2,3),(3,1),(3,2),(3,3)
可以发现第一关键字是 了个1,3个2,3个3这样的规律
首先对数宇对从O开始编号,所以对于输入的k = k - 1
设idx = k / n + 1
可以发现 idx 代表的就是 aridx]为数字对的第一关键字 (数组a升序的前提下)
对于第二个关健字,就只需要 设idx2 = k%n+1,那么aridx2〕 就是第二关键字

        2、数据具有重复元素

例:
a=[1,2,2]
(1, 1), (1, 2), (1,2),(2,1),(2,1),(2,2),(2,2),(2,2),(2,2)
首先k=k-1
可以发现对于第一关键宁,没有发生变化,所以还是设 idx =k/n+1为第1关键字确定了第一关键字之后,就可得到一个区间[1,r]代表a[1 ... r]的元素全部等于a[idx]
设cnt=-1+1
再观察第一关键宁为aridx1的数字对,可以发现其第二关键字是 cnt 个1,cnt 个2.cnt个2的规律
ar11之前有 1-1个数,所以前面共有num = (1 -1)*n个数字对,那么对应的最后一个数字对的编号为 num = num -1(从o开始编号)
设=k-nm

然后遍历数组,经过一个元素 dx -二cnt,那么当dx <= 0的时候,当前遍历的元素就是第二关键字

可发现,该做法同时可以解决无重复元素的情况

   C语言代码:

#include <stdio.h>
#define ll long long
#define N 100010

int n, a[N];
ll k;


void qsort(int a[], int  l, int r) {
	int i = l, j = r, key = a[i];
	if(i < j) {
		while(i < j) {
			while(i < j && a[j] > key)
				--j;
			if(i < j) {
				a[i] = a[j];
				++i;
			}
			while(i < j && a[i] < key) 
				++i;
			if(i < j) {
				a[j] = a[i];
				--j;
			}
		}
		a[i] = key;
		qsort(a, l, i-1);
		qsort(a, i+1, r);
	}
}


void init()
{
    scanf("%d%lld", &n, &k);
    for(int i = 1; i <= n; i ++)        scanf("%d", &a[i]);
    
    qsort(a, 1, n);             // 对a数组进行快速排序
    return ;
}

void solve()
{
    init();

    int x = (k - 1) / n;
    int l = 1, r;
    printf("%d ", a[x + 1]);
    
    while(l <= n){
        if(a[l] == a[x + 1]){
            r = l;
            while(r <= n && a[r] == a[x + 1]) r ++;
            r --;
            break;
        }
        l ++;
    }
    ll cnt = r - l + 1;
    ll dx = (l - 1) * 1ll * n - 1;
    dx = k - 1 - dx;
    
    for(int i = 1;i <= n;i ++){
        dx -= cnt;
        if(dx <= 0){
            printf("%d ", a[i]);
            break;
        }
    }
    return ;

}

int main()
{
    int t = 1;  // scanf("%d", &t);
    while (t --)
        solve();
    return 0;
}

解二(C++版):

        首先直接用map哈希表存储a数组中每个数字出现的次数,然后给a数组从小到大排序一遍,然后我们用变量idx对a数组遍历一边。因为排过序,所以只会出现两种情况:a[i] ≤ a[i + 1];我们对于这两种情况整合,因为我们在哈希表中存储了每个元素出现的次数。所以我们可以计算出每个元素的对子出现的个数(相同元素一并计算),就是map[a[i]] * n个的,我们将其k -= map[a[i]] * k;当k <= 0时,我们跳出循环。

        那么在这种情况下,a[idx]一定是我们所查询的对子的第一关键字,直接输出即可;此时k是<= 0的,此时再分三种情况:1、a[idx]在a数组中是否是唯一的,若是 那第二关键字就是a[k + n];2、我们的k是在减去a[idx]元素与小于等于a[idx]的元素组合成为对子的数量后小于等于0的  还是在减去a[idx]与大于a[idx]的元素组合成为对子的数量之后小于等于0的。(可能有点绕口,大家可以自己想一下,第二种情况可以分两种情况)。

        最后我们再将k < 0 回推到k >= 0时,再输出那个元素即可.

        对于第二段中的内容,我举一个样例{1, 2, 3 , 3, 4, 5},在这个样例中,3有重复元素,当我们遍历到这里的时候,第一个3与前面的1 2 3和它后面的3组合,而第二个3可以直接“插队”,与它前面的1 2 3 3组合(这个“前面”都包括自己)。当这些组合完之后,第一个3才会继续与后面的4 5组合,第二个3同理。

        所以在这这种情况下,我们需要知道相同元素“插队”对我们的结果是否有影响。

C++代码:


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 1e5 + 10;
map<int, int> mp;
int n, a[N], pos;
ll k;

void init()
{
    cin >> n >> k;
    for(int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        mp[a[i]] ++;            // 记录a数组中每个元素出现的次数
    }
    
    sort(a + 1, a + n + 1);                 // 排序
    return ;
}

void solve()
{
    init();
    
    int idx = 1;
    bool st = false;        // 用来记录a[idx]在a数组中出现不止一次的两种情况
    while (idx <= n)
    {
        ll tt = mp[a[idx]];
        if(tt > 1) {            // a[idx]在a数组中出现不止一次
            idx += tt - 1;          // 因为我们对相同元素一并计算了,所以我们让下标idx直接跳过相同元素
            k -= tt * idx;          // k减去与≤a[idx]的元素组成的对子数
            if(k > 0) { k -= tt * (n - idx);    st = true;  }
        }                                       // 若此时k仍然大于0,再减去与>a[idx]的元素组成的对子数 并且记录一下
        else    k -= n;
        
        if(k <= 0)  break;
        st = false; idx ++;     // 因为k减去与>a[idx]的元素组成的对子数后没有跳出循环,说明k > 0;
    }                           // 所以将st标回false;下表下移
    
    cout << a[idx] << ' ';
    
    if(mp[a[idx]] == 1) cout << a[k + n] << endl;       // 第一种情况:a[idx]在a数组中只出现过一次
    else if(st) {                           // k减去与>a[idx]的元素组成的对子数后 k <= 0
        k += mp[a[idx]] * (n - idx);    // 先一并加回来
        int pos = idx + 1;
        while (pos <= n )       // 然后从idx + 1处再慢慢减去相同元素与后面一个一个组合个数量
        {
            k -= ll(mp[a[idx]] * mp[a[pos]]);
            if(k <= 0)  break;      // 当k <= 0时就可以跳出,因为a[idx]出现过不止一次,一个一个组合时都是相同的对子,所以k < 0也是因为减去了相同滴对子数
            if(mp[a[pos]] > 1)  pos += mp[a[pos]] - 1;
            pos ++;     // 下标下移,上面那个循环是为了跳过在idx后面出现相同元素。
        }
        cout << a[pos] << endl;     // 直接输出
    }
    else {                  // k减去与≤a[idx]的元素组成的对子数后 k <= 0
        int pos = idx;
        while (pos >= 1)
        {
            k += ll(mp[a[idx]] * mp[a[pos]]);
            if(k > 0)   break;
            if(mp[a[pos]] > 1)  pos -= mp[a[pos]] - 1;
            if(pos == 1)    break;        // 当pos == 1时,只能是a[1]了 所以跳出
            pos --;
        }                           // 与上面的注释差不多,只是从向右减对子数变成了向左加对子数
                                    // 因为在上面,我们将k加回了令它<=0的状态,所以上面是k > 0,而这里k ≤ 0
        cout << a[pos] << endl;
    }
    return ;
}

int main(void)
{
//    clock_t start = clock(), finish;
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    // com();
    int t = 1;  // cin >> t;
    while (t --)
        solve();
     
//    finish = clock();
//    cout << "Run time: " << (finish - start) << "ms." << endl;
    return 0;
}

L、七对子

题目大意:

        这是一个人的麻将游戏,玩家只有你一个人。该麻将有1到9万饼索各四张,还有“东南西北白发中”各四张,初始你将获得十三张手牌,每局你都可以从牌堆中摸一张牌,回合结束前你需要打出一张牌,你要做的就是将自己的手牌变为七个对子(即有七对两两相同的牌)问:在采用最优策略的情况下你获胜的概率是多少,对最终答案进行1000000007的模除,得到期望轮数。(输入保证你的初始手牌没有大于两张相同的牌)。(原题链接)

题解:

        这题作为防AK,因为不会打麻将的根本无从下手。开玩笑的......这题不会打麻将的也能做,而且并不是特别难。

        有题我们可知:牌总数:(9+9+9+7)*4=136;开始时手中牌有13张,则牌堆有123张

        牌堆最少还有3张牌可以摸(少于三张百分之百结束游戏),此时摸牌最坏情况是,还差最后一个对子没有配成,牌堆的三张牌都相同,最多情况下牌堆有123张牌

        手牌最坏情况下起手牌中有0个对子(还差7个),最好情况下有6个对子(还差1个)

        摸牌时:如果摸到可以凑成对子的牌,将手中的任意一张单牌扔掉,否则将摸到的牌打出;可以证明这是最优策略。因为在其它情况运气不好,可能无牌可摸了也凑不出七对子。

        此时,我们设f[i][j] 表示牌堆中有i张牌,而手上还差j个对子达成七对子的期望轮数。即手中拥有2 * j - 1张单牌,则可以得到:

摸到对子牌的概率:        (3 * (2 * j - 1)) / i

摸到单牌的概率:            (i - 3 * (2 * j - 1)) / i

        状态计算:f[i][j]由上一状态  f[i-1][j-1]*(3*(2*j-1))/i 和 f[i-1][j]*(i-(3*(2*j-1)))/i转移而来,每次转移都需要加上消耗的本回合数。则状态转移方程为:

 f[i][j] = (f[i - 1][j - 1] + 1) * (3 * (2 * j - 1)) / i + (f[i - 1][j] + 1) * (i - 3 * (2 * j - 1)) / i;

        由于概率是分数,而结果需要对分数进行取模操作,则由乘法逆元的定义可知:

对于整数a, p且gcd(a, p) == 1,则一定存在唯一一个b满足a * b ≡ 1(mod p)。

        (gcd()为最大公约数,gcd(a, p) == 1,即a, p互质)

则:a * b = 1(mod p) =>  b = 1 / a(mod p)

又·根据费马小定理:b^(p-1) = 1(mod p) => b^(p-2) = 1 / b(mod p)

可以看出来逆元:1 / b (mod p) = b^(p-2)

可以得出a / b对质数p取模就是 a * b^(p-2) mod p 。

由题1000000007是一个质数,所以该公式任意的a都满足。

        有以上推导可知:f[123][7-ans]为所求,其中ans表示为当前手牌已有的对子数量。即牌堆中有123张牌,手上还差7-ans对对子。

代码:

#include <stdio.h>
#define ll long long
#define N 200
#define mod 1000000007

ll f[N][N];    // f[i][j] 表示有i张底牌,还差j个对子达成七对子的期望轮数
int flag[5][10], pos ;              // flag 标记该牌是否已经存在
char st[6] = {'.', 'm', 'p', 's', 'z'};      // 映射flag数组的行标

ll qmi(ll a, ll k)           // 公式a * b^(p-2) mod p中,求b^(p - 2);
{
    ll res = 1;
    while(k) {
        if(k & 1)
            res = (res * a) % mod;
        
        a = (ll)(a * a) % mod;      // 边乘边取模  以免爆变量类型内存
        k /= 2;
    }
    return res;
}

void starts()
{
    for(int i = 3;i <= 123; i ++)
        for(int j = 1; j <= 7; j ++) {
            ll st1 = (((f[i - 1][j - 1] + 1) * (3 * (2 * j - 1)) % mod) * qmi(i, mod - 2) % mod);
            ll st2 = (((f[i - 1][j] + 1) * (i - (3 * (2 * j - 1))) % mod) * qmi(i, mod - 2) % mod);
            f[i][j] = st1 + st2;
        }
    return ;        // 预处理出所有答案
}

int init()
{
    char str[25];   int ans = 0;
    scanf("%s", str);
    
    for (int i = 1; i <= 4; i ++)
        for(int j = 1; j <= 9; j ++) 
            flag[i][j] = 0;                     // 还原上一个样例已经拥有的牌的状态
    
    for(int i = 1; i < 26; i += 2) {
        int num = str[i - 1] - '0';
        char cc = str[i];
        for (int j = 1; j <= 4; j ++) {
            if (cc == st[j]) {
                if (flag[j][num])   ans ++;         // 若之前有过这张牌,则已有的对子数ans ++
                else    flag[j][num] = 1;
                break;
            }
        }
    } return ans;
}

void solve()
{
    int ans = init();
    printf("Case #%d: %lld\n", ++ pos, f[123][7-ans] % mod);
    return ;
}

int main(void)
{
    starts();
    int t = 1;  scanf("%d", &t);
    while (t --)
        solve();
    return 0;
}

下面放一个C++代码,思路和上面差不多,大家可以看看语法

C++代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 200, mod = 1e9 + 7;

ll f[N][N];    // f[i][j] 表示有i张底牌,还差j个对子达成七对子的期望轮数
int pos ;
map<string, bool> mp;

inline ll qmi(ll a, ll k)
{
    ll res = 1;
    while(k) {
        if(k&1)
            res = (res * a) % mod;
        
        a = (ll)(a * a) % mod;
        k >>= 1;
    }
    return res;
}

inline void start()
{
    for(int i = 3;i <= 123; i ++)
        for(int j = 1; j <= 7; j ++) {
            ll st1 = (((f[i - 1][j - 1] + 1) * (3 * (2 * j - 1)) % mod) * qmi(i, mod - 2) % mod);
            ll st2 = (((f[i - 1][j] + 1) * (i - (3 * (2 * j - 1))) % mod) * qmi(i, mod - 2) % mod);
            f[i][j] = st1 + st2;
        }
    return ;
}

int init()
{
    string str; cin >> str;
    int ans = 0;    mp.clear();
    
    for(int i = 0; i < str.size(); i += 2) {
        string st = str.substr(i, 2);
        if (mp[st]) ans ++;
        mp[st] = true;
    } return ans;
}

void solve()
{
    int ans = init();
    cout << "Case #" << ++ pos << ": " << f[123][7 - ans] % mod << endl;
    return ;
}

int main(void)
{
//    clock_t start = clock(), finish;
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
//    freopen("in.txt", "r", stdin);
//    freopen("out.txt", "w", stdout);
    start();
    int t = 1;  cin >> t;
    while (t --)
        solve();
    
//    finish = clock();
//    cout << "Run time: " << (finish - start) << "ms." << endl;
    return 0;
}

结语:

        本次“第一届程序设计大赛”为所有热爱编程的学生提供展示才能的平台。骄,败不馁 ,输赢不是最重要的,比赛中互相学习、互相进步才是最宝贵的奖品,让我们继续努力,加油学习,在交流中成长,在互动中进步。
        祝所有的ACMer不止 精彩不断”!!

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值