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(log_4n)\)
B. Digits
给定\(n,(2<=n<=10^9),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+10^6+10^{12}+...)*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,但是至多可能存在一个元素是\(-10^9\sim10^9\)
顺序排列所有子数组的和(去重)
假设没有这个额外的元素,很容易意识到这是求最大/最小子数组和,由于数字绝对值为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<10^{18}\)。
求整数对A B,要求\(L<=A<=B<=R\),且GCD(A,B)=G,且要最大化B-A。
最关键的点在于GCD(A,B)=G的意义,这表示A,B都是G的倍数,且\(\frac{A}{G}\)和\(\frac{B}{G}\)互质。
然后就是,给定两个数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(C^2logU)\)吧,其中\(C<30\)
E. Matrix Transformation
给定矩阵A,B 定义两种操作
任选矩阵A一行\(i\),任选\(x>=0\),对这一行所有元素执行\(A_{ij} = A_{ij}\&x\)
任选矩阵A一列\(j\),任选\(x>=0\),对这一列所有元素执行\(A_{ji} = A_{ji}|x\)
问,能否将A转换为B
很好的题。
首先要能意识到拆位操作,因为可以选择x=1<<k,则不同bit之间是操作无关的。
问题转换为A,B均为0 1矩阵。
举一个最简明的例子
A左下角的1,必须通过行变换为0,才能与B一致
然后A右下角再执行列变换为1
然后A右上角再执行行变换为0
最后当A左上角执行列变换为1时,此时左下角从0变回了1。
产生了循环,所以A是无法变成B的。
这启发我们,行列的变换不能产生环。
具体的:
- 我们先枚举所有位置,如果\(A_{ij}\)和\(B_{ij}\)不相等,则必须产生一次变换。
如果\(B_{ij}\)为0,则是行变换,否则是列变换。 - 一轮枚举结束后,我们考虑某一变换的行或列,以行为例,该行的每个元素变换后都会变成0
那么我们需要检查B的对应行是否全为0, 有不是0的,则需要对该列执行列变换。 - 如果有新的变换添加进来,则需要重复执行上述检查操作,直到无新增变换为止。
- 此时按照所有产生操作的的行和列,和对应的\(B_{ij}\)的值,进行连边处理。
如果\(B_{i,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);}}//只要有某一位不合法返回NOif (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)\)