[CF] E. Swapping Characters - Educational Codeforces Round 34 (Rated for Div. 2)

https://codeforces.com/contest/903/problem/E

题目大意

有某未知长为 $n$ 的串 $s$ 和 $k$ 个由串 $s$ 通过交换两个不同下标字符构成的串 $s_i$。其中 $k \le 2500, nk \le 5000$。输入未必合法。问是否有这样的串 $s$,如果没有输出 $-1$,否则给出任意合法的串。

简要题解

观察:

  1. 两个串 $s_i$ 和 $s_j$ 之间最多只会有 $4$ 个位置不一样,否则原串一定不存在。(因为一次交换可以最多让两个位置不同)。
  2. 两个串如果只有 $1$ 个位置不同,则说明字符频率不同,这无法通过 $s$ 交换得到。
  3. 两个串如果没有不同,则可能是原串的某个相同交换。
  4. $3$ 个或者 $4$ 个不同是可能的。
  5. 某串只可能与原串有 $2$ 个不同,或者当原串有重复字符时,与原串有 $0$ 个不同。

于是我们找出和 $s_0$ 不同最多的串 $s_i$。

  1. 如果最多不同个数是 $0$ 则我们直接任意交换 $s_0$ 的两个元素就可以作为原串。
  2. 对于 $2, 3, 4$ 的情况,我们直接枚举 $s_0$ 和 $s_i$ 不同位置的字母的所有交换后的可能。如果有解则一定可以找出,否则无解。

这里证明一下为什么这样是对的。

$4$ 的情况是最显然的,因为 $4$ 个不同意味着这里必然包含了两次交换的所有位置,则其他位置的元素都是定死的。则枚举不同位置的字母的排列显然可以遍历所有可能情况。

$3$ 的情况是类似的,两次交换一定覆盖了同一个公共位置和两个不同的位置。所以其他位置也都是定死的。

$2$ 不太显然。能得到 $2$ 只有以下两种可能。

   aba 
12 baa
23 aab

   abcc
12 bacc
34 abcc

注意到这两种可能中都需要有重复字母存在,而所有的串和 $s_0$ 的差别只有 $2$,因此我们总可以将 $s_0$ 作为 $s$。

复杂度

$T$:$O(nk)$ 这里有个较大常数 $4!$

$S$:$O(nk)$

代码实现

#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
*/

void solve() {
  int k, n; cin >> k >> n;
  vector<string> s(k);
  for (int i = 0; i < k; i++) {
    cin >> s[i];
  }

  int p = 0, mx = 0;
  for (int i = 1; i < k; i++) {
    int diff = 0;
    for (int j = 0; j < n; j++) {
      if (s[0][j] != s[i][j]) diff++;
    }
    if (diff == 1 || diff > 4) {
      cout << "-1\n";
      return;
    }

    if (diff > mx) {
      mx = diff;
      p = i;
    }
  }

  if (mx == 0) {
    swap(s[0][0], s[0][1]);
    cout << s[0] << '\n';
    return;
  }

  bool has_same = false;
  map<char, int> cnt;
  for (int i = 0; i < n; i++) {
    cnt[s[0][i]]++;
  }
  for (auto [v, c] : cnt) {
    if (c >= 2) has_same = true;
  }

  vector<char> chs;
  vector<int> pos;
  for (int i = 0; i < n; i++) {
    if (s[0][i] != s[p][i]) {
      chs.push_back(s[0][i]);
      pos.push_back(i);
    }
  }

  vector<int> ord(mx);
  iota(ord.begin(), ord.end(), 0);

  string ans = s[0];

  vector<int> dpos;

  do {
    for (int i = 0; i < mx; i++) {
      ans[pos[ord[i]]] = chs[i];
    }

    bool f = 1;

    for (int i = 0; i < k; i++) {
      dpos.clear();
      for (int j = 0; j < n; j++) {
        if (ans[j] != s[i][j]) {
          dpos.push_back(j);
        }
      }

      int sz = dpos.size();
      if (sz > 2 || sz == 1) {
        f = 0;
        break;
      }
      if (sz == 0) {
        if (has_same) {
          continue;
        } else {
          f = 0;
          break;
        }
      }

      if (ans[dpos[0]] != s[i][dpos[1]] || ans[dpos[1]] != s[i][dpos[0]]) {
        f = 0;
        break;
      }
    }

    if (f) {
      cout << ans << '\n';
      return;
    }

  } while (next_permutation(ord.begin(), ord.end()));

  cout << "-1\n";
}

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

标答做法

标答做法是 $O(n ^ 2 k)$ 的。

先统计每个串和 $s_0$ 之间不同的位置。

直接暴力枚举交换 $s_0$ 中的要交换的两个位置。看这组交换之后对 $k$ 个串不同位置的影响。如果修复到了 $2$ 个或 $0$ 个且有重复字母则成立。

Prev: [CF] D. Almost Difference - Educational Codeforces Round 34 (Rated for Div. 2)
Next: [CF] E. Cheap Dinner - Educational Codeforces Round 104 (Rated for Div. 2)