Codeforces Round #821 (Div. 2) 题解

A.Consecutive Sum

statement: 给定一个长度为$n$的数组,你可以任意交换两个距离为$k$的数,求交换后连续$k$个数组元素之和的最大值 Solution: 直接分成$k$组,然后取每组的最大值就是答案! Code:
int a[N];
void solve() {
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= n; ++i) {
        int x;
        cin >> x;
        a[i % k] = max(a[i % k], x);
    }
    ll ans = 0;
    for (int i = 0; i < k; ++i) {
        ans += a[i];
        a[i] = 0;
    }
    cout << ans << endl;
}

B.Rule of League

statement: 有$n$个人进行比赛,比赛持续$n-1$次,第一次$1$和$2$战斗,第二次$2和3$战斗,以此类推...最后一次$n-1和n$战斗。给定两个数$x,y$,表示每个人赢的场数为$x,y$。需要我们构造出每一场获胜的人并输出,若无解输出$-1$ Solution: 首先$x,y$一定是一个整数和一个$0$,反之如果所有人都赢过,但是$n$个人只有$n-1$场比赛,显然矛盾,此时无解输出$-1$,。因此所有赢的人都赢了$y$场,输的人都直接输了。如果有解的话$n-1 % y == 0$,我们只需要第一次让$1$赢,$1$赢$y$次,下一个人继续赢即可 Code:
int a[N];
void solve() {
    int n, x, y, xx, yy;
    cin >> n >> xx >> yy;
    x = min(xx, yy), y = max(xx, yy);
    if (x != 0 || y == 0 || (n - 1) % y != 0) {
        cout << -1 << endl;
        return;
    }
    int cur = -1, last = 1;
    bool ok = 0;
    for (int i = 0; i < n - 1; ++i) {
        if (i == 0) {
            cout << last << " ";
            continue;
        }
        if ((i % y == 0) && ok) {
            last += y;
        } else if ((i % y == 0) && !ok) {
            last += y + 1;
            ok = 1;
        }
        cout << last << " ";
    }
    cout << endl;
}

C.Parity Shuffle Sorting

statement: 给定一个数组,每次操作可以选定两个点$l, r$,如果$a[l] + a[r]$是奇数,那么$a[r] = a[l]$,反之$a[l] = a[r]$,操作后需要构造出一个不下降的序列,输出任意一组解使得操作次数不超过$n$。 Solution: 看到不下降序列,可以想到极端的操作可以使该序列的元素全部都相等,然后操作次数不超过$n$次也很有提示性,我们可以看到当$a[l] + a[r]$是奇数,操作是将左边的值赋给右边,反之是将右边的值赋给左边,这是我可以想到先把首尾的值操作成相等的,然后遍历整个数组,如果$a[i]+a[1]$是偶数,选择$[1,i]$,把首位的值给$i$位置,反之则选择$[i,n]$,把末尾的值给i,这样最后整个序列的值一定是可以全部相等的,按照想法模拟即可! Code:
ll a[N];
void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    if (n == 1) {
        cout << 0 << endl;
        return;
    }
    cout << n - 1 << endl;
    cout << 1 << " " << n << endl;
    if (((a[1] + a[n]) & 1) == 0) {
        a[1] = a[n];
    }
    for (int i = 2; i < n; ++i) {
        if ((a[1] + a[i]) & 1) {
            cout << 1 << " " << i << endl;
        } else {
            cout << i << " " << n << endl;
        }
    }
}

D1.Zero-One (Easy Version)

statement: 给定两个$01$字符串,要求经过操作使得两串相等。每次操作可以选择任意两个位置不相等的位置翻转,如果两个位置距离为$1$,那么翻转的代价为$x$,距离大于$1$则代价为$y$,求最小的花费代价。 Solution: 由于题目里给出限制条件${x<=y}$,那么问题就简单了起来,一个很简单的贪心,我们去标记每一个两串不同的位置,如果不同的位置是奇数个,显然不论如何操作都是无法让两串相同的,此时直接输出$-1$即可,如果不同的位置超过两个的话,肯定是选择花费$y$代价是最贪心 ,有一个地方需要特判,当位置不同的个数只有两个的时候,由于$y$操作的距离限制,必须选择不同的位置,分两次进行反转,代价是$2*y$,而$x$操作只需要操作一次,代价是$x$,这个特例需要做出min判断,代码如下: Code:
void solve() {
    string s1, s2;
    ll n, x, y;
    cin >> n >> x >> y;
    cin >> s1 >> s2;
    vector v;
    for (int i = 0; i < n; ++i) {
        if (s1[i] != s2[i]) {
            v.push_back(i);
        }
    }
    if (v.size() & 1) {
        cout << -1 << endl;
        return;
    }
    if ((v.size() == 2) && (v[0] == (v[1] - 1))) {
        cout << min(x, 2 * y) << endl;
        return;
    }
    cout << y * (v.size() / 2) << endl;
}

D2.Zero-One (Hard Version)

statement:Solution: 与 D1 问题不同的是,没有限制$ x\le y$ .

因为串的长度只\(5000\),可以考虑区间DP

定义$ dp[i][j]$表示 \(s1[i,j]\) 变成 \(s2[i,j]\) 的最小花费

\(cnt\)表示两串不相同的位置的个数
\(gao(a,b)\)用于计算选取了\(a,b\)两个位置进行操作的代价
\({dp[i][j]=min( dp[i][j-2]+gao(j-1,j),dp(i+1,j-1)+gao(i,j),dp(i+2,j)+gao(i,i+1),cnt/2*y)}\)

Code:
ll dp[5010][5010];
vector v;
ll n,x,y;
string s1,s2;
ll gao(ll a,ll b) {
    int dis = abs(v[a] - v[b]);
    if (dis == 1) {
        return min(x, y * 2);
    } else {
        return min(dis * x, y);
    }
}
void solve() {
    // memset(dp,inf,sizeof dp);
    int cnt = 0;
    cin >> n >> x >> y;
    cin >> s1 >> s2;
    v.clear();
    for (int i = 0; i < n; ++i) {
        if (s1[i] != s2[i]) {
            v.push_back(i);
            cnt++;
        }
    }
    if (cnt & 1) {
        cout << -1 << endl;
        return;
    }
    for (int k = 2; k <= cnt; k += 2) {
        for (int l = 0; l + k - 1 < cnt; ++l) {
            int r = l + k - 1;
            if (k == 2) {
                dp[l][r] = gao(l, r);
            } else {
                dp[l][r] = min(
                        min(dp[l + 1][r - 1] + gao(l, r), dp[l + 2][r] + gao(l, l + 1)),
                        min(dp[l][r - 2] + gao(r - 1, r), ll(cnt / 2) * y));
            }
        }
    }
    cout << dp[0][cnt - 1] << endl;
}

E. Conveyor

statement: 给定一个$n*m$的棋盘,每个位子都有一个传送带,初始状态所有的传送带都是向右的,每单位时间在$[0, 0]$位置放入一个小球,小球每一个时刻都会往当前方向移动一格。在移动后,原本向右的方向变成向下,向下的格子变成向右。给定若干个询问,求$t$时刻$[x, y]$点是否有球。 Solution: 设$f[i][j]$为截止$t$时刻一共有多少球经过$[i, j]$这个点,经过模拟此规则下小球的运动规律, 发现一个球想要走到$[x, y]$至少需要$x + y - 1$的时间,只有大于该时间的球才有可能出现在$[x, y]$的球。所以初始化f[0][0]要减去这部分时间,因此可能能到的球的数量是$t - x - y + 1$,一个点$[i, j]$会传入$(f[i][j] +1)/2$的球到右边,$f[i][j] / 2$的球到左边。我们可以$n^2$递推出$t$时刻每个点经过了多少个球,如何判断$t$时刻有没有球,那无非是比较$t和t-1$时刻有没有新增就好了! Code:
ll t,x,y;
ll f[150][150];
int calc(ll t,ll x, ll y) {
    memset(f, 0, sizeof f);
    f[0][0] = max(0ll,t - x - y + 1) ;
    for (int i = 0; i <= 130; ++i) {
        for (int j = 0; j <=130;++j) {
            f[i][j + 1] += (f[i][j] + 1) / 2;
            f[i + 1][j] += f[i][j] / 2;
        }
    }
    // dbg(f[x][y]);
    return f[x][y];
}
void solve() {
    cin >> t >> x >> y;
    if (t == 0 && x == 0 && y == 0) {
        cout << "YES" << endl;
        return;
    }
    if (t == 0) {
        cout << "NO" << endl;
        return;
    }
    if (x + y > t) {
        cout << "NO" << endl;
        return;
    }
    if (calc(t, x, y) <= calc(t - 1, x, y)) {
        cout << "NO" << endl;
    } else {
        cout << "YES" << endl;
    }
}
posted @ 2022-10-08 21:25  tententenYep  阅读(59)  评论(0)    收藏  举报