Codeforces Round #641 (Div. 1)

https://codeforces.com/contest/1349

记错时间错过了。但是感觉可以补一下。一天一场div1(1/30)。

A - Orac and LCM

题意:给 \(n\in[1,100000]\) 个数的数组 \(a\)\(a_i\) 范围在 \([1,200000]\) 。定义一个集合 \(s\)\(gcd\) 为被 \(s\) 中的所有数整除的最大的数,定义一个集合 \(s\)\(lcm\) 为整除 \(s\) 中的所有数的最小的数。取出数组 \(a\) 的所有两两组合数数对的 \(lcm\) 记作集合 \(t\) ,求 \(gcd(t)\)

题解:就是说先取数对的 \(lcm\) 然后再取所有数的 \(gcd\) ,首先因为 \(lcm\) 每种质因子中最大的那个一定会出现在 \(t\) 的某个数的因子中,但是这好像没有用因为会被 \(gcd\) 去掉。

所以直接就是求所有数的 \(gcd\) ?观察第二个样例 \(lcm(10,24)=120\) ,确实包含因子 \(40\) ,答案并不是 \(2\) 。仔细一想这种题肯定是直接分解成质因数形式观察就可以了。

10 24 40 80

以质因子 \(2\) 的角度考察,得到

1 2 3 4

他们的 \(lcm\) 集合为

2 3 4 3 4 4

gcd的结果为

2

所以就是取这种质因数中最小的两个就可以了,考虑如何快速对这些数进行质因数分解,最简单的思路就是直接random_shuffle或者sort,然后验证所有的质因子 \(k\) ,若是某个质因子 \(k\) 有至少2个数不含 \(k\) 则直接break。注意到每个 \(a_i\) 至多有 \(6\) 种不同的质因子,应该大部分都是提前break的了,假如有某个质因子到很后才break,则这种质因子不应该超过十几种。

PS:溢出了,太久不打搞得离谱,还好没打呢,好演啊。

int n;
int a[100005];
bool vis[200005];

int check(int d) {
    int mind1 = INF, mind2 = INF;
    int cnt0 = 0;
    for(int i = 1; i <= n; ++i) {
        if(a[i] % d != 0) {
            ++cnt0;
            if(cnt0 >= 2)
                return 1;
        }
        int cnt = 0;
        int x = a[i];
        while(x % d == 0) {
            ++cnt;
            x /= d;
        }
        if(cnt < mind2) {
            mind2 = cnt;
            if(mind2 < mind1)
                swap(mind1, mind2);
        }
    }
    int res = 1;
    while(mind2--)
        res *= d;
    return res;
}

void TestCase() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    sort(a + 1, a + 1 + n);
    ll ans = 1;
    for(int i = 2; i <= 200000; ++i) {
        if(vis[i])
            continue;
        ans *= check(i);
        for(int j = 2 * i; j <= 200000; j += i)
            vis[j] = 1;
    }
    printf("%lld\n", ans);
    return;
}

另一种解法是,求出前缀gcd和后缀gcd,然后再全部搞一次lcm,这个看起来就太复杂了。

B - Orac and Medians

题意:给一个 \(n\in[1,100000]\) 个数的数组 \(a\) ,每次操作可以选择一段连续区间,然后把这段区间的值全部替换成这段区间的中位数,假如区间的长度是偶数则选择小的那个(左)中位数。问是否能把数组 \(a\) 变为全部都是 \(k\)

(假的)题解:首先 \(k\) 必须要出现。然后,发现一个惊人的事实: \(k\) 可以“感染”两侧的比它大的数!而连续两个的 \(k\) 可以“感染”两侧的比它小的数!所以只要存在一个 \(k\) 的两侧至少有一个大于等于它的数,就是yes。

但这只是充分条件而不是必要条件,经过这个检测之后还暂时是no的话,则说明所有的 \(k\) 两侧都是比它小的数,但是可能可以通过选取一个区间使得 \(k\) 恰好成为(左)中位数。基于上面的分析,我们得知只要有一个长度至少为2的区间使得 \(k\) 为(左)中位数,答案就是yes。

这个可能要借助一下数据结构?把所有比 \(k\) 小的视作 \(-1\) ,把所有比 \(k\) 大的视作 \(+1\) ,好像变成了最大子数组和dp?

参考一下别人的:https://www.cnblogs.com/dysyn1314/p/12881386.html

再看一下官方题解,貌似是只要存在一对相邻的大于等于 \(k\) 的数,他们之间夹的小于 \(k\) 的数小于等于1,就可以构造。充分性是显然的:首先,选择这一对数作为左右边界,那么区间内的数都会变成大于等于 \(k\) ,然后选择其中的连续的两个大于等于 \(k\) ,再加上新的一个数,一定可以变成三个大于等于 \(k\) (也就是说可以向两侧扩展),若这两个数之中至少有一个是 \(k\) ,那么这就是一个构造,否则向两侧扩展之后必定会遇到 \(k\) 然后被 \(k\) 感染。

必要性的话就不是特别显然,若任意两个相邻的大于等于 \(k\) 的数之间夹了至少两个小于 \(k\) 的数,则无论怎么选择都只能把区间变成小于 \(k\) 的数。

这一场也太恶心了吧。

int n, k;

void TestCase() {
    scanf("%d%d", &n, &k);
    int prev_ge = -1, mindis = INF, exist_k = 0;
    for(int i = 1, a; i <= n; ++i) {
        scanf("%d", &a);
        if(a >= k) {
            if(prev_ge != -1)
                mindis = min(mindis, i - prev_ge);
            prev_ge = i;
            if(a == k)
                exist_k = 1;
        }
    }
    if(exist_k == 1 && (n == 1 || mindis <= 2))
        puts("yes");
    else
        puts("no");
    return;
}
posted @ 2020-05-12 23:48  KisekiPurin2019  阅读(349)  评论(0编辑  收藏  举报