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}\)