Educational Codeforces Round 123 (Rated for Div. 2) 题解(A-E)

1644A - Doors and Keys

题意: 3 3 3 种颜色的门和钥匙,需要先遇到小写字母的钥匙后打开大写字母的门,问能不能通过去。

简单模拟即可,当存在一个小写字母的时候记录一下,遇到大写字母的时候检查其对应的小写字母是否存在。

参考实现 1:

char s[7];

int main() {
    int T; read(T);
    while (T--) {
        read(s);
        bool vis[3] = {0, 0, 0}, flg = 1;
        FOR(i, 0, 5) {
            if (s[i] == 'r') vis[0] = 1;
            else if (s[i] == 'g') vis[1] = 1;
            else if (s[i] == 'b') vis[2] = 1;
            else if (s[i] == 'R') flg &= vis[0];
            else if (s[i] == 'G') flg &= vis[1];
            else if (s[i] == 'B') flg &= vis[2];
        }
        print(flg ? "YES" : "NO");
    }
    return output(), 0;
}

参考实现 2:

int main()
{
    int t;cin>>t;
    rep(kase,1,t)
    {
        set<char> S;
        string str;cin>>str;
        bool flg=1;
        for(auto &c:str)
        {
            S.insert(c);
            if(isupper(c)&&!S.count(tolower(c)))
            {
                flg=0;
                break;
            }
        }
        cout<<(flg?"YES":"NO")<<'\n';
    }
    return 0;
}

1644B - Anti-Fibonacci Permutation

题意: T T T 组数据,给定 n n n,要求构造 n n n 个“反斐波那契排列”,一个反斐波那契排列满足 ∀ i ( 3 ≤ i ≤ n ) \forall i( 3\le i\le n) i(3in),有 p i − 2 + p i − 1 ≠ p i p_{i-2}+p_{i-1}\ne p_i pi2+pi1=pi t ≤ 48 , n ≤ 50 t\le 48,n\le50 t48,n50

div 2 的 B 题,考虑从简单的形式开始思考如何构造。

我们发现,一个形如 1 , 2 , 3 , ⋯   , n 1,2,3,\cdots, n 1,2,3,,n 的排列只存在一个地方不满足限制: 1 + 2 = 3 1+2=3 1+2=3。那么只要将 1 1 1 扔到排列尾部, 2 , 3 , ⋯   , n , 1 2,3,\cdots,n,1 2,3,,n,1 显然是满足要求的排列,然后将前面的 n − 1 n-1 n1 个元素循环移位一下,形如 i , i + 1 , ⋯   , n , 1 , ⋯   , i − 1 , 1 i,i+1,\cdots,n,1,\cdots,i-1,1 i,i+1,,n,1,,i1,1。就能构造出 n − 1 n-1 n1 个满足题意的排列。

还剩下一个,怎么办呢,上面的排列都以 2 , 3 , ⋯   , n 2,3,\cdots, n 2,3,,n 开头,这次我们考虑让 1 1 1 开头。发现 1 , n , 2 , 3 , ⋯   , n − 1 1, n, 2,3,\cdots, n -1 1,n,2,3,,n1 满足题意,直接输出即可。

int main() {
    int T; read(T);
    while (T--) {
        int n; read(n);
        FOR(i, 2, n) {
            FOR(j, i, n) print(j, ' ');
            FOR(j, 2, i - 1) print(j, ' ');
            if (i != 1) print(1, ' ');
            putchar('\n');
        }
        print(1, n, ' ');
        FOR(j, 2, n - 1) print(j, ' ');
        putchar('\n');
    }
    return output(), 0;
}

1644C - Increase Subarray Sums

题意:给定一个序列 a a a 和一个非负整数 x x x,令 f ( k ) f(k) f(k) 为 对 a a a 中的 k k k 个不同元素都加上 x x x 后, a a a 的最大子段和。对于 k ∈ [ 0 , n ] k\in[0,n] k[0,n] 求出 f ( k ) f(k) f(k) n ≤ 5000 n\le 5000 n5000,数据不会爆 int

解法 1:基于经典的 O ( n ) O(n) O(n) 最大子段和的 DP,令 f i , j f_{i,j} fi,j 为强制选 a i a_i ai,对 j j j 个元素加了 x x x 后的最大子段和,则类比经典 dp 的转移,我们可以得到
{ f i , 0 = max ⁡ ( f i − 1 , 0 , 0 ) + a i f i , j = max ⁡ ( f i − 1 , j + a i , a i , f i − 1 , j − 1 + a i + x , a i + x ) 1 ≤ j ≤ i \begin{cases} f_{i,0} = \max(f_{i-1,0},0)+a_i\\ f_{i,j} = \max(f_{i-1,j}+a_i,a_i,f_{i-1,j-1}+a_i+x,a_i+x)&1\le j\le i \end{cases} {fi,0=max(fi1,0,0)+aifi,j=max(fi1,j+ai,ai,fi1,j1+ai+x,ai+x)1ji
记得输出答案的时候取前缀最大值,然后要和 0 0 0 max ⁡ \max max

const int maxn = 5005;
int a[maxn], n, x;
int f[maxn][maxn], ans[maxn];

int main() {
    int T; read(T);
    while (T--) {
        read(n, x);
        FOR(i, 1, n) {
            read(a[i]);
            FOR(j, 0, n) f[i][j] = -2e9, ans[j] = 0;
        }
        f[0][0] = 0;
        FOR(i, 1, n) {
            f[i][0] = max(f[i - 1][0] + a[i], a[i]);
            FOR(j, 1, i) {
                f[i][j] = max(f[i - 1][j] + a[i], a[i], f[i - 1][j - 1] + a[i] + x, a[i] + x);
            }
            FOR(j, 0, i) chkmax(ans[j], f[i][j]);
        }
        FOR(i, 0, n) print(ans[i] = chkmax(ans[i], ans[i - 1]), ' ');
        putchar('\n');
    }
    return output(), 0;
}

解法 2:考虑贪心。既然要求 f ( k ) f(k) f(k),那就求出所有长度至少为 k k k 的子段和,取其最大值加上 k × x k\times x k×x 即可。正确性:去除的子段长度一定要 ≥ k \ge k k 才能给 k k k 个位置加上 x x x。然后同样要对答案取前缀最大值。

#define N 5005

int sum[N][N],a[N],ans[N],maxsum[N],n, x;

int main()
{
    int t;cin>>t;
    rep(kase,1,t)
    {
        cin>>n>>x;
        rep(i,1,n)cin>>a[i],sum[1][i]=sum[1][i-1]+a[i];
        rep(i,2,n)rep(j,i,n)sum[i][j]=sum[1][j]-sum[1][i-1];
        per(len,n,0)
        {
            maxsum[len]=-2e9;
            if(len>0) rep(i,1,n-len+1)chkmax(maxsum[len],sum[i][i+len-1]);
            if(len<n) chkmax(maxsum[len],maxsum[len+1]);
        }
        rep(k,0,n)ans[k]=max(maxsum[k]+k*x,0);
        rep(k,1,n)chkmax(ans[k],ans[k-1]);
        rep(k,0,n)cout<<ans[k]<<' ';
        cout<<'\n';
    }
    return 0;
}

1644D - Cross Coloring

题意: n × m n\times m n×m 的格子一开始全为白色, q q q 次操作,每次操作选择 ( r , c ) (r, c) (r,c),然后将第 r r r 行全部和第 c c c 列全部格子涂成 [ 1 , k ] [1, k] [1,k] 中的任意颜色。问最后有多少种可能的局面。 T ≤ 1 0 4 T\le 10^4 T104 1 ≤ n , m , k , q ≤ 2 × 1 0 5 1\le n,m,k,q\le 2\times 10^5 1n,m,k,q2×105 ∑ q ≤ 2 × 1 0 5 \sum q\le 2\times 10^5 q2×105

遇到涂色问题不妨倒序考虑,我们会发现,一次涂色操作会对答案产生贡献,当且仅当它影响的行或列至少有一方没被覆盖完。那么我们用 std::set 分别维护一下被占用了的行和列,若当前操作的 r r r c c c 没有被占用,那么就加入 set 并且让答案乘上 k k k。有一个小优化即记录一下占用的行和列的个数,若占用了 n n n 行或占用了 m m m 列则直接终止循环。

const int maxn = 2e5 + 5;
int n, m, k, q;
int qx[maxn], qy[maxn];

int main() {
    int T; read(T);
    while (T--) {
        read(n, m, k, q);
        FOR(i, 1, q) read(qx[i], qy[i]);
        int visr = 0, visc = 0;
        set<int> remr, remc;
        modint ans = 1;
        DEC(i, q, 1) {
            if (visr == n || visc == m) break;
            int flg = 0;
            if (!remr.count(qx[i]))
                ++visr, remr.insert(qx[i]), flg = 1;
            if (!remc.count(qy[i]))
                ++visc, remc.insert(qy[i]), flg = 1;
            if (flg) ans *= k;
        }
        print(ans);
    }
    return output(), 0;
}

另外,部分代码使用的是 O ( n T ) O(nT) O(nT) 的预处理来维护被占用的行/列,但是由于没有 ∑ n \sum n n 的限制,这样的代码可能过了 Pretests 后由于评测波动被 hack(如果常数偏大)。这启示我们注意观察题目限制条件,并且平时尽可能写常数小的代码

1644E - Expand the Path

题意:给定 n × n n\times n n×n 的网格,一个机器人初始在 ( 1 , 1 ) (1,1) (1,1),给定其由若干个 RD 构成的操作序列,两种字母分别代表右移一格和下移一格。你可以以任意次数将操作序列中的某个操作复制一遍,例如 RDR 可以变成 RDDR 也可以变成 RRDR,但要求机器人不能走出网格。问机器人有可能经过的格子数量。 1 ≤ T ≤ 1 0 4 1\le T\le 10^4 1T104 2 ≤ n ≤ 1 0 8 2\le n\le 10^8 2n108,操作序列总长 ≤ 2 × 1 0 5 \le 2\times 10^5 2×105

首先对于全是 R 或全是 D 的操作序列,答案显然为 n n n,特判掉即可。

否则令 R 的个数为 c n t R \mathrm{cnt}_R cntRD 的个数为 c n t D \mathrm{cnt}_D cntD,则我们发现,对于第一个 RD 或者 DR 之后的每个格子,其都可以覆盖以其为左上角的 ( n − c n t R + 1 ) × ( n − c n t D + 1 ) (n - \mathrm{cnt}_R + 1)\times (n - \mathrm{cnt}_D + 1) (ncntR+1)×(ncntD+1) 的矩形,因为可以通过任意倍增 R 或者 D 来实现。

现在的问题就是数出这些格子的数量。画图之后可以发现,我们可以行列分开考虑。对于每行的贡献加上对于每列的贡献再加上右下角的矩形即为答案,当然需要统计一下本身走过的路径,详见代码。

using ll = long long;
const int maxn = 2e5 + 5;
char s[maxn];

int main() {
    int T; read(T);
    while (T--) {
        ll n, cntr = 0, cntd = 0;
        read(n);
        read(s + 1);
        int len = strlen(s + 1);
        ll nowx = 1, nowy = 1;
        FOR(i, 1, len) {
            if (s[i] == 'D') {
                ++cntd;
            } else {
                ++cntr;
            }
        }
        if (cntr == 0 || cntd == 0) {
            print(n);
        } else {
            ll cntr2 = 0, cntd2 = 0, ans = len + 1; // 首先会覆盖 len + (1,1) 个格子
            if (s[1] == 'R') { // 分类讨论
                ans += (cntd + 1) * (n - cntr - 1); // 这是关于每行的贡献
                FOR(i, 1, len) {
                    if (s[i] == 'D') break;
                    ++cntr2; // 统计开头的 R 操作的个数
                }
                ans += (cntr - cntr2 + 1) * (n - cntd - 1); // 统计关于每列的贡献,需要去除开头的连续的不会产生贡献的 R
            } else { // 下面的情况同理
                ans += (cntr + 1) * (n - cntd - 1);
                FOR(i, 1, len) {
                    if (s[i] == 'R') break;
                    ++cntd2;
                }
                ans += (cntd - cntd2 + 1) * (n - cntr - 1);
            }
            ans += (n - cntr - 1) * (n - cntd - 1); // 最后需要计算右下角的矩形
            print(ans);
        }
        FOR(i, 1, len) s[i] = 0;
    }
    return output(), 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值