Codeforces #641 div2 A - E 解题报告
A. Orac and Factors
\(Description:\)
设 \(f(n)\) 是 \(n\) 的最小约数 \((f(n) > 1)\)。那么对于一次操作而言 \(n = n + f(n)\),求第 \(k\) 次操作后,\(n\) 为多少?
\(Solution:\)
对于 \(n\) 是偶数的情况下,\(f(n) = 2\),那么加上 \(f(n)\) 后 \(n\) 仍然是偶数,所以每次都 \(+ 2\)。
对于 \(n\) 是奇数的情况下,\(f(n)\) 也一定是奇数,那么加上 \(f(n)\) 后,\(n\) 就变成了偶数。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
int t; cin >> t;
while(t --){
ll n, k;
cin >> n >> k;
if(n & 1){
int mark = 0;
for(int i = 2; i * i <= n; i ++){
if(n % i == 0){
n += i;
mark = 1;
break;
}
}
if(!mark) n += n;
cout << n + (k - 1) * 2 << endl;
}else{
cout << n + k * 2 << endl;
}
}
return 0;
}
B. Orac and Models
\(Description:\)
给定一个长为 \(n\) 的数组 \(a\),在数组挑选几个数组成一个新序列,要求新序列的任意两个数要满足:\(j\ mod\ i = 0\ and\ a_i < a_j\),\(i, j\) 是该数在原数组的下标。
\(Solution:\)
设 \(f[i]\) 为以 \(a_i\) 结尾的构成合法新序列的最长长度。那么我们去枚举 \(i\) 的约数 \(j\),于是得到:\(f[i] = max(f[i], f[j] + 1)\)。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
int s[N], f[N];
int main(){
int t; cin >> t;
while(t --){
cin >> n;
for(int i = 1; i <= n; i ++)
scanf("%d", &s[i]);
for(int i = 1; i <= n; i ++){
f[i] = 1;
for(int j = 1; j * j <= i; j ++){
if(i % j == 0){
if(s[i] > s[j])
f[i] = max(f[i], f[j] + 1);
if(s[i] > s[i / j])
f[i] = max(f[i], f[i / j] + 1);
}
}
}
cout << *max_element(f + 1, f + n + 1) << endl;
}
return 0;
}
C. Orac and LCM
\(Description:\)
给定长度为 \(n\) 的数组 \(a\),求由 \((lcm(a_i, a_j)\ |\ i < j)\) 得到一些数的 \(gcd\)。
\(Solution:\)
单独看 \(a_1\),我们可以得到 \(lcm(a_1,a_2),lcm(a_1,a_3),...,lcm(a_1,a_n)\)。则:
我们预处理出一个 \(gcd\) 的后缀,就可以简单的得出结果。
证明一下上面的公式:
假设:\(lcm(a_1, a_2) = a_1 \times a_2 \times x_2\),那么 \(x_2 = \frac{1}{gcd(a_1,a_2)}\);
同理:\(lcm(a_1,a_3) = a_1 \times a_3 \times x_3\);那么 \(x_3 = \frac{1}{gcd(a_1,a_3)}\);
那么设: \(t = gcd(lcm(a_1,a_2),lcm(a_1,a_3)) = gcd(a_1 \times a_2 \times x_2, a_1 \times a_3 \times x_3) = a_1 \times gcd(a_2 \times x_2, a_3 \times x_3)\)。
因为:\(gcd(ab, m) = gcd(a, m) \times gcd(b, m)\);
所以:\(t = a_1 \times gcd(a_2, a_3) \times gcd(x_2, x_3) \times gcd(a_2,x_3) \times gcd(a_3, x_2)\);
由于:\(gcd(\frac{1}{a}, \frac{1}{b}) = \frac{1}{gcd(a,b)}\)
则:\(gcd(a_2, x_3) = gcd(\frac{a_2}{1}, \frac{1}{gcd(a_1,a_3)}) = \frac{gcd(a_2,1)}{gcd(1,gcd(a_1,a_3))} = 1\);
同理:\(gcd(a_3,x_2) = 1\);
那么:
\(t = a_1 \times gcd(a_2,a_3) \times gcd(x_2, x_3) = \frac{a_1 \times gcd(a_2, a_3)}{gcd(gcd(a_1,a_2), gcd(a_1,a_3))}\);
由:\(gcd\) 满足交换律:\(gcd(gcd(a,b), gcd(a,c)) = gcd(a, a, b, c) = gcd(a, b, c) = gcd(a, gcd(b, c))\);
那么:\(t = \frac{a_1 \times gcd(a_2,a_3)}{gcd(a_1,gcd(a_2,a_3))} = lcm(a_1,gcd(a_2,a_3))\).
证毕。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef long long ll;
int n;
ll a[N], g[N];
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
ll lcm(ll a, ll b) { return a * b / gcd(a, b); }
int main(){
cin >> n;
for(int i = 1; i <= n; i ++)
scanf("%d", &a[i]);
g[n + 1] = 0;
for(int i = n; i >= 1; i --)
g[i] = gcd(g[i + 1], a[i]);
ll ans = 0;
for(int i = 1; i < n; i ++){
ll tmp = lcm(a[i], g[i + 1]);
ans = gcd(ans, tmp);
}
cout << ans << endl;
return 0;
}
D. Orac and Medians
\(Description:\)
给定长为 \(n\) 的数组 \(a\) 和一个 \(k\),,问是否可以通过任意次操作将数组每个元素变为 \(k\)?
操作是:将数组区间 \([l, r]\) 的数变为该区间的中位数。
\(Solution:\)
很显然的一个判断是确认数组中有没有 \(k\) 这个元素。
对于中位数的确认,显然涉及到了排序,但是时间不允许。但是如果我们只考虑长度 \(2\) 的区间,显然小的那个数就是中位数。那么对于固定的 \(k\) 来说,我们只需要他的左边或右边存在一个 \(\geq k\) 的数,那么就可以变为 \(k\),那么此时我们就得到了两个连续的 \(k\),那么我们在这个区间上去扩展一个元素,即区间长度为 \(3\),无论第三个元素的大小,中位数都一定是 \(k\),那么我们就得到了三个连续的 \(k\),以此类推下去我们就可以构造出来了。
那么我们接下来的任务就是在 \(k\) 的左边或右边搞出一个 \(\geq k\) 的数,那么我们根据上面的思路,如果存在两个连续的数 \(\geq k\),那么我们就可以扩展到三个数,四个数......直到遇到扩展的元素为 \(k\)。
那么我们就得到了一个条件:存在两个连续的数 \(\geq k\);
如果不满足上述条件是不是就一定不可以了呢?对于这种情况:\(a_i, a_{i+1}, a_{i+2},(a_i \geq k, a_{i+1} < k, a_{i+2} \geq k)\),显然也是可以的。所以只要满足这两个条件就可以了。
还有一个坑点就是要特判 \(n = 1\) 的情况。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, k;
int a[N];
bool judge(){
int mark = 0;
for(int i = 1; i <= n; i ++){
if(a[i] == k) mark = 1;
}
if(!mark) return 0;
if(n == 1 && a[n] == k) return 1;
for(int i = 2; i <= n; i ++){
if(a[i] >= k && a[i - 1] >= k) return 1;
if(i >= 3 && a[i] >= k && a[i - 2] >= k) return 1;
}
return 0;
}
int main(){
int t; cin >> t;
while(t --){
cin >> n >> k;
for(int i = 1; i <= n; i ++)
scanf("%d", &a[i]);
if(judge()) puts("yes");
else puts("no");
}
return 0;
}
E. Orac and Game of Life
\(Description:\)
给出一个 \(0, 1\) 矩阵,每经过一轮循环矩阵就有可能变换。
\(1.\) 如果一个元素存在一个相邻元素和他相同,那么该元素就会变换。
\(2.\) 如果不存在,那么该轮循环就不会发生变换。
\(Solution:\)
显然的一个事实是:如果矩阵中不存在一组相邻的元素相同,那么矩阵就永远不会变。否则,矩阵的每一位元素都有可能发生变换,可以参考样例三。
那么我们只需要把那些可以发生变换的元素提取出来,然后用他们去同化那些原本不会变的元素即可,记录下每个元素的第一次变换的循环是那一次即可。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10, INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
typedef long long ll;
int n, m, t;
char s[N][N];
int a[N][N];
int vis[N][N];
int d[N][N];
int to[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
int check(int x, int y){
if(x >= 1 && x <= n && y >= 1 && y <= m) return 1;
return 0;
}
int main(){
cin >> n >> m >> t;
for(int i = 1; i <= n; i ++)
scanf("%s", s[i] + 1);
memset(vis, 0, sizeof vis);
memset(d, INF, sizeof d); // 第一次变换的循环
queue<PII> q;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j ++){
a[i][j] = s[i][j] - '0';
for(int k = 0; k < 4; k ++){
int x = i + to[k][0];
int y = j + to[k][1];
if(check(x, y) && s[x][y] == s[i][j]){
q.push({i, j}); // 提取存在相邻相同元素的
d[i][j] = 0; // 置为 0,因为原本就会变
vis[i][j] = 1; // 标记已经访问过
break;
}
}
}
while(!q.empty()){
PII t = q.front(); q.pop();
int x = t.first, y = t.second;
for(int i = 0; i < 4; i ++){ // 用这个元素去同化他周围的
int xx = x + to[i][0];
int yy = y + to[i][1];
if(check(xx, yy) && !vis[xx][yy]){
q.push({xx, yy});
d[xx][yy] = d[x][y] + 1;
vis[xx][yy] = 1; // 标记一下
}
}
}
while(t --){
int x, y;
ll p; // p 很大
scanf("%d%d%lld", &x, &y, &p);
p -= d[x][y]; // 减去需要同化的循环次数
if(p <= 0 || d[x][y] == INF) printf("%d\n", a[x][y]);
else printf("%d\n", a[x][y] ^ (p & 1));
}
return 0;
}