Educational Codeforces Round 120 (Rated for Div. 2) A - D

A - Construct a Rectangle#

题意:
给你3个数l1,l2,l3,问能否分割其中一个数,使得四个数组成一个矩形,特殊地,正方形也是矩形。

  • 判段一下是否有两数之和等于另一数后者是否有两数相等且第三个数为偶数
#include <bits/stdc++.h>

using namespace std;

void solve() {
	int l1,l2,l3; cin >> l1 >> l2 >> l3;
	if(l1 + l2 == l3 || l1 + l3 == l2 || l2 + l3 == l1) {
		cout << "YES\n";
	} else if(l1 == l2 && l3 % 2 == 0|| l1 == l3 && l2 % 2 == 0|| l2 == l3 && l1 % 2 == 0) {
		cout << "YES\n";
	} else {
		cout << "NO\n";
	}
}

int main() {
	int T; cin >> T;
	while(T --) {
		solve();
	}
}

B - Berland Music#

题意:
给定排列p和一个二进制01s,构造一个排列q使得二进制串中si=1,sj=0,qi>qj,并且要求i=1n|qipi|最小。

  • 贪心
  • 首先根据01串把1n分为两大类,小的分配给串中为0的位置,大的反之,对应的分配规则即按照原先p中大小关系分配给对应大小关系的q使得差尽可能的小。
#include <bits/stdc++.h>

using namespace std;

void solve() {
	int n; cin >> n;
	vector<int>p(n + 1),q(n + 1);
	for(int i = 1;i <= n;i ++) {
		cin >> p[i];
	}
	vector<pair<int,int>>a1,a0;
	string s; cin >> s;
	for(int i = 0;i < s.size();i ++) {
		if(s[i] == '1')
			a1.push_back({p[i + 1],i + 1});
		else 
			a0.push_back({p[i + 1],i + 1});
	}
	sort(a1.begin(),a1.end());
	sort(a0.begin(),a0.end());
	int pos = 0;
	for(auto i : a0) {
		q[i.second] = ++pos;
	};
	for(auto i : a1) {
		q[i.second] = ++pos;
	}
	for(int i = 1;i <= n;i ++) {
		cout << q[i] << " \n"[i == n];
	}
}

int main() {
	int T; cin >> T;
	while(T --) {
		solve();
	}
	return 0;
}

C - Set or Decrease#

题意:
给定一个数列a和一个数k。每次操作你可以选择如下两种操作之一:

  • 选择一个位置i,使得ai=ai+1
  • 选择两个位置i,j,使得ai=aj

问需要最少的操作次数使得ak

  • 根据题目所给样例我们也能贪心的想到,更优的方案是摁住数列中最小的数减一定次数后,再把数列中剩余大的数一起减到当前最小数的值
  • 将数组排序后,维护一个后缀后,方便每次统计答案
  • 二分操作次数,每次check时,因为k很大,我们不能枚举最小数减多少次,我们直接枚举后缀的几个数变小,剩下的次数直接让最小的a减去即可
  • 注意枚举的边界,因为我们默认用a1进行减操作,所以只需枚举0n1的后缀和即可,其实也易得变a1肯定不是最好的操作
  • 复杂度O(nlogn)
#include <bits/stdc++.h>

using namespace std;
//1 1 1 2 2 3 6 6 8 10

void solve() {
	int n; long long k; 
	cin >> n >> k;
	vector<long long>a(n + 1),s(n + 2,0);
	for(int i = 1;i <= n;i ++) cin >> a[i];
	sort(a.begin() + 1,a.begin() + 1 + n);
	for(int i = n;i >= 1;i --) s[i] = s[i + 1] + a[i];
	if(s[1] <= k) {
		cout << "0\n";
	} else if(n == 1) {
		cout << a[1] - k << '\n';
	} else {
		auto check = [&](long long x) {
			for(int i = 0;i < n && i <= x;i ++) {//枚举把后缀的几个数变掉 这么写不要带n因为默认了第一个是用来减的 不过也易得 先减再变肯定比直接变好
				long long t = a[1] - (x - i);
				long long del = s[n - i + 1] - (t * i) + (x - i);
				if(s[1] - k <= del)
					return true;
			}
			return false;
		};
		long long l = 1,r = s[1] - k,ans;
		while(l <= r) {
			long long mid = l + r >> 1;
			if(check(mid)) {
				r = mid - 1; ans = mid;
			} else {
				l = mid + 1;
			}
		}
		cout << ans << '\n';
	}
}

int main() {
	// ios::sync_with_stdio(false);
	// cin.tie(nullptr);

	int T; cin >> T;
	while(T --) {
		solve();
	}
	return 0;
}

D - Shuffle#

题意:
给定一个字符串s和一个整数k,你可以对任意一个包含恰好k1连续的子序列的做shuffle操作,即重排这个序列中的所有字母,问最后能获得字符串共有多少种?

法Ⅰ直接考虑答案,法Ⅱ是从部分扩展到整体。

法 Ⅰ

  • 计数类问题,很明显我们如果直接统计每个有k1的子串,必然会有很多重复的情况,我们要做的就是去掉这些重复的情况
  • 在长度为n的子串中包含k个1,那他们的打乱方案就是Cnk
  • 如下图所示
    image
  • 红色序列和蓝色序列均符合要求,但是明显他们中间夹着的绿色的部分会在重复计算,按照容斥原理我们在加上红蓝部分的同时减去绿色部分即可
  • 预先处理一下所有1的位置,即可O(n)完成
#include <bits/stdc++.h>

using namespace std;

//法Ⅰ 容斥计数
const int mod = 998244353;

int C[5010][5010];

int main() {
	for(int i = 0;i <= 5000;i ++) {
		C[i][0] = 1;
		for(int j = 1;j <= i;j ++) {
			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;		
		}
	}//预处理组合数
	int n,k; cin >> n >> k;
	string s; cin >> s;
	vector<int>p;
	p.push_back(0);//两端假装放进1 来简化边界计算
	for(int i = 0;i < n;i ++) {
		if(s[i] == '1') {
			p.push_back(i + 1);
		}
	}
	p.push_back(n + 1);
	long long ans = 0;
	for(int i = k;i <= p.size() - 2;i ++) {
		int len = p[i + 1] - 1 - (p[i - k] + 1) + 1;
		ans = (ans + C[len][k]) % mod;
		if(i > k) {
			len = p[i] - 1 - (p[i - k] + 1) + 1;
			ans = ((ans - C[len][k - 1]) % mod + mod) % mod;
		}
	}
	if(k == 0 || p.size() - 2 < k) {
		cout << "1\n";
	} else {
		cout << ans << '\n';
	}
	return 0;
}

法 Ⅱ

  • 题目所给数据范围n5000,那么一定会存在O(n2)做法
  • 恰好等于k1的打乱方法等于其字串小于等于k1的打乱方法的总和,考虑扩展时重复的情况去掉即可
  • 从子串[l,r][l1,r+1]扩展时,考虑什么情况下会重复,即保持l1r+1的原样会重复,因为打乱内层时这两个位置不受影响。所以当打乱到这层时,我们必须这两个位置和上次的位置保持不一致才是和内层操作时不一样的方案数贡献
  • 分类讨论一下即可,是0强行放1,1强行放0
#include <bits/stdc++.h>

using namespace std;

const int mod = 998244353;

int C[5010][5010],pre[5010];

int main() {
	for(int i = 0;i <= 5000;i ++) {
		C[i][0] = 1;
		for(int j = 1;j <= i;j ++) {
			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;		
		}
	}
	int n,k; cin >> n >> k;
	string s; cin >> s;
	s = ' ' + s;
	for(int i = 1;i <= n;i ++) {
		pre[i] = pre[i - 1] + (s[i] == '1');
	}
	if(k == 0 || pre[n] < k) {
		cout << "1\n";
		return 0;
	}
	long long ans = 1;//自己本身是一种
	for(int i = 1;i <= n;i ++) {
		for(int j = i + 1;j <= n;j ++) {
			if(pre[j] - pre[i - 1] > k) continue;
			int cnt1 = pre[j] - pre[i - 1];
			int cnt0 = j - i + 1 - cnt1;
			if(s[i] == '0') {
				cnt1 --;
			} else {
				cnt0 --;
			}
			if(s[j] == '0') {
				cnt1 --;
			} else {
				cnt0 --;
			}
			if(cnt1 < 0 || cnt0 < 0) continue;
			ans = (ans + C[cnt0 + cnt1][cnt1]) % mod;
		}
	}
	cout << ans << '\n';
	return 0;
}
posted @   x7x7g7c7  阅读(146)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示
主题色彩