把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

[ARC195D] Swap and Erase 分析

题目概述

给定一个数列 $ A = (A_1, \ldots, A_N) $。你可以对 $ A $ 进行以下两种操作,顺序和次数不限:

  1. 交换操作:设操作前 $ A $ 的长度为 $ K $。选择满足 $ 1 \leq i \leq K - 1 $ 的整数 $ i $,交换 $ A $ 的第 $ i $ 项和第 $ (i+1) $ 项。
  2. 删除操作:设操作前 $ A $ 的长度为 $ K $。选择满足 $ 1 \leq i \leq K $ 且 $ A $ 的前 $ i $ 项全部相等的整数 $ i $,删除 $ A $ 的前 $ i $ 项。

请求出将 $ A $ 变为空数列所需的最小总操作次数。

赛时思路

考虑了一个可以通过交换操作进行优化的序列(分 \(x\) 个连通块):

1 2 1 2

1 2 1 3 2 3

1 2 1 3 2 4 3 4

1 2 1 3 2 4 3 5 4 5

这个是很有启发的,但是赛时没有考虑更加普遍的情况,因此无了。

分析1:普通 \(dp\) 做法

这个可以加上区间加和区间覆盖变成一道动态 \(dp\) 的题目(也就是我们的模拟赛题目)。

我们一定是先交换完然后再一次性消掉嘛。

考虑什么时候交换是有用的。

比方说下面这种情况:

1 2 1 2

那么我们可以考虑中间的 \(1\)\(2\) 交换可以得到更优的答案 \(3\) 而不是直接消掉的 \(4\)

考虑到可能有些交换是无意义的假设有 1 2 2 1,那么将后面的那一个 \(1\) 交换到前面是没有意义的,因为交换就是为了让我们的操作二尽量更少而且少得要比操作一的次数多才对我们的答案有利。

我们如果一个数交换了两次,那么最多影响的是当前这个数和交换的两个数中的一个,对答案显然不会更优。

因此我们得出结论:每个数最多只是会被交换 \(1\)

考虑依据这个进行 \(dp\),于是设 \(f_{i,0/1}\) 表示考虑到第 \(i\) 个数,当前这个数与前面的数是换还是不换。

对于 \(f_{i,0}\) 有:

\[f_{i,0}=\min\{f_{i-1,1}+[a_i\ne a_{i-2}],f_{i-1,0}+[a_i\ne a_{i-1}]\} \]

对于 \(f_{i,1}\),我们从 \(i-2\) 转移过来,因为从 \(i-1\) 的话一旦考虑了顺序问题那么必定有一个数会被交换两次:

\[f_{i,1}=1+\min\{f_{i-2,0}+[a_i\ne a_{i-2}]+[a_i\ne a_{i-1}],f_{i-2,1}+[a_{i}\ne a_{i-3}]+[a_i\ne a_{i-1}]\} \]

代码1

时间复杂度 \(\mathcal{O}(n)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#define int long long
#define N 200005
using namespace std;
int T,n,a[N],f[N][2];
signed main(){
    cin >> T;
    for (;T--;) {
        scanf("%lld",&n);
        for (int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
        f[1][0] = 1,f[1][1] = 1e18;
        for (int i = 2;i <= n;i ++) {
            f[i][0] = min(f[i - 1][0] + (a[i] != a[i - 1]),f[i - 1][1] + (a[i] != a[i - 2]));
            if (i >= 2) f[i][1] = 1 + (a[i] != a[i - 1]) + min(f[i - 2][0] + (a[i] != a[i - 2]),f[i - 2][1] + (a[i] !=a[i - 3]));
        }
        printf("%lld\n",min(f[n][0],f[n][1]));
    }
    return 0;
}

分析2:贪心+模型构造

我们继续在块为 \(2\) 之后(也就是 \(3\))考虑。

1 2 1 3 2 3

我们可以交换第 \(2,3\) 个数然后交换第 \(4,5\) 的数可以得到更优的答案。

于是我们抽离模型:a b c d,并且钦定交换 b c 而且 \(a\ne b,b\ne c,c\ne d\),因为如果这里有一个条件不满足的话交换都是不优的。

考虑分类讨论一下:

  • \(a=c,b=d\),那么显然交换会使得我们的答案 \(-1\)
  • \(a=c\) 或者 \(b=d\),那么交换是不劣的。
  • \(a\ne c,b\ne d\),显然不会交换。

考虑操作 \(1\) 是更优的,直接全部搞操作 \(1\)?错,我赛时就是这样,无法获得好看的分数。

因为我们发现难道操作 \(2\) 真的一点用都没有吗?

考虑我的那个样例:

1 2 1 3 2 3

用操作 \(2\) 之后可以变成:

1 1 2 3 2 3

然后后面的那个可以直接用操作 \(1\) 使得答案少 \(1\)

当时也在考场上推出一个更大的例子(虽然推出来这个是少 \(1\),但是忽视了操作 \(2\) 的存在,就相当于是找规律找到的):

1 2 1 3 2 4 3 5 4 6 5 6

过程如下:

(1 1 2 3) 2 4 3 5 4 6 5 6
1 1 (2 2 3 4) 3 5 4 6 5 6
1 1 2 2 (3 3 4 5) 4 6 5 6
1 1 2 2 3 3 (4 4 5 6) 5 6
1 1 2 2 3 3 4 4 5 5 6 6

最后一步是直接使用操作1

也就是说我们贪心地先用操作 \(2\),因为这样可以创造出更多的操作 \(1\),而且记得跳过这些处理过的区间,因为这是不交的。

代码2

时间复杂度 \(\mathcal{O}(n)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdlib.h>
#include <vector>
#define int long long
#define N 200005
using namespace std;
int n,a[N];
signed main(){
    int T;
    cin >> T;
    for (;T--;) {
        scanf("%lld",&n);
        for (int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
        int ans = 0;
        for (int i = 1;i <= n;i ++) ans += (a[i] != a[i - 1]);
        a[n + 1] = a[n + 2] = a[n + 3] = 0;
        for (int i = 1;i <= n;i ++)
            if (a[i] == a[i + 2] && a[i] != a[i + 1]) {//1 2 1
                int j = i + 1;
                while(true) {
                    if (a[j] == a[j + 2]) break;
                    if (a[j] != a[j + 3]) break;
                    if (a[j + 1] == a[j + 2]) break;//赛时没有想到
                    j += 2;
                }
                if (a[j] == a[j + 2]) ans --,i = j + 1;
            }
        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2025-11-20 22:29  high_skyy  阅读(0)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end