其他分享
首页 > 其他分享> > Codeforces Round #794 (Div2) A~E题解

Codeforces Round #794 (Div2) A~E题解

作者:互联网

https://codeforces.com/contest/1686

好久没写题了,写着玩玩,发现思维有所下滑

A题

题意:
给你\(n\)个数,每次操作可以选择\(n - 1\)个数,将这\(n - 1\)个数变成它们的平均值,问你能否在有限次操作内使得所有数相等。

思路:
看所有数的总和的平均数在序列里有没有就行了。

int a[N];
 
void solve() {
	int n;
	cin >> n;
	int s = 0;
	for(int i = 1 ; i <= n ; i ++) cin >> a[i], s += a[i];
	bool flag = false;
	if(s % n == 0) {
		s /= n;
		for(int i = 1 ; i <= n ; i ++)	
			if(a[i] == s) {
				flag = true;
				break;
			}	
	}
	
	if(flag) cout << "YES\n";
	else cout << "NO\n";
}

B题

题意:
给你一个数组,问你最多能把这个数组划分成多少个连续的奇数子数组
奇数数组是指这个数据的逆序对数目为奇数

思路:
为了使段数更多,每段区间当然是越短越好。因此直接贪心,每次出现一个递减的数就截断。

int a[N];
 
void solve() {
	int n;
	cin >> n;
	for(int i = 1 ; i <= n ; i ++) cin >> a[i];
	
	int cnt = 0;
	int now = 0;
	for(int i = 1 ; i <= n ; i ++) {
		if(a[i] >= now) now = a[i];
		else {
			cnt ++;
			now = 0;
		}
	}
	cout << cnt << "\n";
}

C题

题意:
给你一个数组,把它当做环,问你能否通过随意排序使得这个环满足每个数要么比相邻的数都大,要么比相邻数都小。

思路:
首先,\(n\)为奇数肯定不行,因为首尾会矛盾。
要构造这种锯齿状,我们只需要排序后,奇偶位置交替放大的和小的数就行,然后检验即可。

int a[N];
int b[N];
 
void solve() {
	int n;
	cin >> n;
	for(int i = 1 ; i <= n ; i ++) cin >> a[i];
	
	if(n & 1) cout << "NO\n";
	else {
		sort(a + 1, a + n + 1);
		int l = 1, r = n / 2 + 1;
		int now = 1;
		for(int i = 0 ; i < n / 2 ; i ++){
			b[now ++] = a[l + i];
			b[now ++] = a[r + i];
		}
		bool flag = true;
		for(int i = 1 ; i <= n ; i ++) {
			int l, r;
			l = i - 1, r = i + 1;
			if(l == 0) l = n;
			if(r == n + 1) r = 1;
			if((b[i] > b[l] && b[i] > b[r]) ||
				(b[i] < b[l] && b[i] < b[r])) continue;
			else {
				flag = false;
				break;
			}
		}
		if(flag) {
			cout << "YES\n";
			for(int i = 1 ; i <= n ; i ++) cout << b[i] << " ";
			cout << "\n";
		}	
		else cout << "NO\n";
	}
}
 

D题

题意:
给你四种基串 A, B, AB, BA的数目,和一个目标串S,问你能否通过基串任意顺序组合得到目标串S。

思路:
首先看看目标串和所给基串的A,B字符数目是否相等,不相等直接\(NO\)
假如我们能把AB和BA串在目标串S的填充好,那么剩下的位置是确定的,因此我们只需要考虑AB和BA串怎么放就行。
按照相邻不同的规则对目标串进行拆分,我们可以得到如下四种串
1.ABAB
2.BABA
3.ABABA
4.BABAB
显然,3号串在去除1个A后,可以由两个AB或者两个BA组成,4号串类似。
而1号和2号串则分别由两个AB、BA组成。
很容易发现ABAB串也可以转化成由BA构成,但是会损失两个字符,即 A BA B。
我们先将1号和2号串按照各自基串填充,然后用3、4号串对应去补充不足。
在此操作后,如果还有AB或BA串数目有缺失,那么考虑另一种串可能有多。
这个时候我们采取转化,将多的那一种串尽可能转化成另一种,注意一次转化整个连续串。
因为转化是有损失的,因此我们优先转化长度大的,这样才是最优的。

void solve() {
	int a, b, ab, ba;
	cin >> a >> b >> ab >> ba;
	string s;
	cin >> s;
	// a + c + d     b + c + d
	int A = 0, B = 0;
	for(int i = 0 ; i < s.size() ; i ++)
		if(s[i] == 'A') A ++;
		else B ++;
	if(A != a + ab + ba || B != b + ab + ba) {
		cout << "NO\n";
		return;
	}
	
	int cab, cba, both;
	cab = cba = both = 0;
	int st = 0;
	vector<int> AB, BA;
	for(int i = 0 ; i <= s.size() ; i ++) {
		if(i == s.size() || (i > 0 && s[i] == s[i - 1])) {	// 遇到相同的字母 
			int len = i - st;
			if(len & 1) both += len / 2;
			else {
				if(s[st] == 'A') cab += len / 2, AB.push_back(len / 2);
				else cba += len / 2, BA.push_back(len / 2);
			} 
			st = i;
		}
	}
	
	int sum = cab + cba + both;
	if(sum < ab + ba) {
		cout << "NO\n";
		return;
	}
	
	if(cab < ab){
		int d = min(both, ab - cab);
		cab += d;
		both -= d;
	}
	if(cba < ba) {
		int d = min(both, ba - cba);
		cba += d;
		both -= d;	
	}
	
	sort(AB.begin(), AB.end(), greater<int>());
	sort(BA.begin(), BA.end(), greater<int>());
	
	for(int i = 0 ; i < AB.size() ; i ++) {
		if(cab - 2 < ab) break;
		int now = AB[i];
		int d = min(cab - ab, now);
		cba += d - 1;
		cab -= d;
	}
	
	for(int i = 0 ; i < BA.size() ; i ++) {
		if(cba - 2 < ba) break;
		int now = BA[i];
		int d = min(cba - ba, now);
		cab += d - 1;
		cba -= d;
	}
	
	if(cab >= ab && cba >= ba) cout << "YES\n";
	else cout << "NO\n";
}

E题

题意:
给你一个括号串,每次操作可以选择任意一段串翻转,问你最少操作多少次使得整个括号串合法。

思路:
我们用\(1\)表示\((\),用\(-1\)表示\()\),用sum表示它们的前缀和,如果括号串合法,当且仅当对任意的i都有 \(sum[i] >= 0\)
我们找到数组中的最大\(sum[pos]\),那么我们翻转\([1, pos]\)和\([pos + 1, n]\)一定会满足条件。
因为如果翻转之后,\([1, pos]\)出现了\(sum[i] < 0\),那么\(sum[pos]\)将不会是最大前缀和,因为我们抛弃原来的\([i, pos]\)的数可以使得sum更大。
同理,区间\([pos + 1, n]\)也可以类似证明。
所以我们最多操作\(2\)次,那么考虑什么时候操作\(1\)次和\(0\)次。
显然当原串就合法就是0次。
假设区间\([l, r]\)是覆盖所有\(sum < 0\)的最小区间,那么:
如果r后面有更多的\((\)那么r应该贪心的往右边延申。也就是找到最大的\(sum[i]\)位置。
对于l,如果l左边有\()\)那么应该贪心的尽量往左边延申。也就是找到最小的\(rsum[i]\)的位置(从后往前的前缀和)。
这样我们就翻转贪心过后的\([l, r]\),如果检验合法,那么就一定可以一次翻转,否则就需要两次。

int a[N];
int sum[N], rsum[N];
char s[N];
int n;
 
bool check() {
	for(int i = 1 ; i <= n ; i ++){
		sum[i] = sum[i - 1] + a[i];
		if(sum[i] < 0) return false;
	}
	return true;
}
 
void solve() {
	
	cin >> n;
	cin >> (s + 1);
	n *= 2;
	for(int i = 1 ; i <= n ; i ++){
		if(s[i] == '(') a[i] = 1;
		else a[i] = -1;
		sum[i] = sum[i - 1] + a[i];
	}
	for(int i = n ; i >= 1 ; i --)
		rsum[i] = rsum[i + 1] + a[i];
	
	
	if(check()) {
		cout << "0\n";
		return;
	}
	
	int minv = n + 1, maxv = 0, pos = 1;
	for(int i = 1 ; i <= n ; i ++) {
		if(sum[i] < 0) {
			minv = min(minv, i);
			maxv = max(maxv, i);
		}
		if(sum[pos] < sum[i]) pos = i;
	}
	
	int l = minv, r = maxv;
	for(int i = maxv + 1 ; i <= n ; i ++)
		if(sum[r] < sum[i]) r = i;
	for(int i = minv - 1 ; i >= 1 ; i --)
		if(rsum[l] > rsum[i]) l = i;
	
	reverse(a + l, a + r + 1);
	if(check()) {
		cout << 1 << "\n";
		cout << l << " " << r << "\n";
		return;
	}
	cout << 2 << "\n";
	cout << 1 << " " << pos << "\n";
	cout << pos + 1 << " " << n << "\n";
}

标签:794,BA,int,题解,sum,Codeforces,AB,now,cout
来源: https://www.cnblogs.com/luoyicong/p/16339480.html