这次省赛,实际上我并无准备,因为我一如既往地对算法没有什么兴趣。

竞赛一共八道题,A和B题是填空题,后六道题都是程序设计。前五题都做了,第六题暴力应该只拿了部分分,后两题就没时间写了。

在赛场上当时看到C++编译器是2014年发布的gcc 4.7.4,用的是C++98标准。但蓝桥杯的云端是可以运行C++11以上的标准的代码的。

本地做题环境的编译器低版本,导致许多C++函数用不了。比如 to_string()函数用不了。前两题最快速的做饭需要将数转到字符串类型,但是我只知道c++中有一个方便的 to_string() 函数,还好当时我看到赛场机子中有Pycharm,所以我就用python计算了前两道填空题。

这个向下兼容的过程,消耗了我大量时间。

最致命的是,有一道题我需要声明一个迭代器,但是我不知道迭代器的类型名,一般来说都是用auto自动推导的,但auto只在C++11标准以上才有,导致这道题的代码我在本地环境无法测试。明明代码是正确的,但本地的低版本编译器却认为我的代码是错误的。这种割裂感很少有。

而现在看来这道题也依旧做对了。

// C++98
std::vector<int> vec;
std::vector<int>::iterator it = vec.begin();
// C++ 11
auto it = vec.begin();  // 自动推导为 std::vector<int>::iterator

与当时赛场的老师沟通,得知组委会只要求提供5.11版本的dev c++,而他们似乎只更新了代码编辑器版本,但一般来说dev c++是捆绑了一个编译器的,但不知为何内置的编译器却十分古典,感觉有点意思。

比赛完后,我查找了在 C++98 的字符串类型转换方法:

// for C++98/03 standard
string old_int_to_string(int v) {
    ostringstream oss;
    oss << v;
    return oss.str();

}

string old_double_to_string(double v) {
    ostringstream oss;
    oss << fixed << setprecision(6) << v;
    return oss.str();

}

string old_c_style_to_string(int v) {
    char buffer[32]; // make sure buffer is suffcient
    sprintf(buffer, "%d", v);
    return string(buffer);
}

事实上,生活在草台班子里的我们不必认真太多。

部分题解

7. 拼好数

当时留给我写第七道题的时间只有5分钟左右,大概读完了题就已经结束了。 这道题是省赛 C++ C组,Python A组,Java A组的题。

题目

我们将含有不少于 6 个 6 的数视为一个好数。例如 666666, 162636465666 是好数,12366666 不是好数。 

给定 n 个正整数 $a_i$,你可以把这些数分成若干组拼起来,每组内的数可以按任意顺序拼,但一组最多只能有 3 个数。求最多可以得到多少个好数。

输入格式

输入的第一行包含一个正整数 n 。 

第二行包含 n 个正整数 a1, a2, · · · , an ,相邻整数之间使用一个空格分隔。

输出格式

输出一行包含一个整数表示答案,即最多可以得到的好数的数量

【样例输入 1】
3
66 66 66

【样例输出 1】
1

【样例输入 2】 
7 666666 16166 6696 666 6 6 6 

【样例输出 2】 
2

这个问题是分组问题:如何分组,使得包含6个6的组尽可能的多。而每个组最多能有三个数,那么最少能有1个数。

我们可以统计每个数中6的个数:比如将 16166 6193 66 转化为 3 1 2,再进行排序,之后算出如何相加才能得到最多的6。

分组的情况只有三种:

sum1 = a1
sum2 = a1 + a2
sum3 = a1 + a2 + a3

我们为了得到最大的组数,使用贪心策略追求局部最优,则每次都要尽可能地先选择最少的数,且尽可能地用最大的数与最小的数组合:

如果一个数包含6个以上的6,则单独成一组。

此时,转换后的数组只可能由 1 2 3 4 5 这五个数字组成。

对于 5 我们为它找 1,如果没有则找 2。同理,对于4,我们找 2,没有则找 3。

此时未配对的数,只可能由 1 2 3 4 组成。三元组中:首先查找4 + 1 + 1,对于 4 + 2 + 1,不优于 4 + 2。

而对于 1 2 3,3 + 2 + 1 优于 3 + 2 + 2 ,并且优于 3 + 3,因为 1 2 如果没有 3 就无法成组。

最后只剩 2 3 的情况:

  • 2 + 2 + 2
  • 3 + 2 + 2
  • 3 + 3

#include <bits/stdc++.h>
using namespace std;
int n;
int count_six(int n) {
	string s = to_string(n);
	int cnt = count(s.begin(), s.end(), '6');
	if(cnt > 6) cnt=6;
	return cnt;
}

int main() {
	cin >> n;
	int a[7] = {0};
	for(int i = 0;i<n;i++) {
		int t; cin>>t;	
		a[count_six(t)] += 1;
	}
	int ans = a[6];
	int idx = 1; // range 1 2 3 4 5
	while(a[5]>0) {
		if(a[idx]){
			a[idx]--; a[5]--;
			ans++;
		} else if(idx < 6) {
			idx++;
		}
	}
	while(a[4] > 0 && a[1] > 1) {
		a[4]--; a[1]-=2;
		ans++;
	}
	idx = 2; // range 2 3 4
	while(a[4] > 0) {
		if(a[idx]){
			a[idx]--; a[4]--;
			ans++;
		} else if(idx < 5) {
			idx++;
		}
	}
	while(a[3] && a[2] && a[1]) {
		--a[3]; --a[2]; --a[1];
		ans++;
	}
	
	// 2 + 2 + 2
    ans += a[2] / 3;
    a[2] = a[2] % 3;
    
    // 3 + 2 + 2
    int possible = min(a[3], a[2] / 2);
    ans += possible;
    a[3] -= possible;
    a[2] -= 2 * possible;
 
    // 3 + 3
    ans += a[3] / 2;
    a[3] = a[3] % 2;

	cout << ans << endl;
	return 0;
}

8. 登山

题目

小蓝正在登山,山峰的高度构成 n 行 m 列的正整数矩阵,$a_(i, j)$ 表示第 i 行 第 j 列格子 (i, j) 上的山峰的高度。小蓝以一种特别的方式进行登山,如果他此刻在第 p 行第 q 列的格子 (p, q) 上,那么下一步可以选择: 

  1. 走到格子 (i, q),满足 ai,q < ap,q 且 i > p ; 

  2. 走到格子 (i, q),满足 ai,q > ap,q 且 i < p ; 

  3. 走到格子 (p, j),满足 ap, j < ap,q 且 j > q ; 

  4. 走到格子 (p, j),满足 ap, j > ap,q 且 j < q 。 

小蓝想知道,如果他依次从每一个格子开始出发,按照最优策略,他最高能到达的山峰的高度的平均值是多少?

输入格式

输入的第一行包含两个正整数 n, m ,用一个空格分隔。 

接下来 n 行,每行包含 m 个正整数。其中第 i 行包含 ai,1, ai,2, · · · , ai,m ,相 邻整数之间使用一个空格分隔。

输出格式

输出一行包含一个实数表示答案,四舍五入保留正好 6 位小数。

题解

我们先来分析一下移动规则:

  • 向下移动 (i > p),需要高度更低。
  • 向上移动 (i < p),需要高度更高。
  • 向右移动 (j > q),需要高度更低。
  • 向左移动 (j < q),需要高度更高。

每次移动只顺着一个行或列方向上的变化,并且是可以跳着走的。

对于每一个格子,或者叫做起点 $(p, q)$,都要算出能到达的最高山峰高度,最后将每一个格子的结果求平均值。

我自己尝试直接用dfs,加上记忆搜索来做,只通过了部分:

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

vector<vector<int>> a;
vector<vector<int>> dp;
int n, m;
set<pair<int, int>> used; // unordered_set require customizing hash func
const NEG_INF = -1;

int dfs(int p, int q) {
	if(dp[p][q] != -1) return dp[p][q];
	if(used.find({p, q}) != used.end()) return NEG_INF;
	int max_h = a[p][q];

	// Downward
	for(int i = p+1; i<n; i++) {
		if(a[i][q] < a[p][q]){
			used.insert({p, q});
			max_h = max(max_h, dfs(i, q));
			used.erase({p, q});
		}
	}
	// Upward
	for(int i = p-1; i>=0; i--) {
		if(a[i][q] > a[p][q]) {
			used.insert({p, q});
			max_h = max(max_h, dfs(i, q));
			used.erase({p, q});
		}
	}
	// Rightward
	for(int j = q+1; j<m; j++) {
		if(a[p][j] < a[p][q]){
			used.insert({p, q});
			max_h = max(max_h, dfs(p, j));
			used.erase({p, q});
		}
	}
	// Leftward
	for(int j = q-1; j>=0; j--) {
		if(a[p][j] > a[p][q]){
			used.insert({p, q});
			max_h = max(max_h, dfs(p, j));
			used.erase({p, q});
		}
	}
	dp[p][q] = max_h;
	return max_h;
}

int main() {
	ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> m;
    a.assign(n, vector<int>(m));
    dp.assign(n, vector<int>(m, -1));
    for(int i = 0; i < n; ++i) {
	    for(int j = 0; j < m; ++j) {
		    cin >> a[i][j];
	    }
    }

	double total = 0;
	for(int i = 0; i < n; ++i) {
	    for(int j = 0; j < m; ++j) {
		    total += dfs(i, j);
	    }
    }
    double average = total / (n * m);
    cout << fixed << setprecision(6) << average << endl;
	return 0;
}

Refs

https://www.luogu.com.cn/article/xkrwf6li

https://blog.csdn.net/m0_57883655/article/details/147405108