Educational Codeforces Round 89 (Rated for Div. 2)
https://codeforces.com/contest/1366
A - Shovels and Swords
贪心
B - Shuffle
正解应该是随便乱搞就可以的,不过当时没有仔细想,而且受到以前做过的另一个idea的影响,导致这道题搞复杂了。
下面的解法可以对付多个 \(x\) 的情况。
注意这里只需要记录 \(r[i]+1\) 进入离散化,和差分的思想类似,每次遇到离散化的点状态才会改变,所以 \(r[i]\) 不需要记录。易知这里的 \(n\) 也没什么用。
int n, m, x;
int idx[2005], top;
bool suc[2005];
int l[1005], r[1005];
int id(int k) {
return lower_bound(idx + 1, idx + 1 + top, k) - idx;
}
void TestCase() {
cin >> n >> x >> m;
top = 0;
idx[++top] = x;
idx[++top] = x + 1;
for(int i = 1; i <= m; ++i) {
cin >> l[i] >> r[i];
idx[++top] = l[i];
idx[++top] = r[i] + 1;
}
sort(idx + 1, idx + 1 + top);
top = unique(idx + 1, idx + 1 + top) - (idx + 1);
fill(&suc[1], &suc[top] + 1, 0);
suc[id(x)] = 1;
for(int i = 1; i <= m; ++i) {
int lid = id(l[i]);
int rid = id(r[i] + 1);
for(int j = lid; j < rid; ++j) {
if(suc[j] == 1) {
fill(&suc[lid], &suc[rid], 1);
break;
}
}
}
int ans = 0;
for(int j = 1; j <= top; ++j) {
if(suc[j])
ans += idx[j + 1] - idx[j];
}
cout << ans << endl;
return;
}
fill是真的好用,以后多多注意这个函数的用法。
C - Palindromic Paths
注意若有奇数个点,则正中间的“对角线”位置的不用修改。
*D - Two Divisors
题意:给 \(n\in[1,5\cdot 10^5]\) 个数,数的范围 \([1,10^7]\) ,对每个数 \(x\) 求下面的子问题。
子问题:给一个数 \(x\) ,求其两个因数 \(d_1>1\) 和 \(d_2>1\) ,满足 \(gcd(d_1+d_2,x)=1\) ,无解输出两个-1。
题解:一开始觉得是随便取两个质因数,就可以,但是很显然若输入120就会翻车。一般认为加法和因数并没有什么关系,所以在这里想了很久,但是突然注意到其实输入120可以输出2和15,也可以输出3和10,也可以输出5和6。就猜测是不是所有的质因数,取出其中一个,然后另外的全部乘在一起,再加起来,会得到一个质数呢?这个很明显是不对的,比如2和7加起来就是合数9,但是惊人发现其实加起来的好像和选出的质因数都互质。
仔细联想一下“不存在最大的质因数”这个证明,就能明白这是为什么。
显然若 \(x\) 只有一种质因子,则无解。设 \(x\) 有 \(k\geq2\) 个质因子 \(p_1,p_2,...,p_k\) ,则构造 \(d_1=p_1,d_2=p_2p_3...p_k\) ,则 \(d_1+d_2=p_1+p_2p_3...p_k\) ,这个显然不含有 \(p_1,p_2,...,p_k\) 中的任意一个质因子。(若 \(d_1+d_2\) 含有质因子 \(p_1\) 则说明 \(p_1|d_2\) 但这是不可能的,其他情况同理)
所以就上网复制一个预处理后 \(O(\log{x})\) 质因数分解的算法。
int n;
int a[500005];
const int MAXN = 1e7;
int p[MAXN + 5], ptop;
int pm[MAXN + 5], pk[MAXN + 5];
void sieve() {
int n = MAXN;
pm[1] = 1;
for(int i = 2; i <= n; ++i) {
if(!pm[i]) {
p[++ptop] = i;
pm[i] = i;
pk[i] = i;
}
for(int j = 1; j <= ptop; ++j) {
int t = i * p[j];
if(t > n)
break;
pm[t] = p[j];
if(i % p[j]) {
pk[t] = pk[p[j]];
} else {
pk[t] = pk[i] * p[j];
break;
}
}
}
}
int ans1[500005];
int ans2[500005];
void TestCase() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
sieve();
for(int i = 1; i <= n; ++i) {
if(a[i] == pk[a[i]]) {
ans1[i] = -1;
ans2[i] = -1;
} else {
int x = a[i];
ans1[i] = pm[x];
x /= pk[x];
ans2[i] = 1;
while(x != 1) {
ans2[i] *= pm[x];
x /= pk[x];
}
}
}
for(int i = 1; i <= n; ++i)
printf("%d%c", ans1[i], " \n"[i == n]);
for(int i = 1; i <= n; ++i)
printf("%d%c", ans2[i], " \n"[i == n]);
return;
}
仔细想想,其实并不需要 \(O(\log{x})\) 质因数分解。选择两个因数 \(d_1=p_1,d_2=x/(p_1)^{\alpha_1}\) 即可,总体复杂度 \(O(n+\max\{x\})\) 。
其他的想法:
\(gcd(d_1+d_2,x)=1\) 显然必要条件为 \(gcd(d_1,d_2)=1\) ,而这个也是充分条件,因为 \(gcd(d_1,d_2)=1\) 得 \(gcd(d_1+d_2,d_2)=1\) 得 \(gcd(d_1+d_2,kd_2)=1\) ,取 \(k=x/d_2\) 即可。
int n;
int p[10000005], ptop;
int pk[10000005];
void sieve() {
int n = 10000000;
for(int i = 2; i <= n; ++i) {
if(!pk[i]) {
p[++ptop] = i;
pk[i] = i;
}
for(int j = 1; j <= ptop; ++j) {
int t = i * p[j];
if(t > n)
break;
if(i % p[j]) {
pk[t] = pk[p[j]];
} else {
pk[t] = pk[i] * p[j];
break;
}
}
}
}
int ans1[500005];
int ans2[500005];
void TestCase() {
sieve();
scanf("%d", &n);
for(int i = 1, x; i <= n; ++i) {
scanf("%d", &x);
if(x == pk[x]) {
ans1[i] = -1;
ans2[i] = -1;
} else {
ans1[i] = pk[x];
ans2[i] = x / pk[x];
}
}
for(int i = 1; i <= n; ++i)
printf("%d%c", ans1[i], " \n"[i == n]);
for(int i = 1; i <= n; ++i)
printf("%d%c", ans2[i], " \n"[i == n]);
return;
}
*E - Two Arrays
题意:给一个 \(n\) 个数的数组 \(a\) 和一个 \(m\) 个数的数组 \(b\) 。数组 \(b\) 单调递增。求把 \(a\) 划分为 \(m\) 个子区间 ,使得第 \(j\) 个子区间的最小值恰好为 \(b_j\) 的方案数。
题解:由于 \(b\) 单调递增,所以先把 \(a\) 求一次后缀最小值变成非严格单调递增。然后双指针法转移出满足 \(a_i=b_j\) 的段的长度分别是多少,除了第一段的长度以外,其他的全部乘起来。
例如用修改一下的第一个样例:
10 3
12 10 20 20 25 30 30 35 32 32
10 20 30
取后缀最小值后为:
10 3
10 10 20 20 25 30 30 32 32 32
10 20 30
那么第一段和第二段的划分位置必须在两个20的前面。
而且第二段和第三段的划分位置必须在两个30的前面。
所以答案是4。
需要注意的是这个25必须分配给第二段,这些32必须分配给第三段。
为什么要取后缀最小值,目的是把数组 \(a\) 变成非严格单调递增,因为在这个问题里要匹配一个严格单调递增的数组 \(b\) ,这样变换是等价的。要保证“第 \(j\) 个子区间的最小值恰好为 \(b_j\) ”,则说明这一段必须保留至少一个 \(b_j\) 并且不能保留任何比 \(b_j\) 小的数。由于这个限制所以比 \(b_j\) 大而比 \(b_{j+1}\) 小的数也必须和 \(b_j\) 放在一起。
这个解法可以解决 \(b_j\) 找不到匹配的 \(a_i\) 的问题(包括 \(m>n\) 的情况),因为这种情况下匹配的长度是0。
但是这个解法少考虑了第一段的 \(b_1\) 之前的数,若有 \(a_1<b_1\) 则说明直接无解。
int n, m;
int a[200005];
int b[200005];
void TestCase() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
for(int j = 1; j <= m; ++j)
scanf("%d", &b[j]);
for(int i = n - 1; i >= 1; --i)
a[i] = min(a[i + 1], a[i]);
ll ans = (a[1] == b[1]);
for(int j = 1, i = 1; j <= m; ++j) {
while(i <= n && a[i] < b[j])
++i;
int len = 0;
while(i <= n && a[i] == b[j]) {
++i;
++len;
}
if(j > 1)
ans = ans * len % MOD;
}
printf("%lld\n", ans);
return;
}