Loading

CW 11.02 模拟赛 FSYo T2

题面

自出题
挂个 pdf
题面下载

算法

看到交换, 这里有一个套路:
确定最终的形态后, 交换次数即为逆序对个数
我们直接设 \(f_{i, j, k, 0 / 1 / 2}\) 表示 \(3\) 种颜色填到哪里了,最后一个是什么颜色,逆序对数最少是多少

转移分最后一个是什么颜色讨论

关于 \(O(1)\) 求逆序对的方法:

    if(i==0 && a) f[a][b][c][0] = min(f[a][b][c][0], min(f[a-1][b][c][1], f[a-1][b][c][2])+max(0, b-pre[0][a].first)+max(0, c-pre[0][a].second));
    if(i==1 && b) f[a][b][c][1] = min(f[a][b][c][1], min(f[a][b-1][c][0], f[a][b-1][c][2])+max(0, a-pre[1][b].first)+max(0, c-pre[1][b].second));
    if(i==2 && c) f[a][b][c][2] = min(f[a][b][c][2], min(f[a][b][c-1][0], f[a][b][c-1][1])+max(0, a-pre[2][c].first)+max(0, b-pre[2][c].second));

\(O(n ^ 3)\)


考虑复习

看到题目容易想到直接计算可能的方案, 这个是简单的, 转移方程和上面的是一样的
考虑逆序对怎么计算

动态维护逆序对, 你发现填上第 \(i\) 个位置, 这个位置会和前面的一些位置产生逆序对, 具体怎么计算个数?


你发现对于同一种颜色, 一定不用改变其顺序, 我们需要改变的是不同颜色之间的顺序
基于此, 将原序列编号, 然后按照颜色顺序对于现在的序列标号, 交换次数即为标号序列的逆序对数

具体的, 对于

  • 原序列 : RRGYY
  • 现序列 : RGYRY
  • 标号序列 : 1 4 2 3 5

本质上只有同颜色的相对位置不变, 这算是典型的逆序对求法


考虑计算

你发现新加入一个字母, 可以知道其在标号序列中的下标与值, 计算逆序对个数只需要看标号序列中, 前面有多少个数大于它即可
具体怎么做?

你考虑「标号大于它」的实际意义, 即存在多少个在原序列中在它之后的现在在他之前
你可以预处理每个地方应该有那个颜色在他之前, 然后计算的时候只需要看是否匹配即可

代码

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 202;
const int N = 505;
const int INF = 1e9;

int n, f[maxn][maxn][maxn][3], pos[3];
pair<int, int> pre[3][maxn];
char s[402];

inline ll read()
{
    ll x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

int main()
{
    freopen("s.in", "r", stdin);
    freopen("s.out", "w", stdout);

    n = read();
    scanf("%s", s + 1);
    for (int i = 1; i <= n; i++)
    {
        if (s[i] == 'R')
            pos[0]++, pre[0][pos[0]] = make_pair(pos[1], pos[2]);
        else if (s[i] == 'G')
            pos[1]++, pre[1][pos[1]] = make_pair(pos[0], pos[2]);
        else
            pos[2]++, pre[2][pos[2]] = make_pair(pos[0], pos[1]);
    }
    memset(f, 0x3f, sizeof(f));
    if (pos[0])
        f[1][0][0][0] = 0;
    if (pos[1])
        f[0][1][0][1] = 0;
    if (pos[2])
        f[0][0][1][2] = 0;
    for (int a = 0; a <= pos[0]; a++)
    {
        for (int b = 0; b <= pos[1]; b++)
        {
            for (int c = 0; c <= pos[2]; c++)
            {
                for (int i = 0; i <= 2; i++)
                {
                    if (i == 0 && a)
                        f[a][b][c][0] = min(f[a][b][c][0], min(f[a - 1][b][c][1], f[a - 1][b][c][2]) + max(0, b - pre[0][a].first) + max(0, c - pre[0][a].second));
                    if (i == 1 && b)
                        f[a][b][c][1] = min(f[a][b][c][1], min(f[a][b - 1][c][0], f[a][b - 1][c][2]) + max(0, a - pre[1][b].first) + max(0, c - pre[1][b].second));
                    if (i == 2 && c)
                        f[a][b][c][2] = min(f[a][b][c][2], min(f[a][b][c - 1][0], f[a][b][c - 1][1]) + max(0, a - pre[2][c].first) + max(0, b - pre[2][c].second));
                }
            }
        }
    }
    printf("%d\n", min(f[pos[0]][pos[1]][pos[2]][0], min(f[pos[0]][pos[1]][pos[2]][1], f[pos[0]][pos[1]][pos[2]][2])));

    return 0;
}

总结

算套路吧(?)

逆序对求法值得学习, 类似于前缀和


题目中的常数一般用于 \(\rm{dp}\)

posted @ 2024-11-02 16:20  Yorg  阅读(14)  评论(0编辑  收藏  举报