Words / 连词成句 / 小学生语文题
题目链接:jzoj 5102
题目大意
给你两个字符串,一个是目标,一个是当前。
你每次可以搞一个操作,在当前串选一个位置,把它移到前面的任意一个位置。
问你最少要多少次操作才能得到目标串。
思路
那你考虑正着搞不好搞,你考虑反着搞——从右边往左边搞。
那你就会发现你那些要滑的可以可以留着,等到时匹配不上了再滑,这样就能处理滑的,就很好搞。
然后你考虑 DP,设
f
i
,
j
f_{i,j}
fi,j 为目标拼好了
i
∼
n
i\sim n
i∼n,用掉了原来的
j
∼
n
j\sim n
j∼n 的最少移动次数。
然后我们考虑转移,
f
i
,
j
f_{i,j}
fi,j 可以从这些地方:
f
i
+
1
,
j
+
1
f_{i+1,j+1}
fi+1,j+1(条件是
a
i
=
b
j
a_i=b_j
ai=bj,直接匹配)
f
i
+
1
,
j
f_{i+1,j}
fi+1,j(条件是有多余可以移动的给到
a
i
a_i
ai,至于看有没有我们可以统计字母后缀出现次数,然后减看还有没有多余的
a
i
a_i
ai)
f
i
,
j
+
1
+
1
f_{i,j+1}+1
fi,j+1+1(没有条件,就相当于不跟
i
i
i 匹配,拿
j
j
j 去滑)
然后不难看出答案是 f 1 , 1 f_{1,1} f1,1,初始化时 f n + 1 , j = n − j + 1 f_{n+1,j}=n-j+1 fn+1,j=n−j+1(就全部拿去滑)
接着看如何输出方案。
那不难想到可以记录下你的最优决策是从哪个地方转过来的。
(初始化是从第三个转移过来的)
那如果是从
f
i
+
1
,
j
+
1
f_{i+1,j+1}
fi+1,j+1 转过来的,那就是不用转的标记一下。
然后要转的你就找字母相同的两两匹配(记得要从后面到前面),然后匹配之后中间的字母位置会变,要记得移动(标记也要移动)。
然后就把匹配到的输出就行了。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
int n, q, ans, g[2001][2001][2];
int f[2001][2001], num[2001][31], num1[2001][31];
char a[2001], b[2001];
bool noa[2001], nob[2001];
void csh() {
memset(f, 0x7f, sizeof(f));
memset(g, 0, sizeof(g));
memset(num, 0, sizeof(num));
memset(num1, 0, sizeof(num1));
memset(noa, 0, sizeof(noa));
memset(nob, 0, sizeof(nob));
}
int main() {
// freopen("chinese.in", "r", stdin);
// freopen("chinese.out", "w", stdout);
scanf("%d", &q);
while (q--) {
scanf("%s", a + 1);
n = strlen(a + 1);
scanf("%s", b + 1);
csh();
for (int i = n; i >= 1; i--) {//维护后缀个数和
for (int j = 0; j < 26; j++)
num[i][j] = num[i + 1][j], num1[i][j] = num1[i + 1][j];
num[i][a[i] - 'a']++;
num1[i][b[i] - 'a']++;
}
for (int i = 1; i <= n + 1; i++) {//初始化
f[n + 1][i] = n - i + 1;
g[n + 1][i][0] = n + 1;
g[n + 1][i][1] = i + 1;
}
for (int i = n; i >= 1; i--) {//DP(三种情况)
for (int j = n; j >= 1; j--) {
if (f[i][j] > f[i + 1][j] && num1[j][a[i] - 'a'] - num[i + 1][a[i] - 'a'] > 0) {
f[i][j] = f[i + 1][j];
g[i][j][0] = i + 1;
g[i][j][1] = j;
}
if (f[i][j] > f[i + 1][j + 1] && a[i] == b[j]) {
f[i][j] = f[i + 1][j + 1];
g[i][j][0] = i + 1;
g[i][j][1] = j + 1;
}
if (f[i][j] > f[i][j + 1] + 1) {
f[i][j] = f[i][j + 1] + 1;
g[i][j][0] = i;
g[i][j][1] = j + 1;
}
}
}
printf("%d\n", f[1][1]);
int x = 1, y = 1;
while (x < n + 1 || y < n + 1) {//按你记录的路径跳回去
int xt = g[x][y][0], yt = g[x][y][1];
if (x + 1 == xt && y + 1 == yt)//标记直接匹配的
noa[x] = 1, nob[y] = 1;
x = xt; y = yt;
}
for (int i = 1; i <= n; i++)//直接把要换的一一匹配
if (!noa[i])
for (int j = i + 1; j <= n; j++) {
if (!nob[j] && a[i] == b[j]) {
for (int k = j; k > i; k--)//匹配之后数的位置会改变
nob[k] = nob[k - 1], b[k] = b[k - 1];
printf("%d %d\n", j, i);
break;
}
}
}
fclose(stdin);
fclose(stdout);
return 0;
}