[CF] F. Replace on Segment - Educational Codeforces Round 161 (Rated for Div. 2)

https://codeforces.com/contest/1922/problem/F

题目大意

给出 $n$ 长的数组和颜色数 $x$,满足 $(1 \le x \le n \le 100)$。数组的值 $a_i \ (1 \le a_i \le x)$ 代表每个位置的颜色。你可以执行以下操作若干次:

选取 $1 \le l \le r \le n$ 和 $1 \le k \le x$,满足 $\forall \ l \le i \le r, a[i] \neq k$。将 $[l, r]$ 这一段所有颜色变为 $k$。

问最少多少次操作可以使整个区间 $[1, n]$ 变为相同颜色。

简要题解

引理:如果某一段 $[l, r]$ 被涂过了,则我们可以将其当做一个整体。

证明:

假设 $f(A)$ 表示 $A$ 的整段同色的最少操作次数。$A'$ 表示将 $[l, r]$ 的段缩成段首元素一个之后的数组。

可以证明 $f(A) \ge f(A')$。考虑我们总可以把对于 $A$ 相同的操作应用在 $A'$,得到同色数组。当且仅当 $A$ 的操作 $[l_i, r_i]$ 只与 $[l + 1, r]$ 的元素有关时舍去。 当 $l < l_i \le r$ 时我们将 $l_i$ 调整到 $r + 1$。

可以证明 $f(A) \le f(A')$。考虑对于 $A'$ 的最优操作,我们总可以把对于 $l$ 一个点的操作变为对于 $[l, r]$ 在 $A$ 上一段的操作。因此 $A$ 的最优操作不会更差。

从而我们可以得出 $f(A) = f(A')$。

有了这个基础之后,我们就可以对原问题进行区间 $dp$ 的处理了。

$dp[l][r][c]$ 代表 $[l, r]$ 区间全部涂成颜色 $c$ 的代价。但是我们注意到这是不够的,因为处理完小区间并不总是需要将其涂成同一种颜色,只要其不包含颜色 $c$ 则我们依然可以通过一次操作将其变为全 $c$ 的区间。 因此增加 $dpn[l][r][c]$ 代表 $[l, r]$ 区间全部都不是颜色 $c$ 的最小代价。

我们按照区间长度转移。显然这两组都可以从更短区间转移来,枚举中点合并即可。接下来考虑最后要将区间 $[l, r]$ 刷成某种颜色转移。

$$ \begin{align} dp[i][j][c] & \gets dpn[i][j][c] + 1, \\ dpn[i][j][c] & \gets dp[i][j][d], \quad d \neq c \\ \end{align} $$

我们发现这里转移成环了。

dp[i][j][c] <- dpn[i][j][c] <- dp[i][j][d] <- dpn[i][j][d] <- dp[i][j][c]

这不是先有鸡先有蛋了吗?

但是,我们注意到一个区间最多只需要涂色两次。涂色三次,要么相当于涂色一次($121$ 涂回最初的颜色),要么中间的一次是没意义的($123$ 相当于 $12$)。于是我们将从合并小区间的结果再用 $[l, r]$ 其他情况来的转移跑两遍就好了。

复杂度

$T$:$O(n ^ 3 \cdot x)$

$S$:$O(n ^ 2 \cdot x)$

代码实现

#include <bits/stdc++.h>
using namespace std;

int io_=[](){ ios::sync_with_stdio(false); cin.tie(nullptr); return 0; }();

using LL = long long;
using ULL = unsigned long long;
using LD = long double;
using PII = pair<int, int>;
using VI = vector<int>;
using MII = map<int, int>;

template<typename T> void cmin(T &x,const T &y) { if(y<x) x=y; }
template<typename T> void cmax(T &x,const T &y) { if(x<y) x=y; }
template<typename T> bool ckmin(T &x,const T &y) { 
    return y<x ? (x=y, true) : false; }
template<typename T> bool ckmax(T &x,const T &y) { 
    return x<y ? (x=y, true) : false; }
template<typename T> void cmin(T &x,T &y,const T &z) {// x<=y<=z 
    if(z<x) { y=x; x=z; } else if(z<y) y=z; }
template<typename T> void cmax(T &x,T &y,const T &z) {// x>=y>=z
    if(x<z) { y=x; x=z; } else if(y<z) y=z; }

// mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
// mt19937_64 rnd_64(chrono::system_clock::now().time_since_epoch().count());

/*
---------1---------2---------3---------4---------5---------6---------7---------
1234567890123456789012345678901234567890123456789012345678901234567890123456789
*/

const int M = 105;

int dp[M][M][M], dpn[M][M][M];

void solve() {
  int n, x; cin >> n >> x;
  vector<int> a(n);
  for (int& i : a) {
    cin >> i; i--;
  }

  if (x == 1) {
    cout << "0\n";
    return;
  }

  for (int i = 0; i < n; i++) {
    for (int j = i; j < n; j++) {
      for (int k = 0; k < x; k++) {
        dp[i][j][k] = n;
        dpn[i][j][k] = n;
      }
    }
  }

  for (int i = 0; i < n; i++) {
    for (int c = 0; c < x; c++) {
      dp[i][i][c] = (a[i] != c);
      dpn[i][i][c] = (a[i] == c);
    }
  }

  for (int len = 2; len <= n; len++) {
    for (int l = 0, r = len - 1; r < n; l++, r++) {
      for (int c = 0; c < x; c++) {
        for (int i = l; i < r; i++) {
          cmin(dp[l][r][c], dp[l][i][c] + dp[i + 1][r][c]);
        }

        for (int i = l; i < r; i++) {
          cmin(dpn[l][r][c], dpn[l][i][c] + dpn[i + 1][r][c]);
        }
      }

      for (int i = 0; i < 2; i++) {
        int mndp1 = n, mndp2 = n, mndpn = n;
        for (int c = 0; c < x; c++) {
          cmin(mndp1, mndp2, dp[l][r][c]);
          cmin(mndpn, dpn[l][r][c]);
        }
        for (int c = 0; c < x; c++) {
          cmin(dpn[l][r][c], mndp1 == dp[l][r][c] ? mndp2 : mndp1);
          cmin(dp[l][r][c], min(mndp1, mndpn) + 1);
        }
      }

      // for (int c = 0; c < x; c++) {
      //   cerr << l << ' ' << r << ' ' << c << ' ' << dp[l][r][c] << ' ' << dpn[l][r][c] << endl;
      // }
    }
  }

  int ans = n;
  for (int i = 0; i < x; i++) {
    cmin(ans, dp[0][n - 1][i]);
  }
  cout << ans << '\n';
}

int main() {
  int t = 1; 
  cin >> t;
  while (t--) {
    solve();
  }
  return 0;
}

标答做法

更模式化的解决环的办法大概是,直接找到其中某些值的最优解!

对于 $dp[l][r][c]$ 如果 $a[l] = c$ 则 $dp[l][r][c]$ 与 $dp[l + 1][r][c]$ 的结果应该相同。

证明:

考虑操作序列 $op$。如果 $op_i$ 操作的区间不包含 $l$ 则无所谓。如果为 $[l, x]$ 刷成 $c$ 则显然它满足操作时 $a[l, x] \neq c$,同理将这个操作变为 $[l + 1, x]$ 则 $a[l + 1, x] \neq c$ 还成立。因此 $dp[l][r][c] \ge dp[l + 1][r][c]$。

同理因为有转移 $dp[l][r][c] = dp[l][l][c] + dp[l + 1][r][c]$ 其中 $dp[l][l][c] = 0$ 则 $dp[l][r][c] \le dp[l + 1][r][c]$。得出 $dp[l][r][c] = dp[l + 1][r][c]$。

同理,不太显然的,对于 $dpn[l][r][c]$ 如果 $a[l] \neq c$ 则 $dpn[l][r][c]$ 与 $dpn[l + 1][r][c]$ 也是相同的,但其实这里证明是和 $dp$ 的完全一致的。

右边界的讨论是同样的。

如此我们说如果区间 $[l, r]$ 的端点 $a[l] = c, a[r] = d$:

  1. $c = d$。这种情况,我们可以直接得出所有的 $dpn[l][r][e] \ (e \neq c)$ 和 $dp[l][r][c]$
color   dp   dpn
  0           v
  1           v
  .           v
  c     v     
  .           v

进一步由 $dp[l][r][e] \gets dpn[l][r][e] + 1$ 的转移得到所有 $dp[l][r][e]$ 的值

color   dp   dpn
  0     v     v
  1     v     v
  .     v     v
  c     v     
  .     v     v

最后由所有 $dp[l][r][e]$ 的值推导出 $dpn[l][r][c]$ 的值。

color   dp   dpn
  0     v     v
  1     v     v
  .     v     v
  c     v     v
  .     v     v
  1. $c \neq d$。 不妨设 $c < d$。此时分别靠缩短左右边界即可得到所有的 $dpn[l][r][e]$ 的值。
color   dp   dpn
  0           v
  1           v
  .           v
  c     v     v
  .           v
  d     v     v
  .           v

进一步由 $dp[l][r][e] \gets dpn[l][r][e] + 1$ 的转移得到所有 $dp[l][r][e]$ 的值

color   dp   dpn
  0     v     v
  1     v     v
  .     v     v
  c     v     v
  .     v     v
  d     v     v
  .     v     v

复杂度是一样的,代码如下

#include <bits/stdc++.h>
using namespace std;

int io_=[](){ ios::sync_with_stdio(false); cin.tie(nullptr); return 0; }();

using LL = long long;
using ULL = unsigned long long;
using LD = long double;
using PII = pair<int, int>;
using VI = vector<int>;
using MII = map<int, int>;

template<typename T> void cmin(T &x,const T &y) { if(y<x) x=y; }
template<typename T> void cmax(T &x,const T &y) { if(x<y) x=y; }
template<typename T> bool ckmin(T &x,const T &y) { 
    return y<x ? (x=y, true) : false; }
template<typename T> bool ckmax(T &x,const T &y) { 
    return x<y ? (x=y, true) : false; }
template<typename T> void cmin(T &x,T &y,const T &z) {// x<=y<=z 
    if(z<x) { y=x; x=z; } else if(z<y) y=z; }
template<typename T> void cmax(T &x,T &y,const T &z) {// x>=y>=z
    if(x<z) { y=x; x=z; } else if(y<z) y=z; }

// mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
// mt19937_64 rnd_64(chrono::system_clock::now().time_since_epoch().count());

/*
---------1---------2---------3---------4---------5---------6---------7---------
1234567890123456789012345678901234567890123456789012345678901234567890123456789
*/

const int M = 105;

int dp[M][M][M], dpn[M][M][M];

void solve() {
  int n, x; cin >> n >> x;
  vector<int> a(n);
  for (int& i : a) {
    cin >> i; i--;
  }

  if (x == 1) {
    cout << "0\n";
    return;
  }

  for (int i = 0; i < n; i++) {
    for (int j = i; j < n; j++) {
      for (int k = 0; k < x; k++) {
        dp[i][j][k] = n;
        dpn[i][j][k] = n;
      }
    }
  }

  for (int i = 0; i < n; i++) {
    for (int c = 0; c < x; c++) {
      dp[i][i][c] = (a[i] != c);
      dpn[i][i][c] = (a[i] == c);
    }
  }

  for (int len = 2; len <= n; len++) {
    for (int l = 0, r = len - 1; r < n; l++, r++) {
      // don't paint [l, r]
      for (int c = 0; c < x; c++) {
        for (int i = l; i < r; i++) {
          cmin(dp[l][r][c], dp[l][i][c] + dp[i + 1][r][c]);
          cmin(dpn[l][r][c], dpn[l][i][c] + dpn[i + 1][r][c]);
        }
      }

      // paint [l, r]
      for (int c = 0; c < x; c++) {
        if (c != a[l]) {
          cmin(dpn[l][r][c], dpn[l + 1][r][c]);
        }
        if (c != a[r]) {
          cmin(dpn[l][r][c], dpn[l][r - 1][c]);
        }

        if (c != a[l] || c != a[r]) {
          cmin(dp[l][r][c], dpn[l][r][c] + 1);
        }
      }
      if (a[l] == a[r]) {
        int c = a[l];
        cmin(dp[l][r][c], dp[l + 1][r][c]);

        for (int i = 0; i < x; i++) {
          if (i == c) continue;
          cmin(dpn[l][r][c], dp[l][r][i]);
        }
      }
    }
  }

  int ans = n;
  for (int i = 0; i < x; i++) {
    cmin(ans, dp[0][n - 1][i]);
  }
  cout << ans << '\n';
}

int main() {
  int t = 1; 
  cin >> t;
  while (t--) {
    solve();
  }
  return 0;
}

还有神仙提交

用了 bitset 加速,看起来是没有保存所有的转移,但没充分看懂这里记录一下

https://codeforces.com/contest/1922/submission/242351751

#include<bits/stdc++.h>
#pragma GCC optimize("03")

using namespace std;

#define ll long long
#define fastio() ios_base::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL)

#define all(a) a.begin(),a.end()
#define endl "\n"
#define sp " " 
#define pb push_back
#define mp make_pair
#define vecvec(type, name, n, m, value) vector<vector<type>> name(n + 1, vector<type> (m + 1, value))

void __print(int x) {cerr << x;}
void __print(long x) {cerr << x;}
void __print(long long x) {cerr << x;}
void __print(unsigned x) {cerr << x;}
void __print(unsigned long x) {cerr << x;}
void __print(unsigned long long x) {cerr << x;}
void __print(float x) {cerr << x;}
void __print(double x) {cerr << x;}
void __print(long double x) {cerr << x;}
void __print(char x) {cerr << '\'' << x << '\'';}
void __print(const char *x) {cerr << '\"' << x << '\"';}
void __print(const string &x) {cerr << '\"' << x << '\"';}
void __print(bool x) {cerr << (x ? "true" : "false");}

template<typename T, typename V>
void __print(const pair<T, V> &x) {cerr << '{'; __print(x.first); cerr << ','; __print(x.second); cerr << '}';}
template<typename T>
void __print(const T &x) {int f = 0; cerr << '{'; for (auto &i: x) cerr << (f++ ? "," : ""), __print(i); cerr << "}";}
void _print() {cerr << "]\n";}
template <typename T, typename... V>
void _print(T t, V... v) {__print(t); if (sizeof...(v)) cerr << ", "; _print(v...);}
#ifndef ONLINE_JUDGE
#define debug(x...) {cerr << "[" << #x << "] = ["; _print(x);}
#define reach cerr << "reached" << endl
#else
#define debug(x...)
#define reach 
#endif

const int MOD = 1e9+7;
const int64_t inf = 0x3f3f3f3f, INF = 1e18, BIG_MOD = 489133282872437279;
/*--------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/

// #define int int64_t

const int N = 120;

int n, x;
int a[N];
int dp[2][N][N];        // 0 => equal, 1 => exclude
bitset<N> opt[2][N][N];

int32_t main()
{
    fastio();
    int t;
    cin >> t;
    while(t --)
    {
        cin >> n >> x;
        for(int i = 1; i <= n; i ++)    cin >> a[i];

        for(int l = 0; l <= n + 5; l ++)
            for(int r = l; r <= n + 5; r ++)
                for(int t = 0; t < 2; t ++)
                {
                    opt[t][l][r].reset();
                    dp[t][l][r] = inf;
                }        
        
        bitset<N> all;
        for(int i = 1; i <= x; i ++)    all.flip(i);

        for(int i = 1; i <= n; i ++)
        {
            dp[0][i][i] = 0;
            opt[0][i][i].flip(a[i]);

            dp[1][i][i] = 0;
            opt[1][i][i] = all;
            opt[1][i][i].flip(a[i]);
        }

        for(int len = 2; len <= n; len ++)
        {
            for(int l = 1, r = len; r <= n; l ++, r ++)
            {
                for(int m = l; m < r; m ++)     //combine dp[l][m] and dp[m + 1][r]     
                {
                    //dp[0][l][r]
                    {
                        for(int i = 0; i <= 1; i ++)
                        {
                            for(int j = 0; j <= 1; j ++)
                            {
                                auto comb = (opt[i][l][m] & opt[j][m + 1][r]);

                                if(comb.count())
                                {
                                    int cost = dp[i][l][m] + dp[j][m + 1][r] + i + j;
                                    if(cost < dp[0][l][r])
                                    {
                                        dp[0][l][r] = cost;
                                        opt[0][l][r] = comb;
                                    }
                                    else if(cost == dp[0][l][r])
                                        opt[0][l][r] |= comb;
                                }
                            }
                        }
                    }

                    // dp[1][l][r]
                    {
                        auto comb = (opt[1][l][m] & opt[1][m + 1][r]);

                        if(comb.count() == 0)
                        {
                            int cost = dp[1][l][m] + dp[1][m + 1][r] + 1; 
                            if(cost < dp[1][l][r])
                            {
                                dp[1][l][r] = cost;
                                opt[1][l][r] = (opt[1][l][m] | opt[1][m + 1][r]);
                            }
                            else if(cost == dp[1][l][r])
                                opt[1][l][r] |= (opt[1][l][m] | opt[1][m + 1][r]);
                        }
                        else
                        {
                            int cost = dp[1][l][m] + dp[1][m + 1][r]; 
                            if(cost < dp[1][l][r])
                            {
                                dp[1][l][r] = cost;
                                opt[1][l][r] = comb;
                            }
                            else if(cost == dp[1][l][r])
                                opt[1][l][r] |= comb;
                        }
                    }
                }
            }
        }
        cout << min(dp[0][1][n], dp[1][1][n] + 1) << endl;
    }
}
Prev: [CF] E. Increasing Subsequences - Educational Codeforces Round 161 (Rated for Div. 2)
Next: [CF] B. Farmer John's Card Game - Codeforces Round 998 (Div. 3)