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;
}
posted @ 2020-06-13 04:05  KisekiPurin2019  阅读(208)  评论(0编辑  收藏  举报