"扇贝杯"南邮第二届软件和信息技术专业人才大赛题解

题目差不多就是省赛的难度了,so......

A. UCU与SadBB的故事

在很久很久以前,大概也就是3年前,SadBB觉得他的ID过于愚蠢,所以决定换一个ID。于是一个奇怪的ID:UCU,横空出世。

大家觉得UCU一定有着奇怪的含义,比如说可能是一位美丽可爱的小姐姐的代号。

基于这样的猜测,某侦探开始了他的破案之旅,他认为UCU一定代表着,这个小姐姐的名字的第一个字和最后一个字的首字母是一样的,且第二个字的首字母为C。

他收集了SadBB认识的可爱的小姐姐的名字,筛选出了其中三个字的名字,并将她们名字转换成了拼音,眼看真相就要水落石出,来自东方的一股神秘力量将他封印了起来。

现在你拿到了这份名单(所有名字都被转换为拼音全拼了),你能帮忙寻找出这谜之可能存在的小姐姐吗?(本故事纯属虚构)

 

输入:

第1行为一个整数n,1<=n<=100

第2到n+1行,每行由三个拼音单词组成,拼音单词之间用空格隔开。

 

输出:

每行输出一个满足条件的名字的拼音,每个名字的三个拼音之间用空格隔开,名字的输出顺序应与输入顺序一致。

 

样例输入:

4

yu bu yu

jiang chun ji

guan jin chu

li chen liang

 

样例输出:

jiang chun ji

li chen liang


题目分析:这个题。。。直接跳过吧

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 0;i < n;i++){
        char ch[10];
        char name[10][100];
        for(int j = 0;j < 3;j++){
            scanf("%s",name[j]);
            ch[j] = name[j][0];
        }
        if(ch[0] == ch[2]&&ch[1] == 'c'){
           printf("%s %s %s\n",name[0],name[1],name[2]);
        }
    }
    return 0;
}



B. 大法师的故事

UCU接到了出题任务后毫无思路,苦思冥想数日无果后寻求某位可爱的小仙女的帮助。小仙女说:蓝桥杯这么喜欢考暴力,你不如出一道dfs啊。UCU恍然大悟,开始思考如何在题面里加入一位邪恶的大法师。

有一位邪恶的大法师,他有一个n*m的大花园,有一天他把自己的大花园,分成了一个个1*1的格子,所以,一个好好的大花园,就变成了一个n*m的格子阵。他开始布置自己的大花园,啊呸,格子阵。他希望自己的格子阵物尽其用,他打算把自己的格子阵,每两个连在一起,然后在这些1*2的小空间里,做自己爱做的事。

现在大法师希望你能帮助他算出,他有多少的操作空间,啊呸,他有多少的操作方案。注意,每个格子只能属于一个1*2的小空间,并且每个格子必须被划分到一个1*2的小空间。

两种划分方案由如下判定方式来判定是否相同:

如果存在一个格子,在A划分方案中与他相连的格子和在B划分方案中与他相连的格子是不同的,则认为两种划分方案A,B是不同的。

 

输入:

输入一行两个数n、m,用空格隔开。

0<n<=6

30%的数据0<m<=6

70%的数据0<m<=1000

保证n、m中必然有一个是偶数

 

输出:

输出一行方案个数。输出的方案数对1000007取模

 

样例输入:

2 2

样例输出:

2

 

题目分析:状态压缩动态规划,做法不止一种,可以让数字0表示当前位置为横着放的或者是竖着放的下面那块,1代表当前位置是竖着放的上面那块,设dp[i][j]表示第i行状态为j时的方法数,由于当前行的状态最多只受上一行的影响,因此递推的时候只需要考虑相邻两行的状态,根据前面的状态定义,不可能出现相邻行的同一列均为1的情况,判断时直接把两行的状态值与起来即可,然后还需要判断的当前行自身的状态是否合法,直接和上一行求异或即可得到当前行的状态,需要保证连续的两个1之前的0的个数为偶数即可,因为异或后0位置只可能代表横着放的情况(不知道异或什么意思的自行百度),然后横着放的话,空间为1*2,所以偶数个0才能填满

你没看错,代码就这么短 ->

#include <cstdio>  
#include <cstring>
int n, m, dp[1005][1 << 6];
  
bool ok(int t) {
    int cnt = 0; 
    for (int i = 0; i < n; i++, t >>= 1) { 
        if (!(t & 1)) {
            cnt ++;
        } else if (cnt & 1) {
            return false;
        }
    }  
    return !(cnt & 1);
}  

int main() {
    while(scanf("%d %d", &n, &m) != EOF) {  
        memset(dp, 0, sizeof(dp));  
        dp[0][0] = 1;  
        for (int i = 1; i <= m; i++) {
            for (int j = 0; j < (1 << n); j++) {
                for (int k = 0; k < (1 << n); k++) {
                    if (((k & j) == 0) && ok(k ^ j)) {
                        dp[i][j] = (dp[i][j] + dp[i - 1][k]) % 1000007;
                    }
                }
            }
        }
        printf("%d\n", dp[m][0]);  
    }  
}


C.疲惫的UCU

接近年末,连日的工作已经使UCU疲惫不堪,他已经无法充满激情的构思那些新鲜有趣的题目(面)了。

于是他开始回忆起了自己的ACM生涯,又想起了近日惨淡的KPI,看着每日的PV稳步下降,真希望是他自己眼花了,他想,要是眼前的这个曲线,是PV的倒数的曲线,该多好啊。

于是他对于整数的倒数的概念,觉得有趣了起来。

所以,你可以帮帮他吗,他想知道最近新上线的一个小众栏目的PV数的倒数的循环节长度是多少。首先这个PV必然是一个正整数,然后这个正整数的倒数,必然是一个循环小数,啊呸,必然是一个有限小数或者循环小数,如果是有限小数的话,循环节长度自然是0,如果是循环小数,循环节长度是多少呢?

 

输入:

一个正整数,也就是PV的值

0<PV<10000

 

输出:
一个正整数,即为PV的倒数的循环节,如果是有限小数,则输出0

 

样例输入:

2

样例输出:

0

 

样例输入:

3

样例输出

1


题目分析:首先想到PV中为2或5的全部质因子不会影响循环节(如果有)的长度,原因很简单,2和5本身就能被1除尽,所以先把因子2和5筛掉,然后就是这个数字1必然除不尽,所以直接用1除,其实就是用1乘10对PV取模,然后余数继续乘10对PV取模,反复操作,直到余数再次为1,循环的次数即为循环节的长度,不理解的话可以在纸上模拟运算一下比如1/7的过程。

#include <cstdio>

int calc(int a) {
    while (a % 2 == 0) {
        a /= 2;
    }
    while (a % 5 == 0) {
        a /= 5;
    }
    if (a == 1) {
        return 0;
    }
    int ans = 1, res = 10;
    while (res % a != 1) {
        res = res * 10 % a;
        ans ++;
    }
    return ans;
}

int main() {
    int a;
    while(scanf("%d", &a) != EOF){
        printf("%d\n", calc(a));
    }
}



D.数一数 

题目描述:

T:你知道怎么求1~n中奇数的个数吗?

Y:n/2+(n&1)

T:你知道怎么求1~n中3倍数的个数吗?

Y:n/3

T:你知道怎么求1~n中的奇数里3倍数的个数吗?

Y:n/3 – n/6

T:最后一个问题,你知道怎么求1~n中的奇数里数字3出现的次数吗?

Y:呃……

你能帮助Y解决第四个问题吗?

 

输入:

第一行为一个正整数T表示数据组数

接下来T行,每行一个正整数n

 

输出:

对每组数据,输出答案

 

样例输入:

3

3

13

33

 

样例输出:

1

2

6

 

数据范围:

1 <= T <= 10

1 <= n <= 10^18

 

题目分析:先不管奇数的限制,对每位考虑当前位上3出现的次数,假设计算到第i位,容易发现当i>1时,答案同时与高位,第i位和低位的数字有关
1)第i位数字小于3, 次数由高位决定, 比如120, 十位出现3的次数完全由百位的1决定, 为1说明3在十位只出现了10次(30~39), 如果是220, 十位出现了20次3 (30~39, 130~139)

2)第i位数字等于3, 次数由高位和低位同时决定, 比如133, 百位的1会决定十位的3出现10次(30~39), 个位的3会决定十位的3出现4次(130~133)

3)第i位数字大于3, 次数由高位决定,比如140, 百位的1会决定十位的3出现20次(30~39, 130~139)
求高位,低位,和当前位的方法推一推即可得到,最后在考虑奇数的限制,若当前位数不是个位,那么要求为奇数,显然直接除2即可,否则3在个位必然是奇数,不作处理

#include <cstdio>
#define ll long long
using namespace std;

ll sol(ll n) {
    ll ans = 0, base = 1, l = 0, cur = 0, r = 0;
    if (n <= 0) {
        return 0;
    }
    while (n / base != 0) {
        l = n - (n / base) * base;
        cur = (n / base) % 10;
        r = n / (base * 10);
        if (cur < 3) {
            ans += base == 1 ? r * base : r * base / 2;
        } else if (cur == 3) {
            ans += base == 1 ? r * base + l + 1 : (r * base + l + 1) / 2;
        } else if (cur > 3) {
            ans += base == 1 ? (r + 1) * base : (r + 1) * base / 2;
        }
        base *= 10;
    }
    return ans;
}

int main() {
    int T;
    ll n;
    scanf("%d", &T);
    while (T --) {
        scanf("%lld", &n);
        printf("%lld\n", sol(n));
    }
}



E. 算一算

题目描述:

Y是一个吃货,某天T带了n种食物给Y,假设每种食物都足够得多,因为有些东西混在一起吃可能会拉肚子,比如柿子和螃蟹,为方便起见,T规定Y只能选m个,每次只选某种食物的一份,并且每种食物都最多只能选k份,现在请你帮Y计算出共有多少种选食物的组合

 

输入:

第一行为一个整数T,代表数据组数、

第二行为三个整数n,m和k

 

输出:

对每组数据,输出选食物组合的总数,答案对1000000007取模

 

样例输入:

3

1 1 1

2 2 2

3 3 3

 

样例输出:

1

3

10

 

数据范围:

30%数据 1 <= n,m,k <= 1000,T = 5

70%数据 500000 <= n,m,k <= 1000000,T = 10


题目分析:这是一个典型的多重集组合加容斥的问题,先通过隔板法(自行百度)求出总的情况数C(n+m-1,m),然后用总的减去有至少1种元素超过k次加上至少有2种元素超过k次。。。有i种元素超过k次方案数是 C(n,i) * C(n+m-i*(k+1)-1,m-i*(k+1)),C(n+m-i*(k+1)-1,m-i*(k+1))表示种类数为n,次数为m-i*(k+1)的多重集组合,因为至少有i个超过k次,那么每个最少要分配k+1,所以剩下的次数就是m-i*(k+1),求组合数的时候直接预处理阶乘和阶乘逆元,最后注意两个点,1)直接取模算出来的答案可能为负,最后结果要加个MOD再取模;2)k>m时由于最多选m次,可直接将m的值赋给k

#include <cstdio>    
#define ll long long    
int const MOD = 1e9 + 7;    
int const MAX = 2e6 + 5;    
ll n, m, k;  
ll fac[MAX + 5], inv_fac[MAX + 5];  
        
ll qpow(ll x, ll n) {    
    ll res = 1;    
    while (n) {    
        if (n & 1) {
            res = (res * x) % MOD;
        }
        x = (x * x) % MOD;    
        n >>= 1;    
    }    
    return res;    
}    
  
void Init_fac() {  
    fac[0] = 1;  
    for (int i = 1; i <= MAX; i++) {
        fac[i] = (fac[i - 1] * i) % MOD;  
    }
    inv_fac[MAX] = qpow(fac[MAX], MOD - 2);  
    for (int i = MAX - 1; i >= 0; i--) {
        inv_fac[i] = (inv_fac[i + 1] * (i + 1) % MOD) % MOD;  
    }
}  
  
ll C(ll n, ll i) {
    if (i > n) {  
        return 0;
    }  
    return ((((fac[n] % MOD) * (inv_fac[i] % MOD)) % MOD) * (inv_fac[n - i] % MOD)) % MOD;  
}  
  
int main() {     
    Init_fac();
    int T;
    scanf("%d", &T);
    for (int ca = 0; ca < T; ca++) {
        scanf("%lld %lld %lld", &n, &m, &k); 
        if (k > m) {  
            k = m;  
        }
        ll ans = C(n + m - 1, m), sign = -1;  
        for (ll i = 1; i <= n; i++, sign = -sign) {  
            ll tmp = m - i * (k + 1);  
            if (tmp < 0) {
                break;  
            }
            ans = ((ans % MOD) + (sign * C(n, i) * C(n + tmp - 1, tmp)) % MOD) % MOD;  
        }  
        printf("%lld\n", (ans + MOD) % MOD);  
    }    
}



F. 排一排

题目描述:

Y所负责的项目的deadline就要到了,已知项目包含n个需求,每个需求需要一定的时间(单位为小时)去完成,现在Y的小组有m个组员,每个组员只能完成编号连续的若干需求,现在Y想知道如何安排这些组员的工作从而可以在最短的时间内完成全部需求,试求这个最短时间。每个组员的工作是同时进行的,且相互之间不会影响。

 

输入:

第一行为一个正整数T表示数据组数

每组数据的第一行为两个数字n,m,第二行为n个数字代表完成每个需求所需要的小时数。

 

输出:

对每组数据,输出一个整数,代表完成全部需求的最短时间

 

样例输入:

1

3 2

3 2 4

 

样例输出:

5


提示:

Y有两个组员

一个组员完成需求1和2要5小时

一个组员完成需求3要4小时

因此完成全部需求总共需要5小时,且没有更优的安排方案,故答案为5

注:一个组员不可能单独完成需求1和需求3,因为它们编号不连续

 

数据范围:

1 <= T <= 10

1 <= 小时数 <= 10000

60%数据 1 <= n <=1000;1 <= m <= n

40%数据 5000 <= n <=100000;1 <= m <= n


题目分析:二分答案,判断时让每个组员尽可能多的完成编号连续且总和不大于二分值的若干任务,如果需要的组员人数比题目所给的多,或是根本无法完成任务(比如某个任务需要的时间比当前的二分值大),则说明二分值偏小,否则说明二分值偏大

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[100005];
int n, m;


bool check(int x) {
    int i = 0, cur, cnt = 0;
    while (i < n) {
        cur = 0;
        while (i < n && cur + a[i] <= x) {
            cur += a[i];
            i ++;
        }
        if (cur == 0) {
            return false;
        }
        cnt ++;
    }
    return cnt <= m;
}

int main() {
    int T;
    scanf("%d", &T);
    while (T --) {
        scanf("%d %d", &n, &m);
        int sum = 0;
        for (int i = 0; i < n; i++) {
            scanf("%d", &a[i]);
            sum += a[i];
        }
        int l = 0, r = sum, mid, ans = 0;
        while (l <= r) {
            mid = (l + r) >> 1;
            if (check(mid)) {
                ans = mid;
                r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
        printf("%d\n", ans);
    }
}



G. 改一改

题目描述:

给一个由字符’T’和’Y’组成的字符串,由于特殊原因,某些字符已经看不清了,已知的是’T’和’Y’总想相互挨着彼此。假设看不清的地方可以改成’T’或’Y’的任意一个,试求最少会有几组’TT’和’YY’。

 

输入:

第一行为一个正整数T表示数据组数

每组数据为一个字符串S,看不清的地方用’?’表示。

 

输出:

对每组数据,输出一个整数,代表’TT’和’YY’最少出现次数的总和

 

样例输入:

3

TTTYY

??

Y?Y

 

样例输出:

3

0

0

 

提示:

对于样例的第二个字符串可以改写成TY或者YT

对于样例的第三个字符串可以改写成YTY

 

数据范围:

1 <= T <= 10

40%数据 1 <= |S| <=1000

60%数据 5000 <= |S|<= 1000000


题目分析:简单的动态规划问题,设dp[i][0/1]表示第i个字符串为T/Y时前i个字符串的答案,容易得到转移方程(不用解释了吧):
if s[i]=='T' then dp[i][0] = min(dp[i - 1][1], dp[i - 1][0] + 1)
if s[i]==''Y' then dp[i][1] = min(dp[i - 1][0], dp[i - 1][1] + 1)
if s[i]=='?' then  dp[i][0] = min(dp[i - 1][1], dp[i - 1][0] + 1)  
dp[i][1] = min(dp[i - 1][0], dp[i - 1][1] + 1)

初始条件:s[0]=='T' dp[0][0] = 0    s[0]=='Y' dp[0][1] = 0 s[0]='?' dp[0][0] = dp[0][1] = 0,其余均为无穷大

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
int const MAX = 1000005;
int n, dp[MAX][2];
char s[MAX];

int sol() {
	n = strlen(s);
    for (int i = 0; i < n; i ++) {
        dp[i][0] = dp[i][1] = 1000000000;
    }
    char fir = s[0];
    if (fir == '?') {
        dp[0][0] = 0;
        dp[0][1] = 0;
    } else if (fir == 'T') {
        dp[0][0] = 0;
    } else {
        dp[0][1] = 0;
    }
    for (int i = 1; i < n; i ++) {
        char ch = s[i];
        if (ch == '?') {
            dp[i][0] = min(dp[i - 1][1], dp[i - 1][0] + 1);
            dp[i][1] = min(dp[i - 1][0], dp[i - 1][1] + 1);
        } else if (ch == 'T') {
            dp[i][0] = min(dp[i - 1][1], dp[i - 1][0] + 1);
        } else {
            dp[i][1] = min(dp[i - 1][0], dp[i - 1][1] + 1);
        }
    }
    return min(dp[n - 1][0], dp[n - 1][1]);
}

int main() {
    int T;
    scanf("%d", &T);
    while (T --) {
        scanf("%s", s);
        printf("%d\n", sol());
    }
}




H. 凑一凑

题目描述:

T送给Y两颗宝石,价值分别为a和b,可Y都不喜欢,Y只想要一颗宝石,但它必须比那两颗都要珍贵。T感到很为难,于是他也想为难Y一下,并出了下面这样一个问题:

将a和b看做两个由’0’~’9’这10个数字组成的字符数组,你可以从这个两个字符数组中共挑选出k个字符重新凑成一个新数字,要求凑的时候不能打乱其在原字符数组中的相对顺序,比如a=832,b=891,可以选4个数字,答案是9832而非9883,由于将b中的’9’放在了第一位,而b中’8’在’9’的前面,因此新数字中的’9’之后不能再出现b中的’8’了,答案中’9’后面的’8’是a中的’8’,a中选出了3个字符’8’,’3’和’2’,它们在新数字中的相对位置没有改变。你能求出Y能够凑成的最大数字是多少吗?

 

输入:

输入第一行为一个整数T代表数据组数

每组数据的第一行为一个整数k

第二行为由’0’~’9’这10个字符组成的字符串a

第三行为由’0’~’9’这10个字符组成的字符串b

其中1<=|a|<=1000,1<=|b|<=1000,1<=k<=|a|+|b|

 

输出:

对每组数据,输出一个整数,代表所能凑成的最大数字

 

样例输入:

2

4

832

891

3

1

12


样例输出:

9832

121

 

数据范围:

1 <=T <= 5

30%数据 1<= |a|<= 100, 1 <= |b| <= 100

70%数据 800 <=|a| <= 1000,  800 <= |b| <= 1000

1<=k<=|a|+|b|


题目分析:先考虑一个长度为s的字符串的情况,选k个组成的答案最大,可以考虑用一个类似单调栈的数据结构维护一下,在当前枚举的字符大于栈顶且剩余字符足够凑满k的时候将栈顶弹出,时间复杂度为O(s)。现在有两个字符串s1和s2,容易想到利用归并排序的思想,在s1中选i个,则在s2中选k-i个,枚举s1中选择的个数时,简单推理即可得到i的范围是max(0, k-|s2|)~min(|s1|, k),然后通过贪心归并可以得到当前情况下的最优解,最后取全局最优解。合并时最坏的情况下(前面有很多相同数字且k接近两段总长的1/2时)复杂度会退化成O(n^2), 总的最坏的时间复杂度为O(n^3),其实实际上是到不了3方的,为了测试平台对Java的支持,这题标程就直接用Java写了

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

    public static int k;
    public static int[] a;
    public static int[] b;

    static class MyScanner {
        BufferedReader br;
        StringTokenizer st;

        public MyScanner(InputStream in) {
            br = new BufferedReader(new InputStreamReader(in));
            st = new StringTokenizer("");
        }

        public String nextLine() {
            try {
                return br.readLine();
            } catch(IOException e) {
                return null;
            }
        }

        public boolean hasNext() {
            while (!st.hasMoreTokens()) {
                String s = nextLine();
                if (s == null) {
                    return false;
                }
                st = new StringTokenizer(s);
            }
            return true;
        }

        public String next() {
            hasNext();
            return st.nextToken();
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

        public double nextDouble() {
            return Double.parseDouble(next());
        }
    }

    public static boolean judge(int[] x, int i, int[] y, int j) {
        while (i < x.length && j < y.length && x[i] == y[j]) {
            i++;
            j++;
        }
        if (j == y.length) {
            return true;
        }
        if (j < y.length && i < x.length && x[i] > y[j]) {
            return true;
        }
        return false;
    }

    public static String getMax(String s1, String s2) {
        int len = s1.length();
        for (int i = 0; i < len; i++) {
            char ch1 = s1.charAt(i);
            char ch2 = s2.charAt(i);
            if (ch1 == ch2) {
                continue;
            }
            if (ch1 > ch2) {
                return s1;
            } else {
                return s2;
            }
        }
        return s2;
    }

    public static String merge(int[] x, int[] y) {
        int i = 0, j = 0;
        String ans = "";
        while (i < x.length && j < y.length) {
            if (judge(x, i, y, j)) {
                ans += x[i];
                i++;
            } else {
                ans += y[j];
                j++;
            }
        }
        while (i < x.length) {
            ans += x[i];
            i++;
        }
        while (j < y.length) {
            ans += y[j];
            j++;
        }
        return ans;
    }

    public static int[] getNum(int[] x, int cnt) {
        int j = 0, len = x.length;
        int[] ans = new int[cnt];
        for (int i = 0; i < len; i++) {
            while (len - i + j > cnt && j > 0 && ans[j - 1] < x[i]) {
                j--;
            }
            if (j < cnt) {
                ans[j++] = x[i];
            }
        }
        return ans;
    }

    private static String sol(String s1, String s2) {
        int n = s1.length();
        int m = s2.length();
        a = new int[n];
        b = new int[m];
        for (int i = 0; i < n; i++) {
            a[i] = s1.charAt(i) - '0';
        }
        for (int i = 0; i < m; i++) {
            b[i] = s2.charAt(i) - '0';
        }
        String ans = "";
        for (int i = Math.max(0, k - m); i <= Math.min(n, k); i++) {
            int[] sa = getNum(a, i);
            int[] sb = getNum(b, k - i);
            ans = getMax(ans, merge(sa, sb));
        }
        return ans;
    }

    public static void main(String[] args) {
        MyScanner in = new MyScanner(System.in);
        int T = in.nextInt();
        for (int ca = 1; ca <= T; ca++) {
            k = in.nextInt();
            String s1 = in.next();
            String s2 = in.next();
            System.out.println(sol(s1, s2));
        }
    }
}



I. 赌一赌

题目描述:

德州扑克是一个知名度非常高的扑克游戏。牌有四种花色:黑桃(S)、红桃(H)、梅花(C)、方片(D)。每种花色有13张牌,从小到大分别为2、3、4、5、6、7、8、9、10、J、Q、K、A。

 

本题只考虑德扑中的三类牌型:

1)同花:同一花色的五张牌。

2)顺子:数值连续的五张牌。

3)同花顺:既是同花也是顺子。

这三种牌型的大小关系为:同花顺 > 同花 > 顺子。

 

给予RMB玩家一个新技能,开局可随机获得0、1、2、3、4甚至是5张Magic牌(用M表示),Magic牌可以变成你想要的任意数值任意花色的牌,现在请你确定给定牌型所能组成的最优结果。(注: A可以作为比K大的牌,同时在凑顺子的时候也可以当成1来使用)

 

输入:

第一行为一个正整数T表示数据组数

接下来T行,每行输入5个字符串,形式为”花色+数值”(Magic排除外)

 

输出:

对每组数据,输出所能组成的最优牌型,其中

同花输出”TH”,顺子输出”SZ”,同花顺输出”THS”,不能组成这三种之一的则输出”NN”

 

样例输入:

3

H2 H3 H4H5 H7

H2 S3 H4H5 M

H2 H3 SJDQ CA

 

样例输出:

TH

SZ

NN

 

数据范围:

1 <= T <= 5

 

题目分析:小模拟,DFS暴力枚举所有牌型,花色有4种,数值有14种,最多有5个M,但56^5的枚举显然是不行的,容易想到当cntM>3时,必然可以组成同花顺,因此最多只需要枚举56^3即可,注意要特殊处理A的情况,标程写的极其ugly,(可以忽略)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

struct CARD {
    char tp;
    int val;
    CARD(){}
    CARD (char t, int v) {
        tp = t;
        val = v;
    }
};

bool isFlush, isStraight, isStraightFlush;
CARD sCard[5];

bool cmp(CARD c1, CARD c2) {
    return c1.val < c2.val;
}

int getNum(char ch) {
    if (ch >= '2' && ch <= '9') {
        return ch - '0';
    }
    if (ch == '1') {
        return 10;
    }
    if (ch == 'J') {
        return 11;
    } else if (ch == 'Q') {
        return 12;
    } else if (ch == 'K') {
        return 13;
    }
    return 14;
}

bool judgeFlush(CARD *c) {
    char fir = c[0].tp;
    for (int i = 1; i < 5; i++) {
        if (c[i].tp != fir) {
            return false;
        }
    }
    return true;
}

bool judgeStraight(CARD *c) {
    if (c[0].val + 1 == c[1].val && c[1].val + 1 == c[2].val &&
            c[2].val + 1 == c[3].val && c[3].val + 1== c[4].val) {
        return true;
    }
    return false;
}

bool judgeStraightFlush(CARD *c) {
    return judgeFlush(c) && judgeStraight(c);
}

char getTp(int t) {
    if (t == 0) {
        return 'H';
    } else if (t == 1) {
        return 'S';
    } else if (t == 2) {
        return 'D';
    }
    return 'C';
}

void judgeAll(CARD *c) {
    if (isStraightFlush) {
        return;
    }
    for (int i = 0; i < 5; i++) {
        sCard[i] = c[i];
    }
    sort(sCard, sCard + 5, cmp);
    isStraightFlush = judgeStraightFlush(sCard);
    if (isStraightFlush) {
        return;
    }
    if (isFlush) {
        return;
    }
    isFlush = judgeFlush(sCard);
    if (isFlush) {
        return;
    }
    if (isStraight) {
        return;
    }
    isStraight = judgeStraight(sCard);
}

void DFS(CARD *c, int pos) {
    if (isStraightFlush) {
        return;
    }
    if (pos == 5) {
        judgeAll(c);
        if (c[4].val == 14) {
            c[4].val = 1;
        }
        judgeAll(c);
        return;
    }
    for (int j = 2; j <= 14; j ++) {
        for (int t = 0; t < 4; t ++) {
            c[pos].tp = getTp(t);
            c[pos].val = j;
            DFS(c,pos + 1);
        }
    }
}

int main() {
    int T;
    scanf("%d", &T);
    for (int ca = 1; ca <= T; ca++) {
        CARD c[5];
        memset(c, 0, sizeof(c));
        isFlush = false;
        isStraight = false;
        isStraightFlush = false;
        char t[5];
        int cnt = 0;
        for (int i = 0; i < 5; i++) {
            scanf("%s", t);
            if (t[0] != 'M') {
                c[cnt].tp = t[0];
                c[cnt ++].val = getNum(t[1]);
            }
        }
        DFS(c, cnt);
        if (!isStraightFlush) {
            for (int i = 0; i < cnt; i++) {
                if (c[i].val == 14) {
                    c[i].val = 1;
                    DFS(c, cnt);
                }
            }
        }
        if (isStraightFlush) {
            printf("THS\n");
        } else if (isFlush) {
            printf("TH\n");
        } else if (isStraight) {
            printf("SZ\n");
        } else {
            printf("NN\n");
        }
    }
}




J. 拼一拼

题目描述:

T给了Y一个宝箱,宝箱密码原本是一个字符串S(只包含小写字母),T觉得直接把密码告诉Y太没有挑战性了,于是他随机取了S的某些前缀(同一前缀可重复取)重新组成了一个新的字符串S’,然后他把S’告诉了Y,并说明真正的密码是一个最短的字符串S,你可以通过截取S的若干前缀将它们拼接成S’,你能帮Y破译出真正的密码吗?

 

输入:

第一行为一个正整数T表示数据组数

接下来T行,每行一个字符串S’

 

输出:

对每组数据,输出一个字符串,即真正的密码

 

样例输入:

3

abab

aaaaa

abaababc

 

样例输出:

ab

a

abc

 

数据范围:

1 <=T <= 6

40%数据1<= |S|<= 1000

60%数据 5000 <|S| < 1000000 


题目分析:这道题需要对kmp和next数组的概念非常清晰,假设输入的字符串为s,动态的维护答案串t,先将s[0]赋给t[0],之后s每加一个字符,就在t中匹配,失配的话就说明当前t已不能构造出s,则将t扩展,扩展的方式是在t后面加上上一次完全匹配的位置到这次失配位置的全部连续子串(因为题目要求只切分前缀),然后动态更新next数组,考虑到next的性质,更新的复杂度基本可以忽略,不会kmp的建议先去理解一下这个算法

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int const MAX = 2e6; 

char s[MAX], t[MAX];
int nxt[MAX];

int main() {
    int T;
    scanf("%d", &T);
    while (T --) {
        scanf("%s", s);
        memset(nxt, 0, sizeof(nxt));
        memset(t, 0, sizeof(t));
        int tot = -1, j = -1, pre = -1, len = strlen(s);
        for (int i = 0; i < len; i++) {
            while (j != -1 && t[j + 1] != s[i]) {
                j = nxt[j];
            }
            if (t[j + 1] == s[i]) {
                j ++;
                if (j == tot) {
                    pre = i;
                }
            } else {
                for (int k = pre + 1; k <= i; k++) {
                    t[++ tot] = s[k];
                    int idx = tot - 1 < 0 ? -1 : nxt[tot - 1];
                    while (idx != -1 && t[idx + 1] != t[tot]) {
                        idx = nxt[idx];
                    }
                    nxt[tot] = (t[idx + 1] == t[tot] && idx != tot - 1) ? idx + 1 : -1;
                } 
                j = tot;
                pre = i;
            }
        }
        printf("%s\n", t);
    }
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值