CSDN竞赛4期题解

总结

csdn的竞赛,与其他OJ的周赛相比,有些不太好的体验。举个例子,T1只是想分段收个电费,150度以下的怎么收费,151度以上的怎么收费,按照表述151度电就只收150度的电费,因为没说150-151度之间怎么收费了;T2虽然题目没啥毛病,但是给句子中单词逆序,本来需要做些操作的,cpp给定的模板直接将每个单词存入vector了,要做的就是给vector倒着取下数,没啥意义。T3一开始给的唯一一个用例是3 3,结果是10,给3拆分成3个不小于1的整数,有10种拆法,后面刷新修正为1,用例出错不说什么了。如果给4分为两个整数,那么1 3和3 1算一种还是两种题目也没有表述清楚;T4就是像求个最小的修改数量是多少,问的是通过修改最小的数字变成美丽数列,很容易误解为只能改最小的数字。希望后面的竞赛能改掉这些不足的地方吧。

下面题解代码为赛后书写,所以还不能保证能够通过评测,如果有问题,还望各位大佬帮忙指正。

题目列表

1.小玉家的电费

题目描述

夏天到了,各家各户的用电量都增加了许多,相应的电费也交的更多了。小玉家今天收到了一份电费通知单。小玉看到上
面写:据闽价电[2006]27号规定,月用电量在150千瓦时及以下部分按每千瓦时0.4463元执行,月用电量在151~400
千瓦时的部分按每千瓦时0.4663元执行,月用电量在401千瓦时及以上部分按每千瓦时0.5663元执行;小玉想自己验证一
下,电费通知单上应交电费的数目到底是否正确呢。请编写一个程序,已知用电总计,根据电价规定,计算出应交的电费
应该是多少。

分析

首先忽略题目不恰当的表述,重新理解为150以下电费多少,150-400电费多少,400以上电费多少,然后分类讨论下即可。

代码

#include 
#include 
using namespace std;
int main() {
	float n;
	cin>>n;
	float res = 0;
	if(n <= 150)	res += n * 0.4463;
	else {
		res += 150 * 0.4463;
		if(n <= 400)	res += (n - 150) * 0.4663;
		else {
			res += 250 * 0.4663 + (n - 400) * 0.5663;
		}
	}
	printf("%.1f\n",res);
	return 0;
} 

2.单词逆序

题目描述

对于一个字符串,请设计一个算法,只在字符串的单词间做逆序调整,也就是说,字符串由一些由空格分隔的部分组成,
你需要将这些部分逆序。 给定一个原字符串A,请返回逆序后的字符串。例,输入”I am a boy!“输出”boy! a am I“

分析

题目输入模板已经帮我们分割了单词,逆序输出即可。

代码

#include 
#include 
#include 
#include 
std::vector<std::string> solution(std::vector<std::string>& vec){
	std::vector<std::string> result;
	int n = vec.size();
	for(int i = n - 1;i >= 0;i--)	result.push_back(vec[i]);
	return result;
}
int main() {
	std::vector<std::string> vec;
	std::string line_0, token_0;
	getline(std::cin >> std::ws,line_0);
	std::stringstream tokens_0(line_0);
	while(std::getline(tokens_0, token_0, ' ')){
		vec.push_back((token_0));
	}
	std::vector<std::string> result = solution(vec);
	for(auto it=result.begin();it!=result.end();++it){
		std::cout<<*it<<" ";
	}
	std::cout<<std::endl;
	return 0;
}

3.小Q整数分割

题目描述

小Q决定把一个整数n,分割为k个整数。 每个整数必须大于等于1。 小Q有多少方案。

分析

举个例子,如果将4分割为2个正整数,1 3和3 1算不同的方案数的话,那么只需要考虑简单的组合数问题。也就是等价于有一个长度为n的线段,要将这条线段切成k段,就需要在线段上找k - 1个切点,一共有C(n - 1,k - 1)种切法,这个组合数就是答案。

但是本题实际的含义将1 3和3 1视为不同的方案,这个就有点难度了。可以理解为在1到n中找k个正整数,使得这k个正整数之和等于n的方案数。然后问题就变成了方案里面1选了多少个,2选了多少个,…。就可以使用背包问题的思想来解决本题了。

状态表示:f[i][j][t]表示1到i中选了t个整数,且这t个整数的和是j的方案数。t个整数的和可以理解为背包问题中的体积,t相当于附加的限制条件。

状态边界:f[i][0][0] = 1表示前i个整数中一个不选的方案数。

状态转移:需要考虑i这个整数选了几次,选的次数可以是0, 1, 2, …, r次,那么状态转移方程为f[i][j][t] = sum(f[i-1][j-i*r][t-r])。

复杂度分析:枚举i、j、r需要三重循环,而枚举i出现的次数还需要一重循环,本题的数据规模是100,O(n4)的复杂度就有点危险了,考虑使用背包问题类似的优化手段。

考虑f[2][8][4]这个状态转移的过程
2这个数字可以出现0到4次
f[2][8][4] = f[1][8][4] + f[1][6][3] + f[1][4][2] + f[1][2][1] + f[1][0][0]
再来看f[2][6][3]状态的转移过程
f[2][6][3] =              f[1][6][3] + f[1][4][2] + f[1][2][1] + f[1][0][0]
显然,f[2][8][4] = f[2][6][3] + f[1][8][4]

根据上面的例子我们可以得到优化后的状态转移方程:f[i][j][t] = f[i][j-i][t-1] + f[i-1][j][t]。这样一来时间复杂度降到了立方级别,106的计算次数是完全可以接受的。由于每个状态只用到了左边以及上一层相同的状态,所以可以使用滚动数组实现,去掉第一维状态。

代码

#include 
using namespace std;
const int N = 105, MOD = 1e9 + 7;
int n,k;
int f[N][N];
int main() {
	cin>>n>>k;
	f[0][0] = 1;
	for(int i = 1;i <= n;i++) {
		for(int j = i;j <= n;j++) {
			for(int t = 1;t <= k;t++) {
				f[j][t] = (f[j][t] + f[j - i][t - 1]) % MOD;
			}
		}
	}
	cout<<f[n][k]<<endl;
	return 0;
}

4.新型美丽数列

题目描述

定义美丽数列A: 1. 数列中相邻的数越是靠内相对大小加一,a[2]=a[1]+1,a[n-2]=a[n-1]+1… 2. 距离边缘距离相等的
数的大小相等:a[0] = a[n-1],a[1] = a[n-2]… 通过修改最小的数字使得给定数列变成美丽数列。 修改后的值必须仍是正
整数。
输入描述:
第一行输入整数n。(1<=n<=1000)表示数列的大小。
第二行输入n个整数。
输出描述:
输出最小修改。
输入样例:
3
1 1 1
输出样例:
1

分析

比赛时候想的是DFS和DP的思路,后面没时间就没作答了。比赛之后又看了下,分析出题目的隐藏条件,就很容易解决了。

首先,明确题意,题目表述不太清楚,我理解为数列有奇数个元素时,像1 2 3 2 1,除了中间数只有一个,其他数都是由内而外逐渐减去一的;数列有偶数个元素时,像1 2 2 1这种中间两个数是相等的,其他数由内而外减去一。然后给定若干个数列,判断要多少次操作才能将给定的数列转化为美丽数列。(PS:这么简单的表述,题目表述不清像是直接机器从英文题面翻译过来的。)

解题的关键在于一个性质,表面看是要修改很多元素才能变为美丽数列,实际只要确定了中间的数字,其他数字也就确定了。举个例子:

1 4 3 2 5

设中间的元素3改变为t,那么最终的数列就是t - 2,t - 1, t,t - 1,t - 2,来看下每个数字的改变量,分别是t - 3, t - 5, t - 3 - 1, t - 3, t - 7,现在希望改变的元素数量最少,也就是希望改变量数组中0的数量最多,而t是变量,意味着只要让改变量数组中出现最多的元素是0,那么就可以使得改变的元素数量最少,最后发现t - 3出现的次数最多,是3次,所以有三个位置元素不用改变,因此t取3最合适。

如果数列有偶数个元素呢,那么只需要设中间两个数改变为t即可。

具体在实现时,并不需要设变量t,只需要对最中间的数保持不变,由内而外每个数字一次加上1,2,3,…即可,比如1 4 3 2 5,处理后得到3 5 3 3 7,然后出现最多的数字就是3,所以最中间的数字取3,最后不等于3的数有2个,就是需要改变的数量。所以本题相当于给原数组做了下归一化之后求个哈希。

代码

#include 
#include 
#include 
using namespace std;
const int N = 1005;
int n,a[N];
unordered_map<int,int> m;
int main() {
	cin>>n;
	for(int i = 0;i < n;i++)	cin>>a[i];
	int t = (n - 1) / 2;
	if(n & 1) {
		int k = 1;
		while(t - k >= 0) {
			a[t-k] += k;
			a[t+k] += k;
			k++;
		}
	}
	else {
		int k = 1;
		while(t - k >= 0) {
			a[t-k] += k;
			a[t+k+1] += k;
			k++;
		}
	}
	for(int i = 0;i < n;i++)	m[a[i]]++;
	int s = 0;
	for(auto x : m) {
		s = max(s,x.second);
	}
	cout<<n - s<<endl;
	return 0;
} 
} 

你可能感兴趣的:(其它,动态规划,Hash)