GDOI2017 day2 Problem3 小学生语文题

题目大意:

给出一个只由小写英文字母组成的字符串,每次可以把一个字母调到它前面的一个位置,求与目标串相同的最少步数,并输出一组方案。


吹水:

太菜了,比赛时连部分分也没拿。
这个dp还是比较好理解的,但是也不简单,做着非常有意思。


题解:

首先我们很容易想到,每个字符一定只会移一次,这样才是最优的。
然后最关键的是,假设现在把b串(目标串)移动后符合了a串从后面开始的几位,那我们犯不着把已经匹配好的后面几位打乱,于是我们可以想到倒着dp。

fi,j 表示已经把a串的后面i为匹配好,移动了b串的后j位。

1. fi,j = fi1,j
含义是去过的j位当中有一个刚好和 Ai 相同的,直接放到这即可。
那么我们需要判断B的后j位当中是否有多的

2. fi,j = fi+1,j+
Ai = Bj 时,刚好不用移动。

3. fi,j = fi,j+1  +1
取出 Bj ,先不用。

初值:
fn+1,i = n+1i (1 <= i <= n)

f1,1 就是答案。

于是最小步数就求出来了。
我们还要求移动方案。

首先找到那些不用动的点:
记录每个状态是由哪个状态推来的,如果 fi,j 是由 fi+j+1 推来的,显然 Bj Ai 就不会移动。

然后我们直接模拟一遍,从A串(目标串)的左边到右边,对于每一个 Ai ,如果它是被移过来的,直接在b串里随便找一个没被移过且要移动的,移过来即可,这里可以直接暴力把B串右移,反正都是O( n2

槽点:

在暴力右移的时候,我竟然顺着 Bi = Bi1 移动,于是GG,样例还过了,切记,这种整体移动,一定要倒着移,否则就会覆盖,左移当然就顺着做了。

Code:

#include<cstdio>
#include<cstring>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
using namespace std;

const int Maxn = 2005;

char s[Maxn], s1[Maxn];
int n, m, f[Maxn][Maxn], g[Maxn][Maxn][2], sum[26][Maxn], sum1[26][Maxn];
int bz[Maxn], bz2[Maxn];

int main() {
    freopen("chinese.in", "r", stdin);
    freopen("chinese.out", "w", stdout);
    for(scanf("%d", &m); m; m --) {
        memset(f, 30, sizeof(f)); memset(g, 0, sizeof(g));
        memset(bz, 0, sizeof(bz)); memset(bz2, 0, sizeof(bz2));
        scanf("%s", s + 1); scanf("%s", s1 + 1);
        n = strlen(s + 1);
        fo(i, 0, 25) sum[i][n + 1] = sum1[i][n + 1] = 0;
        fd(i, n, 1) {
            fo(j, 0, 25) sum[j][i] = sum[j][i + 1], sum1[j][i] = sum1[j][i + 1];
            sum[s[i] - 'a'][i] ++; sum1[s1[i] - 'a'][i] ++;
        }
        fo(i, 1, n + 1) f[n + 1][i] = n - i + 1, g[n + 1][i][0] = n + 1, g[n + 1][i][1] = i + 1;
        fd(i, n, 1) {
            fd(j, i, 1) {
                if(sum[s[i] - 'a'][i] <= sum1[s[i] - 'a'][j]) {
                    if(f[i + 1][j] < f[i][j])
                        f[i][j] = f[i + 1][j], g[i][j][0] = i + 1, g[i][j][1] = j;
                }
                if(s[i] == s1[j] && f[i + 1][j + 1] < f[i][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 + 1] + 1 < f[i][j])
                    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 xx = g[x][y][0], yy = g[x][y][1];
            if(x + 1 == xx && y + 1 == yy)
                bz[x] = 1, bz2[y] = 1;
            x = xx; y = yy;
        }
        fo(i, 1, n) if(!bz[i]) {
            fo(j, i, n) if(!bz2[j] && s[i] == s1[j]) {
                fd(k, j, i + 1)
                    bz2[k] = bz2[k - 1], s1[k] = s1[k - 1];
                printf("%d %d\n", j, i);
                break;
            }
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值