Educational Codeforces Round 173 (Rated for Div. 2)题解(A-E)

Educational Codeforces Round 173 (Rated for Div. 2)题解(A-E)

A. Coin Transformation

你有一个价值为n的硬币,每次操作,如果n大于3,可以把硬币拆成两个价值为n/4的硬币
执行任意多次后,至多可以获得多少硬币?

显然,这是一个类似快速幂的行为

using i64 = long long;
void solve() {
	i64 n; cin >> n;
	i64 ans = 1;
	while (n > 3) {
		ans *= 2;
		n /= 4;
	}
	cout << ans << endl;
}

复杂度O(log4n)

B. Digits

给定n,(2<=n<=109),d,(1<=d<=9),问如果n!个d串起来形成的数字,可以被[1,3,5,7,9]里哪些数整除?
比如n=3,d=2,则串起来是222222

观察题。分类讨论,设串起来的数字是S
1 必然是可以整除的
3 如果d%3=0或者n>=3则可以整除,d%3=0是显然的;如果n>=3,则S长度是3!的倍数,显然可以被3整除。
5 要想末尾是5或者0,只能d=5
7 d=7是一种情况,另外打表可知3!=111111%7=0,而对于n>3,则S长度是3!的倍数,则有S=(1+106+1012+...)111111,显然可以整除7
9 类似3,更细节一点,d%9=0,或者d%3=0但是n>=3,或者n>=6(注意此时S长度是9的倍数)

using i64 = long long;
void solve() {
	i64 n, d; cin >> n >> d;
	vector<int> ans;
	ans.push_back(1);
	if (d % 3 == 0 || n >= 3) ans.push_back(3);
	if (d % 5 == 0) ans.push_back(5);
	if (d % 7 == 0 || n >= 3) ans.push_back(7);
	if (d % 9 == 0 || d % 3 == 0 && n >= 3 || n >= 6) ans.push_back(9);
	for (auto v : ans) cout << v << " ";
	cout << endl;
}

复杂度O(1)

C. Sums on Segments

一个数组,元素均为-1或者1,但是至多可能存在一个元素是109109
顺序排列所有子数组的和(去重)

假设没有这个额外的元素,很容易意识到这是求最大/最小子数组和,由于数字绝对值为1,所以[min,max]之间必然全部出现,顺序打印即可。
简述下如何求最大子数组和

int ans = 0, sum = 0; //本题可以为空,否则ans应以INT_MIN为初值。
for (auto v : nums) {
	sum += v;
	ans = max(ans , sum);
	if(sum < 0) sum = 0;
}

但是数组中可能存在一个非{-1,1}的元素x,如何处理?
答案由两部分构成,一部分是不包含x,即左右两侧的子数组和的并集。
[min(leftMin,rightMin),max(leftMax,rightMax)]
另一部分是包含x,小心,包含x的话,必须是整体连续
即左部分+x+右部分是连续的。
左部分是以[x-1]为右端点的子数组值域范围
右部分是以[x+1]为左端点的子数组值域范围
注意两部分合并是[leftMin+rightMin,leftMax+rightMax],再加上x即可
题目不难,写起来麻烦

using i64 = long long;
void solve() {
	int n; cin >> n;
	vector<int> nums(n);
	int x = 0; //如果没有找到非{-1,1}的元素,那就取第一个元素即可。
	for (int i = 0; i < n; i++) {
		cin >> nums[i];
		if (nums[i] != 1 && nums[i] != -1) x = i;
	}
	//计算[L,R]的子数组最大值和最小值
	auto dfs = [&](int L, int R, bool calcMin = true)->int {
		int res = 0, sum = 0;
		for (int i = L; i <= R; i++) {
			int v = nums[i];
			sum += v;
			res = calcMin ? min(res, sum) : max(res, sum);
			if (calcMin && sum > 0) sum = 0;
			if (!calcMin && sum < 0) sum = 0;
		}
		return res;
		};
	int minSum = min(dfs(0, x - 1, true), dfs(x + 1, n - 1, true));
	int maxSum = max(dfs(0, x - 1, false), dfs(x + 1, n - 1, false));
	set<int> set;
	for (int i = minSum; i <= maxSum; i++) set.insert(i);//不包含x的部分

	//以下标x-1为右端点的子数组范围
	int leftMin = 0, leftMax = 0, leftSum = 0;
	for (int i = x - 1; i >= 0; i--) {
		leftSum += nums[i];
		leftMin = min(leftMin, leftSum);
		leftMax = max(leftMax, leftSum);
	}
	//以下标x+1为左端点的子数组范围
	int rightMin = 0, rightMax = 0, rightSum = 0;
	for (int i = x + 1; i < n; i++) {
		rightSum += nums[i];
		rightMin = min(rightMin, rightSum);
		rightMax = max(rightMax, rightSum);
	}
	minSum = leftMin + rightMin + nums[x];
	maxSum = leftMax + rightMax + nums[x];
	for (int i = minSum; i <= maxSum; i++) set.insert(i);//包含x的部分
	cout << set.size() << endl;
	for (auto v : set) {
		cout << v << " ";
	}
	cout << endl;
}

复杂度最优可以是O(n),两个区间最后求并集即可。
这里为了方便,直接使用了set去重和排序,此时复杂度是O(nlogn)

D. Problem about GCD

已知区间L,R,和一个整数G,0<L,R,G<1018
求整数对A B,要求L<=A<=B<=R,且GCD(A,B)=G,且要最大化B-A。

最关键的点在于GCD(A,B)=G的意义,这表示A,B都是G的倍数,且AGBG互质。
然后就是,给定两个数P,Q,要想保证区间[P, P+d]和区间[Q,Q+d]中存在互质数对,d的取值可以很小...
证明是不会证明的,反正暴力过了...我才不会说取值从1000降到100又降到30才过的...

using i64 = long long;
i64 gcd(i64 a, i64 b) { return b == 0 ? a : gcd(b, a % b); }
void solve() {
	i64 L, R, G; cin >> L >> R >> G;
	i64 mL = (L + G - 1) / G; //mL*G是[L,R]中G的倍数最小值
	i64 mR = R / G; //mR*G是[L,R]中G的倍数最大值
	i64 r1 = -1, r2 = -1;
	i64 mx = -1;
	for (i64 i = mL; i <= mR && i < mL + 30; i++) {
		for (i64 j = mR; j >= mR - 30 && j >= i; j--) {
			if (gcd(i, j) == 1) {
				if (j - i > mx) {
					r1 = i * G;
					r2 = j * G;
					mx = j - i;
				}
			}
		}
	}
	cout << r1 << " " << r2 << endl;
}

复杂度不确定,就记作O(C2logU)吧,其中C<30

E. Matrix Transformation

给定矩阵A,B 定义两种操作
任选矩阵A一行i,任选x>=0,对这一行所有元素执行Aij=Aij&x
任选矩阵A一列j,任选x>=0,对这一列所有元素执行Aji=Aji|x
问,能否将A转换为B

很好的题。
首先要能意识到拆位操作,因为可以选择x=1<<k,则不同bit之间是操作无关的。
问题转换为A,B均为0 1矩阵。
举一个最简明的例子

(1)A=[0110],B=[1001]

A左下角的1,必须通过行变换为0,才能与B一致
然后A右下角再执行列变换为1
然后A右上角再执行行变换为0
最后当A左上角执行列变换为1时,此时左下角从0变回了1。
产生了循环,所以A是无法变成B的。
这启发我们,行列的变换不能产生环。
具体的:

  • 我们先枚举所有位置,如果AijBij不相等,则必须产生一次变换。
    如果Bij为0,则是行变换,否则是列变换。
  • 一轮枚举结束后,我们考虑某一变换的行或列,以行为例,该行的每个元素变换后都会变成0
    那么我们需要检查B的对应行是否全为0, 有不是0的,则需要对该列执行列变换。
  • 如果有新的变换添加进来,则需要重复执行上述检查操作,直到无新增变换为止。
  • 此时按照所有产生操作的的行和列,和对应的Bij的值,进行连边处理。
    如果Bi,j=1,则i行需要向j列连边,表示需要先执行行变换,再执行列变换。
    反之从j列向i行连边。
  • 最后就是简单的有向图判环。这可以用dfs染色,拓扑排序,并查集等手段处理。

以下是完整代码。

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve()
{
	int n, m; cin >> n >> m;
	vector<vector<int>> A(n), B(n);
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			int v; cin >> v;
			A[i].push_back(v);
		}
	}
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			int v; cin >> v;
			B[i].push_back(v);
		}
	}
	//拆位处理
	for (int k = 0; k <= 30; k++) {
		//第一轮枚举 将变化的行列加入队列中。
		queue<int> q;
		vector<int> flagX(n), flagY(m);
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				if (!flagX[i] && (A[i][j] >> k & 1) && !(B[i][j] >> k & 1)) {
					flagX[i] = 1;
					q.push(i);
				}
				if (!flagY[j] && !(A[i][j] >> k & 1) && (B[i][j] >> k & 1)) {
					flagY[j] = 1;
					q.push(n + j);
				}
			}
		}
		//循环检测是否有新变换加入,直到无新增为止
		while (!q.empty()) {
			int u = q.front();
			q.pop();
			if (u < n) {
				for (int j = 0; j < m; j++) {
					if (!flagY[j] && (B[u][j] >> k & 1)) {
						flagY[j] = true;
						q.push(j + n);
					}
				}
			}
			else {
				for (int i = 0; i < n; i++) {
					if (!flagX[i] && !(B[i][u - n] >> k & 1)) {
						flagX[i] = true;
						q.push(i);
					}
				}
			}
		}
		//有向图建立
		vector<vector<int>> g(n + m);
		vector<int> deg(n + m);
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				if (flagX[i] && !(B[i][j] >> k & 1)) {
					deg[j + n]++;
					g[i].push_back(j + n);
				}
				if (flagY[j] && (B[i][j] >> k & 1)) {
					deg[i]++;
					g[j + n].push_back(i);
				}
			}
		}
		//以下是拓扑排序求环
		int count = n + m;
		queue<int> que;
		for (int i = 0; i < n; i++) if (deg[i] == 0) que.push(i);
		for (int j = 0; j < m; j++) if (deg[j + n] == 0) que.push(j + n);
		while (!que.empty()) {
			int u = que.front();
			que.pop();
			count--;
			for (auto v : g[u]) {
				deg[v]--;
				if (deg[v] == 0) que.push(v);
			}
		}
		//只要有某一位不合法返回NO
		if (count) {
			cout << "NO" << endl;
			return;
		}
	}
	cout << "YES" << endl;
}
int main() {
	std::ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int t = 1; cin >> t;
	while (t-- > 0) {
		solve();
	}
}

复杂度为O(nmlogU)

posted @   云上寒烟  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示