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
题意:给 (nin[1,5cdot 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) 有 (kgeq2) 个质因子 (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], "
"[i == n]);
for(int i = 1; i <= n; ++i)
printf("%d%c", ans2[i], "
"[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], "
"[i == n]);
for(int i = 1; i <= n; ++i)
printf("%d%c", ans2[i], "
"[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
", ans);
return;
}