AtCoder Regular Contest 180(A~D)

A.ABA and BAB(递推)

题意:

给你一个长度为 N N N的字符串 S S S,由字符AB组成。

您可以按任意顺序执行以下两种类型的操作零次或多次:

  • S S S中选择一个(连续)子串ABA并替换为A
  • S S S中选择一个(连续的)子串BAB并替换为B

求出进行上述操作后,可能的字符串的数目 S S S,答案对 1 0 9 + 7 10^9+7 109+7取模。

分析:

我们从前往后每次找出极长的一段ABAB...BABA...,很明显相邻两段是无法合并的,根据乘法原理计算每段可形成的形态的个数最后乘起来即可。

ABAB...BABA...的形态个数我们可以用递推来预处理。设 d p i dp_i dpi表示长度为 i i iABAB...BABA...的形态个数。递推式为: d p i = d p i − 2 + 1 dp_i=dp_{i−2}+1 dpi=dpi2+1 d p 1 dp_1 dp1 d p 2 dp_2 dp2要初始化为 1 1 1

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const int N = 250005;
const int MOD = 1000000007;
char a[N];
LL dp[N];

int main() {
    dp[1] = 1;
    dp[2] = 1;
    for (int i = 1; i < N; i++)
        dp[i] = dp[i - 2] + 1;
    LL n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    char lst = a[1];
    LL ls = 1;
    LL sum = 1;
    for (int i = 2; i <= n; i++) {
        if (a[i] == 'A') {
            if (lst == 'B')
                ls++;
            else {
                sum *= dp[ls];
                ls = 1;
            }
        }
        if (a[i] == 'B') {
            if (lst == 'A')
                ls++;
            else {
                sum *= dp[ls];
                ls = 1;
            }
        }
        lst = a[i];
        sum %= MOD;
    }
    sum *= dp[ls];
    sum %= MOD;
    cout << sum << endl;
    return 0;
}

B.Improve Inversions(构造)

题意:

给你一个 ( 1 , 2 , ⋯   , N ) (1,2,\cdots,N) (1,2,,N)的排列组合 P = ( P 1 , P 2 , ⋯   , P N ) P=(P_1,P_2,\cdots,P_N) P=(P1,P2,,PN)。另外,你还得到一个整数 K K K

您可以执行下列操作零次或多次:

  • 选择整数 l l l r r r( 1 ≤ l < r ≤ N 1\leq l\lt r\leq N 1l<rN)。一对 ( l , r ) (l,r) (l,r)必须满足以下所有条件:
    • K ≤ r − l K\leq r-l Krl
    • P l > P r P_l\gt P_r Pl>Pr
    • 之前从未选择过一对 ( l , r ) (l,r) (l,r)
  • 然后,交换 P l P_l Pl P r P_r Pr的值。

你希望最大限度地增加操作次数。请找出一种实现方法。

分析:

先考虑当 k = 1 k=1 k=1时怎么处理,可以发现交换次数的上界就是排列的逆序对数,则考虑是否存在一种方案使得交换次数总能达到该上界。

考虑按照从小到大的顺序考虑每个数 x x x,设其在序列中初始所在的位置为 p o s x pos_x posx,则我们对于所有 j > p o s x j>pos_x j>posx p j < x p_j\lt x pj<x的数均可以进行一次交换。即将后面的比 x x x小的数按照从大到小的顺序依次与位置 p o s x pos_x posx上的数进行交换,可以发现这样的交换总能进行。

由于我们是按照从小到大的顺序进行这个操作的,因此在处理后面更大的数时比它小的数的相对位置可能会发生变化,但对答案没有影响。因此就得到了一种达到上界的构造方案,推广到 k > 1 k>1 k>1的情形也是类似的,模拟地构造即可。

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 505;
int n, k, a[N], pos[N];
vector<pair<int, int>> ans;

int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        pos[a[i]] = i;
    }
    for (int i = 1; i <= n; ++i) {
        for (int l = pos[i];;) {
            int best = -1, num = -1;
            for (int j = l + k; j <= n; ++j)
                if (a[j] < a[l] && a[j] > best)
                    best = a[j], num = j;
            if (best != -1) {
                ans.push_back(pair<int, int>(l, num)), swap(a[l], a[num]);
            } else
                break;
        }
    }
    cout << ans.size() << endl;
    for (auto [l, r]: ans)
        cout << l << " " << r << endl;
    return 0;
}

C.Subsequence and Prefix Sum(动态规划)

题意:

问题陈述

给你一个长度为 N N N的整数序列 A = ( A 1 , A 2 , ⋯   , A N ) A=(A_1,A_2,\cdots,A_N) A=(A1,A2,,AN)

你要准确地执行一次下面的操作:

  • 选择 A A A的一个非空子序列(不一定连续),并用它的累积和替换它。更精确地说,首先选择一个索引序列 ( i 1 , i 2 , ⋯   , i k ) (i_1,i_2,\cdots,i_k) (i1,i2,,ik),使得 1 ≤ i 1 < i 2 < ⋯ < i k ≤ N 1\leq i_1\lt i_2\lt\cdots\lt i_k\leq N 1i1<i2<<ikN,序列 k k k( 1 ≤ k ≤ N 1\leq k\leq N 1kN)的长度可以自由选择。然后,对于每个 j j j( 1 ≤ j ≤ k 1\leq j\leq k 1jk),用 ∑ 1 ≤ x ≤ j A i x \sum_{1\leq x\leq j}A_{i_x} 1xjAix替换 A i j A_{i_j} Aij的值。所有选定的索引同时进行替换。

求操作后的可能序列 A A A数量,答案对 1 0 9 + 7 10^9+7 109+7取模。

分析:

考虑 D P DP DP,定义 f i , j f_{i,j} fi,j表示处理了前 i i i个数,且此时 a i = j a_i=j ai=j的方案数。

根据操作的性质,当 j ≠ 0 j≠0 j=0时我们可以直接枚举下一个选中的位置 k k k,转移为 f k , a k + j ← f i , j f_{k,ak+j}←f_{i,j} fk,ak+jfi,j,此时 k k k位置上的数一定会发生变化,因此是一种不同的方案。

但当 j = 0 j=0 j=0时情况就有点特殊了,此时直接枚举到的下一个位置 k k k是不会变化的,因此不能记为一种方案。因此需要在 [ i + 1 , k − 1 ] [i+1,k−1] [i+1,k1]中找一个数 v ≠ 0 v≠0 v=0作为中转点将其贡献转给 a k a_k ak,即 f k , a k + v ← f i , j f_{k,a_k+v}←f_{i,j} fk,ak+vfi,j

要注意中间的这段数需要进行去重,因为选它们中值相同的位置不会改变序列本身的形态,是同质的操作。

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 105;
const int M = 100 * 20 + 5;
const int MOD = 1000000007;
int n, a[N], mn, mx, f[N][M], ans;

inline void inc(int &x, int y) {
    if ((x += y) >= MOD) x -= MOD;
}

int main() {
    int k;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        mn += min(a[i], 0);
        mx += max(a[i], 0);
    }
    f[0][0 - mn] = 1;
    for (int i = 0; i < n; ++i)
        for (int j = mn; j <= mx; ++j) {
            if (!f[i][j - mn])
                continue;
            if (j == 0) {
                set<int> s;
                s.insert(a[i + 1]);
                for (k = i + 2; k <= n; ++k) {
                    for (auto x: s) {
                        if (x)
                            inc(f[k][a[k] + x - mn], f[i][j - mn]);
                    }
                    s.insert(a[k]);
                }
            } else {
                for (k = i + 1; k <= n; ++k)
                    inc(f[k][a[k] + j - mn], f[i][j - mn]);
            }
        }
    for (int i = 0; i <= n; ++i)
        for (int j = mn; j <= mx; ++j)
            inc(ans, f[i][j - mn]);
    cout << ans << endl;
    return 0;
}

D.Division into 3(区间)

题意:

问题陈述

给你一个长度为 N N N的整数序列 A = ( A 1 , A 2 , ⋯   , A N ) A=(A_1,A_2,\cdots,A_N) A=(A1,A2,,AN)。请回答下列 Q Q Q个问题。

  • i i i次查询:给你整数 L i L_i Li R i R_i Ri。请为 B = ( A L i , A L i + 1 , ⋯   , A R i ) B=(A_{L_i},A_{L_i+1},\cdots,A_{R_i}) B=(ALi,ALi+1,,ARi)解答下面的问题。
    • B B B分成三个非空的连续子序列。对于每个连续子序列,让我们找出其元素的最大值。求这些最大值之和的最小值。在这里,问题的限制条件迫使 B B B的长度至少为 3 3 3,因此总有至少一种方法将其分成三个非空的连续子序列。

分析:

设最终 [ l , r ] [l,r] [l,r]分裂成了三个区间 [ l 1 , r 1 ] , [ l 2 , r 2 ] , [ l 3 , r 3 ] [l_1,r_1],[l_2,r_2],[l_3,r_3] [l1,r1],[l2,r2],[l3,r3],并且 [ l , r ] [l,r] [l,r]的最大值在 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]内,那么我们令 l 2 = l 1 + 1 , r 1 = l 1 , r 2 = r 3 − 1 , l 3 = r 3 l_2=l_1+1,r_1=l_1,r_2=r_3−1,l_3=r_3 l2=l1+1,r1=l1,r2=r31,l3=r3,一定不会更劣,所以第一种可能的情况就是区间长度 1 , k , 1 1,k,1 1,k,1,这个可以直接 R M Q RMQ RMQ解决。

然后若 [ l , r ] [l,r] [l,r]的最大值在 [ l 1 , r 1 ] [l_1,r_1] [l1,r1] [ l 3 , r 3 ] [l_3,r_3] [l3,r3]内,容易发现,后者与前者等价,只需要反转序列重做即可,所以下面考虑的是 [ l , r ] [l,r] [l,r]的最大值在 [ l 1 , r 1 ] [l_1,r_1] [l1,r1]中的情况。

这个时候,我们令 l 3 = l 2 + 1 l_3=l_2+1 l3=l2+1 r 2 = l 2 r_2=l_2 r2=l2,这样答案也一定不会更劣,于是 l 2 = r 2 l_2=r_2 l2=r2。考虑求出这个最大值所在的位置 p o s pos pos,那么 r = r 3 ≥ l 3 = l 2 − 1 ≥ p o s r=r_3\ge l_3=l_2−1\ge pos r=r3l3=l21pos

于是我们用一个指针维护 r r r,然后开一棵线段树存储每个 l 2 l_2 l2的答案,也即是 a n s l 2 = a l 2 + m a x l 2 < i ≤ r a i ans_{l_{2}}=a_{l_{2}}+max_{l_{2}\lt i\le r}a_i ansl2=al2+maxl2<irai,最后查询 m i n p o s < i < r a n s i min_{pos\lt i\lt r}ans_i minpos<i<ransi即可。

这一部分直接使用单调栈维护,然后用带区间修改的线段树辅助就可以了。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const int N = 300005;

struct node {
    LL l, r, id, lt, mx;
} p[N];

bool cmp(node a, node b) { return a.r < b.r; }

LL n, q, i, j, top, sta[N], a[N], ans[N], st[N][21], st2[N][21], st3[N][21], tr[N << 2];

inline LL solve(LL l, LL r) {
    LL c = log2(r - l + 1);
    return max(st[l][c], st[r - (1 << c) + 1][c]);
}

inline LL solve2(LL l, LL r) {
    LL c = log2(r - l + 1);
    if (st[l][c] >= st[r - (1 << c) + 1][c]) return st2[l][c];
    else return st2[r - (1 << c) + 1][c];
}

inline LL solve3(LL l, LL r) {
    LL c = log2(r - l + 1);
    return min(st3[l][c], st3[r - (1 << c) + 1][c]);
}

inline void build(LL s, LL t, LL p) {
    if (s == t) {
        tr[p] = 0x3f3f3f3f3f3f3f3f;
        return;
    }
    build(s, (s + t) / 2, 2 * p), build((s + t) / 2 + 1, t, 2 * p + 1);
    tr[p] = min(tr[2 * p], tr[2 * p + 1]);
}

inline void add(LL x, LL c, LL s, LL t, LL p) {
    if (s == t) {
        tr[p] = c;
        return;
    }
    if (x <= (s + t) / 2) add(x, c, s, (s + t) / 2, 2 * p);
    else add(x, c, (s + t) / 2 + 1, t, 2 * p + 1);
    tr[p] = min(tr[2 * p], tr[2 * p + 1]);
}

inline LL query(LL l, LL r, LL s, LL t, LL p) {
    if (l <= s && t <= r) return tr[p];
    if (l <= (s + t) / 2 && r > (s + t) / 2)
        return min(query(l, r, s, (s + t) / 2, 2 * p), query(l, r, (s + t) / 2 + 1, t, 2 * p + 1));
    else if (l <= (s + t) / 2) return query(l, r, s, (s + t) / 2, 2 * p);
    else return query(l, r, (s + t) / 2 + 1, t, 2 * p + 1);
}

inline void solve() {
    for (i = n; i >= 1; i--) {
        st[i][0] = a[i], st2[i][0] = i, st3[i][0] = a[i];
        for (j = 1; i + (1 << j) - 1 <= n; j++) {
            if (st[i][j - 1] >= st[i + (1 << j - 1)][j - 1]) st2[i][j] = st2[i][j - 1];
            else st2[i][j] = st2[i + (1 << j - 1)][j - 1];
            st[i][j] = max(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
            st3[i][j] = min(st3[i][j - 1], st3[i + (1 << j - 1)][j - 1]);
        }
    }
    for (i = 1; i <= q; i++) p[i].lt = solve2(p[i].l, p[i].r) + 1;
    sort(p + 1, p + q + 1, cmp);
    build(1, n, 1);
    for (i = 1, j = 1, top = 0; i <= q; i++) {
        while (j <= p[i].r) {
            while (top && a[sta[top]] <= a[j]) top--;
            sta[++top] = j;
            add(top, solve3(sta[top - 1], sta[top] - 1) + a[sta[top]], 1, n, 1);
            j++;
        }
        if (p[i].lt <= p[i].r) {
            LL pos = lower_bound(sta + 1, sta + top + 1, p[i].lt) - sta + 1;
            if (pos <= top) ans[p[i].id] = min(ans[p[i].id], query(pos, top, 1, n, 1) + p[i].mx);
            if (p[i].lt < sta[pos - 1])
                ans[p[i].id] = min(ans[p[i].id], solve3(p[i].lt, sta[pos - 1] - 1) + a[sta[pos - 1]] + p[i].mx);
        }
    }
}

int main() {
    cin >> n >> q;
    for (i = 1; i <= n; i++)
        cin >> a[i];
    for (i = n; i >= 1; i--) {
        st[i][0] = a[i];
        for (j = 1; i + (1 << j) - 1 <= n; j++)
            st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
    }
    for (i = 1; i <= q; i++) {
        cin >> p[i].l >> p[i].r;
        p[i].id = i;
    }
    for (i = 1; i <= q; i++) {
        ans[i] = solve(p[i].l + 1, p[i].r - 1) + a[p[i].l] + a[p[i].r];
        p[i].mx = solve(p[i].l, p[i].r);
    }
    solve();
    reverse(a + 1, a + n + 1);
    for (i = 1; i <= q; i++) {
        swap(p[i].l, p[i].r);
        p[i].l = n - p[i].l + 1;
        p[i].r = n - p[i].r + 1;
    }
    solve();
    for (i = 1; i <= q; i++)
        cout << ans[i] << endl;
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

  • 21
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值