Description
Solution
观察性质,发现最优解下对于目标串中的每个位置都可以找一个初始串的位置匹配,并且匹配确定后最短操作次数一定。
这样我们考虑以匹配为状态(dp)求出最少操作次数,设(dp_{i ,j})表示目标串最后(i)个位置,已经和初始串中([j, l])区间内匹配上,([j, l])区间中可以有没有产生匹配的位置。
那这样对于每个位置我们找一个位置和它匹配,假设我们枚举到(i + 1)位置的状态是(dp_{i + 1, j}),如果当前位置和区间([j, l])中某个位置匹配,那么它首先肯定会和之前匹配的某个位置在最后目标串中的相对位置产生冲突,这样最后需要移动一次使得他们不冲突;其次它能产生匹配当且仅当([j, l])中的当前位置字符的数量比目标串中([i, l])中的当前位置字符的数量多,因为([i, l])中的字符肯定已经和([j, l])中的字符匹配完了,必须确保还有字符可以匹配。
那么如果当前位置和区间([1, j - 1])中的位置匹配,相对位置不会产生冲突,所以不会产生贡献,只需要更新状态中的(j)。
这样就是一个(O(n ^ 3))的(dp)。
per(i, l, 2)
rep(j, 1, l) {
if (dp[i][j] != 0x3f3f3f3f) {
if (tax2[j][st1[i - 1] - 'a'] > tax1[i][st1[i - 1] - 'a'])
dp[i - 1][j] = std::min(dp[i - 1][j], dp[i][j] + 1);
for (int k = 1; k <= j - 1; k++)
if (st2[k] == st1[i - 1])
dp[i - 1][k] = std::min(dp[i - 1][k], dp[i][j]);
}
发现通过后缀(min)优化,可以达到(O(n ^ 2))的复杂度。
转移的时候记录是从什么状态转移过来的,借此可以推出最优解下一种可能的位置匹配方式。
那么知道位置匹配方式之后构造方案就从后往前构造,如果当前位置的匹配位置和之前的某些位置冲突,就把它提前到这些冲突位置中最靠前的那个。
由于输出方案时位置是相对的,所以可以开一个数组记录有多少后面的位置匹配到了它之前的位置算算影响。
Code
#include <bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define MP std::make_pair
#define PII std::pair<int, int>
#define all(x) (x).begin(), (x).end()
#define CL(a, b) memset(a, b, sizeof a)
#define rep(i, l, r) for (int i = (l); i <= (r); ++ i)
#define per(i, r, l) for (int i = (r); i >= (l); -- i)
#define PE(x, a) for (int x = head[a]; x;x = edge[x].next)
typedef long long ll;
template <class T>
inline void rd(T &x) {
char c = getchar(), f = 0; x = 0;
while (!isdigit(c)) f = (c == '-'), c = getchar();
while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
x = f ? -x : x;
}
const int MAXN = 2000;
int l, dp[MAXN + 50][MAXN + 50], tax1[MAXN + 50][50], tax2[MAXN + 50][50], pp[MAXN + 50], vis[MAXN + 50], p[MAXN + 50], ans[MAXN + 50];
char st1[MAXN + 50], st2[MAXN + 50];
PII from[MAXN + 50][MAXN + 50];
void chkmin(int x, int y, int i, int j, int val) {
if (dp[x][y] > dp[i][j] + val)
dp[x][y] = dp[i][j] + val, from[x][y] = MP(i, j);
return;
}
void work(int x, int y) {
pp[x] = y;
int i = from[x][y].fi, j = from[x][y].se;
if (!i && !j) return;
work(i, j);
return;
}
void file() {
freopen("chinese.in", "r", stdin);
freopen("chinese.out", "w", stdout);
return;
}
int main() {
file();
int t;
rd(t);
while (t--) {
memset(vis, 0, sizeof(vis));
memset(p, 0, sizeof(p));
memset(ans, 0, sizeof(ans));
memset(dp, 0x3f, sizeof(dp));
for (int i = 1; i <= MAXN; i++)
for (int j = 1; j <= MAXN; j++)
from[i][j] = MP(0, 0);
memset(tax1, 0, sizeof(tax1));
memset(tax2, 0, sizeof(tax2));
scanf("%s", st1 + 1);
scanf("%s", st2 + 1);
l = strlen(st1 + 1);
per(i, l, 1)
rep(j, 0, 25)
tax1[i][j] = tax1[i + 1][j] + (st1[i] == ('a' + j)),
tax2[i][j] = tax2[i + 1][j] + (st2[i] == ('a' + j));
per(i, l, 1)
if (st2[i] == st1[l])
dp[l][i] = 0;
per(i, l, 1) chkmin(l, i, l, i + 1, 0);
/* per(i, l, 2)
rep(j, 1, l) {
if (dp[i][j] != 0x3f3f3f3f) {
if (tax2[j][st1[i - 1] - 'a'] > tax1[i][st1[i - 1] - 'a'])
dp[i - 1][j] = std::min(dp[i - 1][j], dp[i][j] + 1);
for (int k = 1; k <= j - 1; k++)
if (st2[k] == st1[i - 1])
dp[i - 1][k] = std::min(dp[i - 1][k], dp[i][j]);
}
}*/
per(i, l - 1, 1)
per(j, l, 1) {
if (st1[i] == st2[j])
chkmin(i, j, i + 1, j + 1, 0);
// dp[i][j] = std::min(dp[i][j], dp[i + 1][j + 1]);
if (tax2[j][st1[i] - 'a'] > tax1[i + 1][st1[i] - 'a'])
chkmin(i, j, i + 1, j, 1);
// dp[i][j] = std::min(dp[i][j], dp[i + 1][j] + 1);
chkmin(i, j, i, j + 1, 0);
//dp[i][j] = std::min(dp[i][j], dp[i][j + 1]);
}//后缀min将n^3优化到n^2
printf("%d
", dp[1][1]);
work(1, 1);
per(i, l, 1) if (pp[i] != pp[i + 1] && st2[pp[i]] == st1[i]) p[i] = 1, vis[pp[i]] = 1;
per(i, l, 1) {
if (p[i]) continue;
per(j, l, pp[i] + 1)
if (!vis[j] && st2[j] == st1[i]) {
pp[i] = j;
vis[j] = 1;
break;
}
}//找到最优情况下的匹配。
int poss = l + 1;
per(i, l, 1) {
if (pp[i] < poss) {
poss = pp[i];
continue;
}
printf("%d %d
", pp[i] + ans[pp[i]], poss);
per(j, pp[i], poss)
ans[j]++;
}//按顺序从后面往前构造
}
return 0;
}