“湖南工学院2024寒假第一次训练赛” 题解

Problem A Cruel Summer

来源:"蔚来杯"2022牛客暑期多校训练营 1 G - Lexicographical Maximum
链接:G - Lexicographical Maximum
考察:思维
难度:橙题

本题考察同学们的思维能力,对于字典序而言,是从前往后依次对比大小关系,由于 1 − n 1-n 1n 之间的数长度并不固定,而题目需要我们求出最大的字典序。不难发现,若 n = 616 n=616 n=616,对于所有小于 616 616 616 的三位数的字典序均小于 616 616 616,那么 616 616 616 就是 1 − 616 1 - 616 1616 中的最大字典序吗?当然是错的,前面讲到字典序是根据对比每一位的大小关系而构造的,因此如果选 9 9 9,不难发现经过对比第一位 9 > 6 9>6 9>6,因此 616 616 616 的字典序是小于 9 9 9 的,而如果再选 91 91 91 去跟 9 9 9 比,会发现第一位是相同,但 91 91 91 的第二位为 1 1 1 9 9 9 没有第二位,因此 91 91 91 的字典序是大于 9 9 9 的,同理还可以选 99 99 99 对比,会发现 99 99 99 1 − 616 1-616 1616 中字典序最大的一个。因此我们应该尽可能寻找位数更长,从左往右 9 9 9 的位数更多的数。可以发现 99 99 99 的字典序是大于除 990 − 999 990-999 990999 以外的所有三位数的字典序的,所以给定一个 n n n,若其由 m m m 位构成,则我们仅需判断前 m − 1 m-1 m1 位是否均为 9 9 9,若均为 9 9 9,则说明 n n n 就是最大字典序,否则就是由 m − 1 m-1 m1 9 9 9 所构成的数是最大字典序。

#include<bits/stdc++.h>
typedef long long ll;
const ll mod = 1e9 + 7;
using namespace std;
int main() {
    string a;
    cin >> a;
    int f = 0; // 用于标记是否前 m - 1 位全为 9
    for (int i = 0; i < a.size() - 1; i++) {
        if (a[i] != '9') {
            f = 1;
            break;
        }
    }
    if (f) { // 若不是,则输出 m - 1 个 9
        for (int i = 0; i < a.size() - 1; i++) cout << 9;
    }
    else cout << a; // 否则直接输出 a
    return 0;
}

Problem B Enchanted

来源:第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(上海站)G - Fibonacci
链接:G - Fibonacci
考察:思维
难度:橙题

我们可以通过观察斐波那契数列的规律可知,一定会遵循 奇 奇 偶 奇 奇 偶 奇 奇 偶 奇 奇 偶… 的规律。而我们知道 奇数 * 奇数 = 奇数,奇数 * 偶数 = 偶数,偶数 * 偶数 = 偶数,因此我们实际上答案就跟斐波那契数列中偶数出现个数有关。因此我们需要先算出范围 n n n 内一共有多少个偶数,对于每个偶数它可以创造出 n − 1 n - 1 n1 的价值,于是答案就是 偶数个数 ∗ ( n − 1 ) 偶数个数 * (n - 1) 偶数个数(n1),那你就 w a wa wa 了,因为每个偶数创造出的价值可能会重复,比如 奇 奇 偶 奇 奇 偶 奇,第一个偶数创造了 6 6 6 的价值,因为他会跟除他以外的所有数相乘。但是当我们计算第二个偶数的时候,若依然按照以上公式,则会造成第二个偶数再次跟第一个偶数相乘,从而多出了一个答案,因此,我们算完总价值之后需要减去重复的价值,设 x x x 为偶数个数,而重复的答案价值实际上就是 ∑ i = 1 n ∑ j = i + 1 n \sum_{i=1}^n\sum_{j=i+1}^n i=1nj=i+1n 的种类数为 x ∗ ( x − 1 ) 2 \frac{x * (x - 1)}{2} 2x(x1),则答案为 x ∗ ( n − 1 ) − x ∗ ( x − 1 ) 2 x * (n - 1) - \frac{x * (x - 1)}{2} x(n1)2x(x1)。注意开 l o n g l o n g long long longlong

#include<bits/stdc++.h>
typedef long long ll;
const ll mod = 1e9 + 7;
using namespace std;
int main() {
	int t;
    cin >> t;
    while(t--){
        ll n;
	    cin >> n;
	    ll x = n / 3;
	    cout << x * (n - 1) - x * (x - 1) / 2 << endl;
    }
	return 0;
}

Problem C Getaway Car

来源:"蔚来杯"2022牛客暑期多校训练营 9 A - Car Show
链接:A - Car Show
考察:双指针
难度:橙题

本题是个典型的双指针,我们可以不断移动右端点 r r r 来判断区间 [ l , r ] [l,r] [l,r] 是否满足 1 1 1 ~ m m m 品质至少都存在一个,此时的 l l l 已经固定,当一个 r r r 满足条件时,则说明从当前 r r r 开始,后面的所有端点都为满足条件的右端点,说明从当前左端点 l l l 开始,右端点可以在 [ r , n ] [r, n] [r,n] 之间任选都符合要求,因此左端点为 l l l 的答案就是这个区间的长度,此时再去移动左端点,并将原来记录的左端点品质个数减 1 1 1,并判断该左端点移动后区间是否仍然满足条件,即 n o w now now 是否需要自减。因此只需要不断移动左右端点即可统计出答案,时间复杂度约为 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
typedef long long ll;
const ll mod = 1e9 + 7;
using namespace std;
int cnt[202020], a[202020]; // cnt 数组用于记录该品质在当前区间的数量
int main() {
    int n, m;
    cin >> n >> m;
    ll ans = 0; // 统计答案
    for (int i = 1; i <= n; i++) cin >> a[i];
    int now = 0; // 当前区间品质种数
    for (int l = 1, r = 1; l <= n; l++) {
        while (r <= n && now < m) { // 不断枚举右端点,直到满足条件或者右端点出界为止
            if (!cnt[a[r]]) now++; // 若该品质在当前 [l, r] 区间未出现过,则品质种数加一
            cnt[a[r]]++, r++; // 更新品质数量并更新右端点
        }
        if (now == m) ans += n - r + 2; // 若品质数满足 m 种,则统计答案
        cnt[a[l]]--; // 此时左端点向右移动,因此该点的品质数量减一
        if (!cnt[a[l]]) now--; // 若左端点移动后,该品质数量降为 0,则说明更新后的区间品质种类数会减少 1
    }
    cout << ans;
    return 0;
}

Problem D Blank Space

来源:AtCoder Beginner Contest 242 C - 1111gal password
链接:C - 1111gal password
考察:基础线性 d p dp dp d f s dfs dfs
难度:橙题

由题可知,相邻数位的绝对值不超过 1 1 1,因此如果使用 d p dp dp 去转移的话我们仅需考虑上一位的三种情况,即假设当前转移到第 i i i 位,且该位为 j j j,则可知由 i i i 位数字组成且最后一位为 j j j 的方案数共 d p [ i ] [ j ] dp[i][j] dp[i][j] 种,其由上一位也就是 i − 1 i-1 i1 位转移过来,由于绝对值不超过 1 1 1,因此 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j + 1 ] dp[i][j]=dp[i-1][j-1]+dp[i-1][j]+dp[i-1][j+1] dp[i][j]=dp[i1][j1]+dp[i1][j]+dp[i1][j+1],最后再将 n n n 位,以 1 − 9 1-9 19 结尾的所有答案加起来即为总个数。例如, x y 6 xy6 xy6 是由三位数字组成,且以 6 6 6 结尾,因此在 d p dp dp 数组中用 d p [ 3 ] [ 6 ] dp[3][6] dp[3][6] 表示,而其根据上述转移式可得知由 x 5 , x 6 , x 7 x5,x6,x7 x5,x6,x7 d p [ 2 ] [ 5 ] , d p [ 2 ] [ 6 ] , d p [ 2 ] [ 7 ] dp[2][5],dp[2][6],dp[2][7] dp[2][5],dp[2][6],dp[2][7] 转移而来,这样我们就计算出了长度为 3 3 3,以 6 6 6 结尾的符合条件的数字 X X X 的个数。记得在合适的位置取余,以防数据溢出。

另外也可以使用 d f s dfs dfs 求解,本质其实是差不多的,题解只展示线性 d p dp dp 的解法,其余方法请同学们自行研究实现。

#include<bits/stdc++.h>
typedef long long ll;
const ll mod = 998244353;
using namespace std;
ll dp[2020202][15];
int main() {
	int n;
	cin >> n;
	ll sum = 0;
	for (int i = 1; i <= 9; i++) dp[1][i] = 1; // 初始化 dp 数组
	for (int i = 2; i <= n; i++) { // i 代表当前总位数
		for (int j = 1; j <= 9; j++) { // j 代表当前数位的值
			dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j] + dp[i - 1][j + 1]) % mod;
		}   // 当前第 i 位由第 i - 1 位转移而来,差值绝对值小于等于 1,故为上述三个式子
	}
	for (int i = 1; i <= 9; i++) {
		sum += dp[n][i]; // 将第 n 位的所有答案求和
		sum %= mod; // 记得取余
	}
	cout << sum;
	return 0;
}

Problem E All Too Well

来源:AtCoder Beginner Contest 185 C - Duodecim Ferra
链接:C - Duodecim Ferra
考察:基础数学(组合数) 或 背包
难度:橙题

由题可知,将原始道路划分为 12 12 12 段,因此需要有 11 11 11 个分割点。而每段划分后都是整数长度,因此只能在整数点上分割,故一个长度为 n n n 的原始路段共有 n − 1 n - 1 n1 个分割点,可以从样例解释 1 1 1 中发现。那么题目就转化为在 n − 1 n - 1 n1 个点中选 11 11 11 个点的不同方案数,即计算组合数 C n − 1 11 C_{n-1}^{11} Cn111 为答案。

根据组合数学,我们可以推出递推式 C n m = C n − 1 m + C n − 1 m − 1 C_{n}^{m}=C_{n-1}^{m}+C_{n-1}^{m-1} Cnm=Cn1m+Cn1m1,先将数组第一层初始化后,利用递推式即可求出答案且不会发生数据越界;或者也可以根据组合数的计算公式 C n m = n ! ( n − m ) ! m ! C_{n}^{m}=\frac{n!}{(n-m)!m!} Cnm=(nm)!m!n! 用高精度直接计算或者利用快速幂 + 乘法逆元计算;再者也可以通过背包去转移,方法不唯一,题解中只展示递推式法的代码,其余方法请同学们自行研究实现。又题目已知答案输出不超过 2 63 2^{63} 263,因此可以使用 l o n g l o n g longlong longlong 进行存储。最终实现如下代码所示。

#include<bits/stdc++.h>
typedef long long ll;
const ll mod = 998244353;
using namespace std;
ll c[300][300];
int main() {
    ll n;
    cin >> n;
    n--;
    for (int i = 0; i <= n; i++) c[i][0] = c[i][i] = 1, c[i][1] = i; // 初始化
    for (int i = 1; i <= n; i++) { 
        for (int j = 2; j < i; j++) {
            c[i][j] = c[i - 1][j - 1] + c[i - 1][j]; // 根据递推式转移
        }
    }
    cout << c[n][11];
    return 0;
}

Problem F Long Live

来源:无
链接:无
考察:模拟
难度:橙题

本题虽然题面比较长,但是实际上当阅读完之后会发现其实很简单的,数据范围比较小,我们可以直接模拟即可,我们先用两个数组分别存储题目 A C AC AC 状态和有效提交数,其中记录 A C AC AC 状态的数组若值为 0 0 0,则表示未 A C AC AC,若其值大于 0 0 0,则表示该题 A C AC AC,并且我们将本题 A C AC AC 的时间存进去,因此该数组既可以表示 A C AC AC 状态,也可以表示 A C AC AC 时间,而另一个数组正常存储该题的有效提交数即可,由此可以处理完 n ∗ ( m + 1 ) n*(m+1) n(m+1) 行的输入。

再对封榜的 k k k 次提交进行处理,由于这 k k k 次提交给出的是时间乱序,因此需要先对提交按照时间进行排序,再去处理。在处理时,若 a a a 数组不为 0 0 0,则说明该队伍在本题已经 A C AC AC,根据题目描述中特别提醒 ① 可知,该提交无效;若 a a a 数组为 0 0 0,则说明该队伍之前在本题从未 A C AC AC,因而本次提交无论 A C AC AC 与否都需要增加一次有效提交,若本次提交 A C AC AC,则标记数组 a a a 当前用时,否则继续等待 A C AC AC 后再标记数组 a a a

在处理完所有输入后,需要对每支队伍的 A C AC AC 数量和总用时进行统计,其中根据题目描述中特别提醒 ⑤ 可知,计算每支队伍的时间时,仅需计算 A C AC AC 题目的用时。再根据题目描述中特别提醒 ② 所写的 c m p 1 cmp1 cmp1 进行排序,排序完后按照题目要求输出即可。注意,每列信息之间的空格并不算在每列固定格数之中,所以不要忘记列间空格,否则可能导致格式错误或答案错误。

本题难度其实并不难,当认真阅读完题面后可能就已经有了思路了,就算题面没看太懂,但是样例和样例解释也写得很清楚了,这个样例包含了特别提醒中的所有情况,因此该样例的通过与否决定了本题能否通过,若样例通过但还是答案错误或者格式错误,请检查一下代码是否存在一些细节问题。本题考察了同学们对于结构体的使用和排序是否足够熟练,以及类似于 %-7d 用作左对齐并占 7 7 7 格,多余则补齐空格的操作是否了解,这种 C C C 语言基础知识的掌握是否到位,因此本题还是比较考验同学们的基本功的。

p s : ps: ps: 原本是打算队名采用非固定长度,但怕同学们对于字典序排序不太熟练,因此改成了固定长度,避免了长度不相同时所造成的麻烦,而固定长度的字符串是可以直接使用 s o r t sort sort 排序的。另外输出内容原设定是搞成居中,但为了简化代码,改成了固定格数并向左靠齐。

#include<bits/stdc++.h>
typedef long long ll;
const ll mod = 1e9 + 7;
using namespace std;
int a[50][30]; // a 数组为每支队伍的每个题目是否 ac,若 ac 则赋值为用时,否则赋值为 0 代表未 ac,因此该数组可以两用
int b[50][30]; // b 数组为每支队伍的每个题目共有多少有效提交
struct ss {
    int id, time, ac;
    string name;
}c[50]; // 记录 n 支队伍的名字编号ac数量和用时,以便排序
struct sh {
    int tid, pid, t;
    string re;
}d[2000]; // 记录封榜期间 k 次提交
bool cmp1(ss t1, ss t2) { // 重载排序,根据提醒中的 ② 进行排序
    if (t1.ac == t2.ac) {
        if (t1.time == t2.time) return t1.name < t2.name;
        else return t1.time < t2.time;
    }
    else return t1.ac > t2.ac;
}
bool cmp2(sh t1, sh t2) { // k 次提交仅需将其按照提交顺序排列即可
    return t1.t < t2.t;
}
int main() {
    int n, m, k;
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++) {
        cin >> c[i].name;
        c[i].id = i, c[i].time = 0, c[i].ac = 0; // 初始化
        for (int j = 1; j <= m; j++) {
            char type;
            cin >> type;
            if (type == '+') {
                int ti, num;
                cin >> ti >> num;
                a[i][j] = ti, b[i][j] = num; // 给 ac 的题记录用时和有效提交
            }
            else if (type == '-') {
                int num;
                cin >> num;
                b[i][j] = num; // 给未通过的题记录有效提交
            }
        }
    }
    for (int i = 1; i <= k; i++) cin >> d[i].tid >> d[i].pid >> d[i].t >> d[i].re;
    sort(d + 1, d + 1 + k, cmp2);
    for (int i = 1; i <= k; i++) {
        if (a[d[i].tid][d[i].pid] != 0) continue; // 若该队伍在某题上已 ac,则无需处理本条提交
        // 否则本次提交视为一次有效提交
        b[d[i].tid][d[i].pid]++; // 增加有效提交数
        if (d[i].re[0] == 'A') a[d[i].tid][d[i].pid] = d[i].t; // 若 ac 则标记 a 数组,并用用时代替
    } // 小技巧:由于输入已经告知只有一个字符串的首字母为 A,因此可以直接以该字符串首字母判断,在许多字符串题也适用
    cout << "Place Team Solved Penalty ";
    for (int i = 1; i <= m; i++) printf("%-7c ", i - 1 + 'A'); // 输出题目编号
    cout << endl;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (a[i][j] == 0) continue; // 只有 ac 的题才会计算用时
            c[i].time += (a[i][j] + 20 * (b[i][j] - 1)), c[i].ac++; // 对于 ac 的题目,其总用时为(ac 提交用时 + 20 * (有效次数 - 1))
        }
    }
    sort(c + 1, c + 1 + n, cmp1); // 由于排序后原有的 id 顺序会被打乱,因此之前 c 数组记录的原始 id 将在下面的输出中发挥作用
    for (int i = 1; i <= n; i++) {
        printf("%-5d ", i); // 输出排名
        cout << c[i].name << " "; // 输出队名
        printf("%-6d %-7d ", c[i].ac, c[i].time); // 输出 ac 数和用时
        for (int j = 1; j <= m; j++) {
            if (a[c[i].id][j] == 0) {                       // 若本题未 ac
                if (b[c[i].id][j] == 0) printf("%-7d ", 0); // 且本题无任何提交,输出 0
                else printf("-/%-5d ", b[c[i].id][j]);      // 且本题有过提交,则输出 "-/Num",由于已占 "-/" 两字符,所以后面数字占 5 格
            }
            else printf("+/%-5d ", b[c[i].id][j]);         // 若本题已 ac,则输出 "+/Num",由于已占 "+/" 两字符,所以后面数字占 5 格
        }
        cout << endl;
    }
    return 0;
}

Problem G Karma

来源:牛客编程巅峰赛S2第7场 - 青铜&白银&黄金 B - 牛牛的独特子序列
链接:B - 牛牛的独特子序列
考察:二分答案 或 双指针
难度:橙题

题目中所问的是最大长度,因此我们可以通过二分答案一种字符的长度进行 c h e c k check check,若该长度的 c h e c k check check 符合要求,那么经过二分答案后,所求最大长度即为 3 ∗ k 3 * k 3k。其中 c h e c k check check 函数,根据子序列的定义,可以先从前往后遍历查看是否有 k k k T T T,然后当满足 k k k T T T 的条件后,此时说明子序列的 T T T 已经足够,接下来再去判断是否后面有 k k k L L L,依此类推,最后若三种字符都满足,则说明当前长度满足题意,因此返回 t r u e true true 可以移动左端点 l l l 提高 m i d mid mid 值,反之类似。故时间复杂度为 O ( n ∗ log ⁡ 2 n ) O(n * \log_2n) O(nlog2n) 1 0 6 ∗ 20 = 2 ∗ 1 0 7 10^{6} * 20 = 2*10^{7} 10620=2107 不会超时。

另外也可以通过前缀和 + 双指针去实现,方法不唯一,题解只展示二分答案解法,其余方法请同学们自行研究实现。最终实现如下代码所示。

#include<bits/stdc++.h>
typedef long long ll;
const ll mod = 998244353;
using namespace std;
string a;
int n;
int cnt[5];
int check(int x) {
    memset(cnt, 0, sizeof(cnt));
    int flag = 0;
    for (int i = 0; i < a.size(); i++) {
        if (a[i] == 'T' && flag == 0) { // 先集齐 T 后标记 flag 开始收集 L
            cnt[1]++;
            if (cnt[1] == x) flag = 1;
        }
        if (a[i] == 'L' && flag == 1) { // 集齐 L 后标记 flag 开始收集 E
            cnt[2]++;
            if (cnt[2] == x) flag = 2;
        }
        if (a[i] == 'E' && flag == 2) { // 集齐 E 后标记 flag 说明存在满足条件的子序列
            cnt[3]++;
            if (cnt[3] == x) flag = 3;
        }
    }
    if (flag == 3) return 1; 
    else return 0;
}
int main() {
    cin >> n;
    cin >> a;
    int l = 1, r = a.size(), mid, ans = 0;
    while (l <= r) { // 经典二分答案,二分对象为目标子序列长度的三分之一
        mid = (l + r) / 2;
        if (check(mid)) {
            ans = mid;
            l = mid + 1;
        }
        else r = mid - 1;
    }
    cout << ans * 3; // 最后复原长度
    return 0;
}

Problem H Exile

来源:AtCoder Beginner Contest 249 C - Just K
链接:C - Just K
考察: d f s dfs dfs 或 二进制枚举
难度:橙题

本题是一个比较典型的 d f s dfs dfs 回溯题型,我们先验证时间上是否可行,由于最多只有 15 15 15 个字符串,而每个字符串只有选和不选两种选择,因此最多只有 2 15 = 32768 2^{15}=32768 215=32768 种选择方案,而每种方案我们需要计算其有多少个字母满足次数为 k k k,即遍历 n n n 个字符串的选择情况,将选中的字符串枚举 26 26 26 个字母的出现次数,因此每种方案最大的时间复杂度为 15 ∗ 26 = 390 15*26=390 1526=390,故整个程序的最大运行为 32768 ∗ 390 = 12779520 ≈ 1 0 7 32768*390=12779520≈10^7 32768390=12779520107,故不会超时。注意,每个字符串是固定不变的,因此他的各字母的次数也是不变的,所以我们可以提前预处理出各字符串的各字母出现次数,而不是在每次验证方案时都重复计算,这样可以起到时间优化的作用。另外提一嘴,在写 d f s dfs dfs 时,由于其时间复杂度比较高,因此我们需要尽可能优化 d f s dfs dfs 内的运算,其中可能包括减少重复操作,或者明显不符合要求的情况可以提前结束 d f s dfs dfs 的搜索,再或者优化掉一些不必要的操作,这些都可以对 d f s dfs dfs 的时间复杂度做出优化,也叫剪枝操作。在比赛时,若发现某题能用 d f s dfs dfs 解决,但自己写的程序一直超时,则可以尝试对 d f s dfs dfs 进行剪枝优化,这是 d f s dfs dfs 中的重点也是难点,若剪枝的好,你的 d f s dfs dfs 程序可能会快上数倍。本题的这个优化操作严格来讲不算剪枝,这种避免重复操作的方法称为预处理,是在书写程序时务必要考虑的一点,很多时候进行预处理会大大节省时间开销。

在时间上证明可行后,直接根据已有逻辑编写代码即可。先预处理出每个字符串的各字母个数,再去 d f s dfs dfs,其中 n o w now now 代表的是当前已决定 n o w now now 个字符串的选择情况,由于每个字符串只有选和不选的两种可能,因此先用回溯去代表选该字符串,回溯完后,直接再进下一层 d f s dfs dfs 代表不选该字符串,在决定好 n n n 个字符串选择情况后,直接根据之前标记过的 v i s vis vis 数组判定该字符串是否已选择即可,并计算答案,最后判断满足 k k k 的字母种数,取最大值即可。

另外也可以使用二进制枚举求解,题解只展示 d f s dfs dfs 的解法,其余方法请同学们自行研究实现。

#include<bits/stdc++.h>
typedef long long ll;
const ll mod = 1e9 + 7;
using namespace std;
int n, k;
int a[20][30]; // 用于统计各字符串的各字母个数
int vis[20], sum[30]; // vis 用于回溯,sum 用于统计当前选择方案的各字母个数
string b[20];
int maxn = 0;
void dfs(int now) {
	if (now == n) { // 当 n 个字符串的选择结束后统计答案
		memset(sum, 0, sizeof(sum));
		for (int i = 1; i <= n; i++) {
			if (!vis[i]) continue; // 若未选第 i 个字符串,则跳过
			for (int j = 1; j <= 26; j++) { // 否则统计该字符串的各字母个数
				sum[j] += a[i][j]; // 计算各字母个数
			}
		}
		int num = 0;
		for (int i = 1; i <= 26; i++) {
			if (sum[i] == k) num++; // 若字母个数刚好等于 k,则计数 
		}
		maxn = max(maxn, num); // 统计答案
		return;
	}
	vis[now + 1] = 1; // 若选第 now 个字符串,先标记
	dfs(now + 1);     // 若选第 now 个字符串,再搜索
	vis[now + 1] = 0; // 若选第 now 个字符串,后回溯
	dfs(now + 1);     // 若不选第 now 个字符串,则直接进入下一个字符串的选择搜索
}
int main() {
	cin >> n >> k;
	for (int i = 1; i <= n; i++) {
		cin >> b[i];
		for (int j = 0; j < b[i].size(); j++) {
			int x = b[i][j] - 'a' + 1; // 以 a 为 1,依此类推
			a[i][x]++; // 统计每个字符串的各字母个数
		}
	}
	dfs(0); // 从选中 0 个字符串开始搜索
	cout << maxn;
	return 0;
}

总结

赛时看大家做题情况,发现大家存在的比较明显的一个问题就是没有充分思考正确性,过于依赖样例,而绝大多数比赛的样例实质上不会给出明显提示,更多的是需要自己捏造一些有用的样例去对自己的程序纠错。其中在 A A A 题中绝大部分同学只考虑了样例的情况,而并未思考过其他情况,例如 n = 998 n=998 n=998,其最大的字典序并非 99 99 99,而是 998 998 998,另外有一部分同学直接用 i n t int int 或者 l o n g l o n g long long longlong 去存储 n n n,这明显不符要求,题目给定的 n n n 输入范围为 1 0 1000000 10^{1000000} 101000000,而 l o n g l o n g long long longlong 的范围大约为 1 0 18 10^{18} 1018,二者完全都不在一个量级,因此看到这种输入范围的数字,第一想法就应该是输入字符串,而并非整数类型。其次 B B B 题, n n n 的数据范围到了 1 0 9 10^9 109,并且还有 1 0 4 10^4 104 组,而很多同学完全不去计算时间复杂度,直接上手 f o r for for 循环去计算斐波那契数列,这样的操作很明显时间复杂度已经远远超过题目时间限制 1 s 1s 1s 所能处理的运算,因此需要动手动脑思考斐波那契数列的奇偶性质与题目要求之间的联系,才能做到每组 O ( 1 ) O(1) O(1) 的时间复杂度, 建议大家以后做题的时候多动手写一写,从题干或者样例中发现规律,而不是题目说什么就照着写什么。由于牛客评测机的优化功能,使得部分同学写的 f o r for for 循环也过了,但实际上放回验题组上提交只能通过两个得分点,其他得分点均 T L E TLE TLE,建议这些同学可以重新写写 B B B 题。另外还有一部分同学不开 l o n g l o n g longlong longlong 导致使劲 w a wa wa,希望同学们以后注意数据范围,考虑好该使用 i n t int int 还是 l o n g l o n g longlong longlong,并且考虑好时间和空间复杂度的可行性再去写,而不是一昧的去写一份明显过不去的代码。

整套题涉及的知识点都是提前咨询过 22 22 22 级的,了解到这些知识点 23 23 23 级都有学过才出的,赛前也连续削减过三四次难度,中间还替换过个别难题。个人认为整套题目还是比较好的,设计的知识点并未反复重叠,且难度适中,整套题难度在橙题到黄题之间,前两个思维题算是比较简单的签到题,后面除了 F F F 题需要比较扎实的基础,并且有一定的码量以外,其他题均为对应知识点的魔改题,做题做得多的同学可能一眼就能看出来是什么知识点,因此本次练习赛同学们突出的问题在于刷题不够,一些知识点可能会过,但并不会用。建议同学们赛后及时补题,实在没思路了再看题解或者咨询其他同学,切忌直接照搬代码上去,有的时候虽然有某个题的解决思想,但这并不代表能写出成功 A C AC AC 的代码,因此在理解题解后,请自行编写代码!!!对于想打好信竞的同学,建议你们寒假期间多刷题,起码能完成洛谷官方题单【算法 2 - 5】及前面的所有题单 (https://www.luogu.com.cn/training/list),学有余力的话可以去 c o d e f o r c e s codeforces codeforces 打打 d i v 2 , d i v 3 , d i v 4 div2,div3,div4 div2,div3,div4,不想熬夜的话也可以直接补题, c f cf cf 上的 d i v 2 div2 div2 A , B A,B A,B 偏向思维训练,从 C C C 开始考察算法知识;而 d i v 3 div3 div3 前半部分考察基础知识,后半部分涉及算法;而 d i v 4 div4 div4 整场为语法题或少量算法,另外还有 a t c o d e r atcoder atcoder a b c abc abc 场次可以练习练习。最后,一定养成补题的习惯,并不是比赛打的多水平就高,而是在于赛后是否有对不会的题进行补题,吸收练习赛中的知识点,这才是能力提升的关键!多刷一刷 c f cf cf d i v 2 div2 div2 中的 A , B A,B A,B 提高思维能力,学会如何去思考解决问题,思维能力的提高对于其他知识点的理解和运用都有很大帮助。

赛后不补题,等于白打!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值