Atcoder Regular Contest 128

A  Gold and Silver

考虑一个贪心策略,在金价高的时候把它换成白银,在金价低的时候把白银换成黄金。

于是这题就没了。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string> 
#include <algorithm>

//inline int min(const double x, const double y) {return x < y ? x : y;}
//inline int max(const double x, const double y) {return x > y ? x : y;}

int ans[200005], a[200005], j, lst, n;

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++ i) scanf("%d", a + i);
    for (int i = 1; i <= n; ++ i, j ^= 1) {
        if (!j) {
            while (i < n && a[i] <= a[i + 1]) ++ i;
            ans[i] = 1, lst = i;
        } else {
            while (i < n && a[i] >= a[i + 1]) ++ i;
            ans[i] = 1, lst = i;
        }
    }
    if (j) ans[lst] = 0;
    for (int i = 1; i <= n; ++ i) printf("%d ", ans[i]);
    return 0;
}
View Code

B  Balls of Three Colors

不妨设 $G\ge B$,假设我们要把所有球都变成红色。

用 $B$ 次操作将绿球和蓝球变红。此时蓝球没了,绿球还剩 $G-B$ 个。

考虑如下操作:

1. 将红球和绿球转换成蓝球;

2. 将绿球和蓝球转换成红球;

3. 将绿球和蓝球转换成红球。

此时,绿球数量减 $3$,蓝球数量还是为 $0$,红球数量加 $3$。

即必须要 $3\vert G-B$,我们才能把所有球变成红球。显然以上操作步数最少,为 $G$ 次。

然后考虑三种情况(全部变红,全部变绿,全部变蓝)就行了。

#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#include <cmath>

inline int min(const int x, const int y) {return x < y ? x : y;}
inline int max(const int x, const int y) {return x > y ? x : y;}
int a[4];

int main() {
    int T;
    scanf("%d", &T);
    while (T --) {
        scanf("%d%d%d", a + 1, a + 2, a + 3);
        int ans = 1e9;
        for (int i = 1; i <= 3; ++ i)
        for (int j = i + 1; j <= 3; ++ j)
            if (abs(a[i] - a[j]) % 3 == 0) ans = min(ans, max(a[i], a[j]));
        printf("%d\n", ans == 1e9 ? -1 : ans);
    }
    return 0;
}
View Code

 

C  Max Dot

助我上大分的题

先对这个原数列求一个后缀和,为了方便表述,令 $suf_i=\sum\limits^n_{j=i}A_j$。

先不考虑 $m$ 的限制,我们的答案就是 $S\times \operatorname{max}\{suf_i/i\}$。即找到一个“性价比”最高的后缀,把它们全部设为 $S/len$,其中 $len$ 为这个后缀的长度,即 $n-i+1$。

现在有了 $m$ 的限制,如果 $S/len$ 超过了 $m$,我们的做法就炸了。所以若 $S/len\ge m$,我们先把这个后缀所有数都设为 $m$,然后将 $S$ 减去 $len\times m$,对于原序列的子序列 $a_1,a_2...a_{i-1}$ 再做一遍上述的方法,直到 $S=0$ 为止。

复杂度 $O(n^2)$

#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#include <cmath>
#define int long long

inline double min(const double x, const double y) {return x < y ? x : y;}
inline double max(const double x, const double y) {return x > y ? x : y;}
int a[5005], suf[5005], n, m, s, mx;
double sm[5005], ans = 0;

signed main() {
    scanf("%lld%lld%lld", &n, &m, &s);
    for (int i = 1; i <= n; ++ i) scanf("%lld", a + i);
    for (int lp = n; lp && s >= 1e-8;) {
        mx = 0;
        suf[lp + 1] = 0;
        for (int i = lp; i; -- i) suf[i] = suf[i + 1] + a[i];
        for (int i = lp; i; -- i) {
            suf[i] = suf[i + 1] + a[i];
            sm[i] = 1.0 * suf[i] / (lp - i + 1);
            if (sm[i] > sm[mx]) mx = i;
        }
        double tmp = min(1.0 * (lp - mx + 1) * m, s);
        ans += tmp * sm[mx], s -= tmp, lp = mx - 1;
    }
    printf("%.6lf", ans);
    return 0;
}
View Code

D  Neq Neq

有一个很显然的 dp,$dp_i$ 表示以 $A_i$ 结尾的方案数。

则 $dp_i=\sum dp_j$,其中条件是可以通过某种方法把 $[j+1,i-1]$ 全删掉。

问题就变成了怎么判这个是否能全删掉。

先做一些初步的分析。

若 $A_i=A_{i+1}$,则它们显然删不掉,把它们看作一个数,但它是删不掉的。所以我们干脆就把它作为起点和终点分别计算答案。

什么意思呢,就是比如 $1,2,3,4,4,5,6,7,8,8,8,8,8,9$,我们把它看作三个序列来处理:

$$1,2,3,4$$

$$4,5,6,7,8$$

$$8,9$$

然后把三个序列的方案数乘起来即可。

所以简化后的序列总长度之和仍然是 $O(n)$ 级别,而且不存在两个相邻元素相等的情况。

比赛时想到这里就没更多进展了。

而官方题解给出了证明,即在我们处理后的序列中,显然 $dp_i$ 能由 $dp_{i-1},dp_{i-2}$ 转移而来。而对于一个长度 $\ge 4$ 的子段,当且仅当它含有元素种类至少为 $3$ 时,才能把这个子段删得只剩下两端的数。在我们的 dp 方程中,两端就分别是 $i,j$。

到这里就可以写代码了,但这道题最难的地方就是这个证明和猜到或者推出这个结论。

官方给的证明是归纳法:

首先长度为 $4$ 的子段可以手玩出来结论正确。

对于长度为 $n(n>4)$ 的子段,由于其元素种类个数 $\ge 3$,因此必然存在一个 $i$ 满足 $A_{i-1}\ne A_i\ne A_{i+1}$。

考虑两种情况,第一种是我们把 $A_i$ 删了过后,剩下的长度为 $n-1$ 的序列仍然满足要求(相邻元素不相同且含有至少三种不同元素)。这种情况把 $A_i$ 删了就可以证了。

否则,即 $A_i$ 只在子段中出现了一次,我们把 $A_{i-1}$ 或 $A_{i+1}$ 删掉,剩下的子段仍然满足要求。

总的复杂度显然为 $O(n)$。

#include <cstdio>
#define int long long

const int mod = 998244353;
int a[200005], b[200005], cnt[200005], dp[200005], sum[200005], l[200005], r[200005], tot;

int solve(int n) {
    for (int i = 1; i <= n; ++ i) cnt[b[i]] = dp[i] = 0;
    dp[1] = sum[1] = cnt[b[1]] = 1;
    for (int i = 2, j = 0, now = 1; i <= n; ++ i) {
        if (!cnt[b[i]]) ++ now;
        ++ cnt[b[i]];
        dp[i] = (dp[i - 1] + dp[i - 2]) % mod;
        if (now < 3) {sum[i] = (sum[i - 1] + dp[i]) % mod; continue;}
        while (now >= 3 && j < i - 3) {
            if (!-- cnt[b[j]]) -- now; ++ j;
        }
        if (now < 3) now = 3, ++ cnt[b[-- j]];
        sum[i] = (sum[i - 1] + (dp[i] = (dp[i] + sum[j]) % mod)) % mod;
    }
    return dp[n];
}

signed main() {
    int n, ans = 1;
    scanf("%lld", &n);
    for (int i = 1; i <= n; ++ i) scanf("%lld", a + i);
    int p = 1;
    while (p < n && a[1] == a[p + 1]) ++ p;
    l[tot = 1] = 1, r[1] = p;
    for (int i = p + 1; i <= n; ++ i) {
        int j = i;
        while (j < n && a[i] == a[j + 1]) ++ j;
        if (i != j) l[++ tot] = i, r[tot] = j;
        i = j;
    }
    if (r[tot] != n) ++ tot, l[tot] = r[tot] = n;
    for (int i = 1; i < tot; ++ i) {
        int len = 0;
        for (int j = r[i]; j <= l[i + 1]; ++ j) b[++ len] = a[j];
        ans = ans * solve(len) % mod;
    }
    printf("%lld", ans);
    return 0;
}
View Code

 

posted @ 2021-10-17 14:29  zqs2020  阅读(58)  评论(0编辑  收藏  举报