2019年南京大学计院开放日 机试(思路+代码)

0. 感想

翻翻我的csdn博客就知道,它已经被束之高阁。今天重新启封,一是整理思路和代码,以供参考,学习交流;二是作为timestamp,向躺尸的我说再见(也是想冲淡些自责感),上来立Flag,该抓紧时间做事了。Don’t trifle away your time.
注:有些题有两种解法,我较推崇第二种(复杂度更低,更巧妙)。

1. Find the Smallest Number

Description
Given a non-negative integer number n, which could be represented by a sequence of t digits as in its decimal form, you are supposed to remove k digits from the sequence so that the new number is the smallest among all possibilities. If all digits are removed, the result is 0.

Input
The input contains two lines. The first line is a non-negative t-digit integer n, and the second line is the non-negative integer k.
0 ≤ n &lt; 1 0 100 0 \leq n &lt; 10^{100} 0n<10100
0 &lt; t ≤ 100 0 &lt; t \leq 100 0<t100
0 &lt; k ≤ t 0 &lt; k \leq t 0<kt

Output
The output contains one line with a single integer, which is the smallest number obtained by removing k digits from n.

Sample Input1
  12
  1
Sample Output1
  1
Sample Input2
  12319
  3
Sample Output2
  11

Solution1:贪心

网上也说到了,这是NOI 3528原题,贪心做法。
放一个题解:https://blog.csdn.net/C20190413/article/details/77368590

贪心策略为:每一步总是选择一个使剩下的数最小的数字删去,即按高位到低位的顺序搜索,若各位数字递增,则删除最后一个数字,否则删除第一个递减区间的首字符。然后回到串首,按上述规则再删除下一个数字。重复以上过程k次,剩下的数字串便是问题的解。

Complexity: O ( n 2 ) O(n^2) O(n2)

// 贪心 O(n^2)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 100+5;
char a[maxn];
bool vis[maxn];
int n, k;
int main(){
	freopen("data.in", "r", stdin);
	freopen("1.out", "w", stdout);
	int T; cin >> T;
	while(T--){
		scanf("%s", a);
		scanf("%d", &k);
		int len = strlen(a);
		if(len == k){
			printf("0\n");
			continue;
		}
		memset(vis, 0, sizeof(vis));
		while(k--){
			int j = 0, cur, next;
			while(j < len){
				while(vis[j]) j++;
				cur = j;
				j++;
				while(vis[j]) j++;
				next = j;
				if(a[cur] > a[next]){
					vis[cur] = true;
					break;
				}
			}
		}
		for(int i = 0; i < len; i++)
			if(!vis[i]) printf("%c", a[i]);
		printf("\n");
	}
	return 0;
}

Solution2:栈

在机试前做过类似的题,机试时便想到用栈做,可以降低复杂度。奈何没有拿到满分,十分遗憾。今天重新修正代码,已AC。
整体思路:如果需要进一步删元素(即k > 0),则将即将入栈的元素与栈顶元素比较,如果栈顶元素大,则pop出栈,k -= 1,直到栈顶元素较小或者栈为空,并将新元素入栈。
Complexity: O ( n ) O(n) O(n)

// 栈 O(n)
#include <cstdio>
#include <iostream>
#include <string>
#include <stack>
using namespace std;
const int maxn = 100+5;
int k;
string a;
int ans[maxn], cnt = 0;
stack<int> s;
int main(){
	int T; cin >> T;
	while(T--){
		cin >> a;
		cin >> k;
		int bak = k;
		int n = a.size();
		if(n == k){
			cout << "0" <<endl;
			continue;
		}
		while(!s.empty()) s.pop();
		cnt = 0;
		for(int i = 0; i < n; i++){
			while(!s.empty() && k && s.top() > (int)(a[i]-'0')){
				s.pop(); k--;
			}
			s.push(a[i]-'0');
		}
		while(!s.empty()){
			ans[cnt++] = s.top();
			s.pop();
		}
		int tmp = 0;
		for(int i = cnt-1; i >= 0; i--){
			printf("%d", ans[i]);
			tmp++;
			if(tmp == n-bak) break;
		}
		cout << endl;
	}	
	return 0;
} 

2. Line up

Description
The teacher is trying to line up B boys and G girls to have a “quiet” queue of students: if there are more than K boys standing consecutively in the queue, the queue is not “quiet”. Please help the teacher to figure out how many ways are there to arrange a “quiet” queue?
Note that we do not consider the order of different students with the same gender. For example, in the case below, Q1 and Q2 are considered as the same arrangement.
  Q1: Girl1, Girl2, Boy1, Boy2
  Q2: Girl2, Girl1, Boy2, Boy1

Input
The input contains one line with three integers B, G and K, separated by a single space.

Output
The output contains one line with a single integer W: the number of the ways to arrange a “quiet” queue mod 10007.
1 ≤ B ≤ 100 1 \leq B \leq 100 1B100
1 ≤ G ≤ 100 1 \leq G \leq 100 1G100
1 ≤ K ≤ 100 1 \leq K \leq 100 1K100

Sample input
  3 1 2
Sample output
  2

Solution:动态规划

可以用dfs来搞一搞,但搜索树过于庞大,复杂度太高,拿不了满分。
正解是动态规划。dp数组的求解方式和转移方程不尽相同,这里讲解我的做法。
dp[i][j][k] 已安排i个男生,j个女生,并且以k个男生作为结尾。
转移方程
dp[i][j][k] += dp[i-1][j][k-1]i > 0 && k > 0时;
dp[i][j][0] += dp[i][j-1][k]j > 0时.
Complexity: O ( n 3 ) O(n^3) O(n3)

// dp
#include <cstdio>
#include <iostream>
#define ll long long
using namespace std;
const int maxn = 100+5;
const int maxk = 100+5;
//const int maxn = 5;
//const int maxk = 5;
const int MOD = 10007; 
ll dp[maxn][maxn][maxk];
int main(){
//	freopen("in.txt", "r", stdin);
	int B, G, K; cin >> B >> G >> K;
	dp[1][0][1] = 1; dp[0][1][0] = 1;
	for(int i = 0; i <= B; i++){
		for(int j = 0; j <= G; j++){
			for(int k = 0; k <= K; k++){
				if(i > 0 && k > 0)
					dp[i][j][k] = (dp[i][j][k]+dp[i-1][j][k-1])%MOD;
				if(j > 0)
					dp[i][j][0] = (dp[i][j][0]+dp[i][j-1][k])%MOD;
			}
		}
	}
	ll ans = 0;
	for(int i = 0; i <= K; i++){
		ans = (ans+dp[B][G][i])%MOD;
	}
	cout << ans << endl; 
	return 0;
}

3. The Number of Binary Tree

Description
We are all familiar with the pre-order, in-order, and post-order traversal of binary trees. Given the pre-order and the in-order traversal of a binary tree, the post-order traversal could be computed. It is also possible to find the pre-order traversal, given the post-order and the in-order traversals of a binary tree. However, given the pre-order and post-order of a binary tree, you cannot determine the in-order traversal sequence.
Now, given the pre-order traversal and the post-order traversal of the same binary tree, you are supposed to figure out the number of all possible binary trees.

Input
The input contains 2 lines. The first line gives the pre-order traversal of the tree(s), and the second line gives the post-order traversal of the tree(s). The input character set is {a-z} and the length is no more than 26.

Output
The output contains one line with an integer: the total number of possible binary trees.

Sample input
  abc
  cba
Sample output
  4

Solution1: 暴力搜索

显然pre[0]是根A,pre[1]...pre[len-1]是A的左右孩子,暴力枚举左孩子的个数(即左右子树的分界处),分成左右孩子两个子问题,通过判断是否满足后序序列来判断是否成立(左右子树的个数 && preorder[0] == postorder[末尾])。

#include <iostream>
#include <string>
using namespace std;
string pre, pos;
int cal(string pre, string pos){
	// if(pre == "" && pos == "") return 1;
	if(pre.size() == 0 && pos.size() == 0) return 1;
	if(pre.size() != pos.size()) return 0;
	if(pre[0] != pos[pos.size()-1]) return 0;
	if(pre.size() == 1 && pos.size() == 1) return 1;
	int ans = 0;
	for(int i = 0; i <= pre.size()-1; i++){
		ans += cal(pre.substr(1, i), pos.substr(0, i)) // lchild
			* cal(pre.substr(i+1, pre.size()-i-1), pos.substr(i, pos.size()-i-1)); // rchild
	}
	return ans;
}
int main(){
	// freopen("in.txt", "r", stdin);
	cin >> pre >> pos;
	cout << cal(pre, pos) << endl;
	return 0;
} 

Solution2: 优雅地递归

这道题是 POJ 1240 的简化版(特例),明确地说明是二叉树。如果会了POJ 1240,显然这道题也不在话下。
推荐一篇博客,讲得很好:https://blog.csdn.net/ditian1027/article/details/21132583
Complexity: O ( h n ) O(hn) O(hn). [h为树的高度] [我是怎么认为的…]

#include <iostream>
#include <cstdio>
#include <vector>
#include <string>
#define ll long long
using namespace std;
vector<int> v;
string pre, pos;
ll factorial(int n){
	ll ans = 1;
	for(ll i = 2; i <= n; i++) ans *= i;
	return ans;
}
ll C(int m, int n){ // C_n^m
	ll ans = 1;
	for(ll i = n; i >= n-m+1; i--) ans *= i;
	return ans/factorial(m);
}
void branch(int pre1, int pre2, int pos1, int pos2){
	// pre-order: [pre1, pre2], post-order: [pos1, pos2] 标注了两段的起始位置
	if(pre1 == pre2) return; //the leaf (no child/branch)
	int p1 = pre1+1, p2 = pos1;
	int cnt = 0; // amount of branches
	while(p2 < pos2){
		int len = 0;
		while(pre[p1] != pos[p2+len] && (p2+len < pos2)) len++;
		cnt++;
		branch(p1, p1+len, p2, p2+len);
		p1 = p1+len+1;
		p2 = p2+len+1;
		len = 0;
	}
	v.push_back(cnt);
}
int main(){
	// freopen("in.txt", "r", stdin);
	int m;
	while(cin >> m >> pre >> pos){
		if(m == 0) break;
		v.clear();
		branch(0, pre.size()-1, 0, pos.size()-1);
		ll ans = 1;
		// cout << v.size() << endl;
		for(int i = 0; i < v.size(); i++){
			int b = v[i];
			ans *= 1LL*C(b, m); 
		}
		cout << ans << endl;
	}
	return 0;
}

4. 一些想法

  • 南大举办本科生开放日很用心,充满诚意。安排食宿,还有各个组的展板和学生/老师讲解。整体感觉挺奈斯的。
  • 现在写完解题博客,还是很遗憾+一点小懊恼,去南大前心态不稳,很浮躁,又感觉很累,没有好好准备机试,上机时手感奇差,第一题就卡了很久(虽然思路正确,就调不对个代码)。
  • 南大的机试数据范围很友好,有思路的话,不会特别卡复杂度(个人这么觉得),考点也很正。给大家的建议是上机前多刷点题,找到做题感觉,考场上心态才不容易崩。
  • 祝大家都能得偿所愿。
  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值