习题训练三 题解

A - Sorted Adjacent Differences

 1.题意

  给定一个整数序列,要求重新排列使它满足|a1−a2|≤|a2−a3|≤…≤|an−1−an|。

 2.题解

  题目要求构造完成的序列相邻两数的差不递减,我们先把数组排下序,考虑特殊情况(一位大佬说解构造题的窍门是考虑特殊情况~~),差最大的两个数肯定是原序列中的最大值和最小值,所以把它俩放在序列后两位,再把次大值和次小值往前放即可,依此类推。往vector里塞构造后的数组,记得倒序输出。

 3.代码

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const int maxn = 1e5 + 5;
int t;
int main() {
	ios::sync_with_stdio(false);
	cin >> t;
	while(t--){
		int n;
		int a[maxn] = {0};
		cin >> n;
		for(int i = 1; i <= n; i++) {
			cin >> a[i];
		}
		sort(a + 1, a + n + 1);
		
		vector<int> ans;
		for(int i = 1, j = n; i <= j; i++, j--) {
			if(i == j) {
				ans.push_back(a[i]);
			} else {
				ans.push_back(a[i]);
				ans.push_back(a[j]);
			}
		}
		for(vector<int>::iterator it = ans.end() - 1; it >= ans.begin(); it--) {
			if(it == ans.end() - 1) {
				cout << *it;
			}
			else {
				cout << ' ' << *it;
			}
		}
		cout << endl;	
	}
	return 0;	
}
 

  

B - Powered Addition

 1.题意

  给定一个序列,你可以选择其中任意个元素对它们进行k 次操作,操作为让选中的这些元素加上2 ^ (k - 1),最终使序列成为不严格递增序列,要求最小化k 的值。

 2.题解

  如果一个数大于等于前一个数,那么不动它是最优的,如果小于前一个数,则要给它加上某些数。遍历序列,如果它小于前一个数,就记录下它与前面所有数中最大的数的差,维护序列的最大值和差的最大值,最后填平这个最大差即可。

 3.代码

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
int t; 
int main() {
	ios::sync_with_stdio(false);
	cin >> t;
	while(t--) {
		int n;
		cin >> n;
		
		int a[n + 1] = {0};
		for(int i = 1; i <= n; i++) {
			cin >> a[i];
		}
		//1 7 6 5
		int mmax = a[1]; // 7
		int cha = 0;     // 2 
		for(int i = 2; i <= n; i++) {
			mmax = max(mmax, a[i]);
			if(a[i] < a[i - 1]) {
				cha = max(cha, mmax - a[i]);
			}
		}
	
		int ans = 0;
		while(cha > 0) {
			cha -= pow(2, ans);
			ans++;
		}
		
		cout << ans << endl;
	}
	
	return 0;
}

C - Middle Class

 1.题意

  有n 个人,每人有ai 个硬币,若一个人的硬币数不少于x个硬币,则称这个人为富人,求经过任意次操作后富人的最大数量,操作为把m 个人(0 <= m <= n)的硬币平均分给这m 个人。

 2.题解

  贪心。先按硬币数从大到小排下序,再求一遍前缀和,遍历前缀和,如果sum / i >= x,就说明前i 个人都达到了富人的标准,求出满足条件的最大的i 即可。记得用scanf输入,否则会tle,或者ios::sync_with_stdio(false)来提速。

 3.代码

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
int t; 
bool cmp(int x, int y) {
	return x > y;
}
int main() {
	ios::sync_with_stdio(false);
	cin >> t;
	while(t--) {
		ll n, x;
		ll a[n + 1] = {0};
		cin >> n >> x;
		
		for(ll i = 1; i <= n; i++) {
			cin >> a[i];
		}
		
		sort(a + 1, a + 1 + n, cmp);
		
		for(ll i = 1; i <= n; i++) {
			a[i] += a[i - 1];
		}
		ll ans = 0;
		for(ll i = 1; i <= n; i++) {	
			if((double)a[i] / (double)i < x) {
				break;
			}
			ans = i;
		}
		cout << ans << endl;
	}
	
	return 0;
}

D - Circle of Monsters

 1.题意

  有n 个怪兽围成一个圈,每个怪兽的血量为ai,一次攻击只能对一个怪兽造成一点伤害,当一个怪兽死亡后,会对下一个怪兽造成bi 点伤害,求消灭所有怪兽需要多少次攻击。

 2.题解

  用爆炸把怪兽打死或打残显然比单纯攻击有效,但是我们要消灭的第一个怪兽必须用攻击而消灭。所以我们用一个数组c 记录怪兽被上个怪兽爆炸后剩下的血量(最少为0),然后求出所有怪兽剩下的血量之和,遍历怪兽,找出最优的起始点(sum - c[i] + a[i]最小)即可。

 3.代码

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
int t; 
int main() {
	ios::sync_with_stdio(false);
	cin >> t;
	while(t--) {
		ll n;
		cin >> n;
		
		ll a[n + 1] = {0};
		ll b[n + 1] = {0};
		ll c[n + 1] = {0};
		
		for(int i = 1; i <= n; i++) {
			cin >> a[i] >> b[i];
		}
		
		c[1] = max((ll)0, a[1] - b[n]);
		for(int i = 2; i <= n; i++) {
			c[i] = max((ll)0, a[i] - b[i - 1]);
		}
		ll sum = 0;
		for(int i = 1; i <= n; i++) {
			sum += c[i];
		}
		ll ans = 9e18;
		for(int i = 1; i <= n; i++) {
			ans = min(ans, sum - c[i] + a[i]);
		}
		
		cout << ans << endl;
	}
	
	return 0;
}

  

 E - Kind Anton

 1.题意

  给定两个数组a,b,且数组a 中只包含{-1,0,1} 三个元素中的元素,可任意次操作数组a,问是否能将a变为b。操作为选择一个索引对(i,j),满足i < j,aj 就变为aj + ai。

 2.题解

  题目要求i 必须小于j ,首先如果两个数组的第一个元素不相等肯定不能成功,直接输出no。如果b 数组中的元素小于a 数组中的对应元素,那么只要在[a1,ai)中存在-1即可,同理, 如果b 数组中的元素大于a 数组中的对应元素,那么只要在[a1,ai)中存在1即可,如果检测到数组a 即出现了-1,也出现了1,那么之后的元素肯定能转变为b数组的元素,直接输出yes结束即可。另外,用一个is记录是否已经输出了no,如果没输出则最后输出yes。

 3.代码

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
int t; 
int main() {
	cin >> t;
	while(t--) {
		int n;
		cin >> n;
		int a[n + 1] = {0};
		int b[n + 1] = {0};

		int pos1 = n;
		int pos2 = n;
		for(int i = 1; i <= n; i++) {
			cin >> a[i];
		}
		
		for(int i = n; i > 0; i--) {
			if(a[i] == -1) {
				pos1 = i;
			}
			if(a[i] == 1) {
				pos2 = i;
			}
		}
		
		for(int i = 1; i <= n; i++) {
			cin >> b[i];
		}
		
		int flag1 = 0;
		int flag2 = 0;
		int is = 0;
		
		if(b[1] != a[1]) {
			cout << "NO" << endl;
		} else {
			for(int i = 2; i <= n; i++) {
				if(a[i] < b[i]) {
					if(pos2 < i) {
						flag2 = 1;
					} else {
						cout << "NO" << endl;
						is = 1;
						break;
					}
				} else if(a[i] > b[i]) {
					if(pos1 < i) {
						flag1 = 1;
					} else {
						cout << "NO" << endl;
						is = 1;
						break;
					}
				} 
				
				if(flag1 && flag2) {
					break;
				}
			}
			if(!is) {
				cout << "YES" << endl;
			}
		}
		
	}
	
	return 0;
}

  

F - Eugene and an array

 1.题意

  给定一个序列,如果这个序列的子序列的任意一个子序列和都不为0,则这个子序列合法,要求找出有多少个合法的子序列。

 2.题解

  我们记fi 表示以i 为结尾的合法段的个数,所以我们要求fi 的和(i 从1到n)。我们考虑依次求出每一个fi ,我们要求的段的右端点显然是i ,左端点可以是[1, i ]中的每一个值,但是哪些左端点是合法的呢?如果左端点是p的时候已经不合法了,那么左端点是q的时候显然也不合法(q < p),因为后者包含了前者所包含的全部子段。那么我们只要求出最小的合法位置p,就知道fi = i - p + 1,i之前可能会有若干个和为0的段,那么p 就是这些段的左端点的最大值+1,所以我们每一次计算完i 的贡献后,要更新左端点的最大值。

  计算前缀和ai 后,如果有一个位置j(j < i)满足aj = ai,那么[j + 1, i ]就是一个0段,我们可以用map来存储上一次出现这个数的位置,记作lastpos,那么[lastpos_ai + 1,i ]就是一个0段,p的更新就是max{p ,lastpos_ai + 1},程序里的maxl就是p - 1,所以fi 就是i - maxl,记得开long long。(借鉴大佬的题解)

 3.代码

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const int maxn = 1e5 + 5;
ll n, ans;
ll a[200005];
map<ll, ll> lastpos;
int main() {
	cin >> n;
	for(ll i = 1; i <= n; i++){
		cin >> a[i];
		a[i] += a[i - 1];
	}
	lastpos[0] = 0;
	for(ll i = 1, maxl = 0; i <= n; i++) {
		if(lastpos.count(a[i]) != 0) {
			maxl = max(maxl, lastpos[a[i]] + 1);
		} 
		ans += i - maxl;
		lastpos[a[i]] = i;
	}
	cout << ans << endl;
	
	return 0;
}
 

  

原文地址:https://www.cnblogs.com/lvguapi/p/12971040.html