【SJTUOJ笔记】P1125 Typist (NOI2001 聪明的打字员)

https://acm.sjtu.edu.cn/OnlineJudge/problem/1125
注:本文中,用←→↑↓分别表示光标左移、光标右移、加、减,swap0和swap5分别表示与第一位交换、与最后一位交换。
一开始我联想到另一道有些相似的题目https://acm.sjtu.edu.cn/OnlineJudge/problem/1071,以为是动态规划。但思考之后发现那道题的动态规划想法无法处理本题的交换操作。考虑到字串位数固定只有6位,再加上光标位置,所有状态一共有 6×106 6 × 10 6 种,暴搜加剪枝应该能过。
由于封禁了stl,首先交代一下本题我采用的存储结构。每种状态用一个String类型的变量表示(然而类型内部其实并没有string或char数组一类的东西),成员如下:

  • int s,表示字符串对应的整数, 0s999999 0 ≤ s ≤ 999999
  • int step,表示从初始状态到当前状态的步数;
  • int cursor, 表示当前状态的光标位置, 0cursor5 0 ≤ c u r s o r ≤ 5
  • 拷贝赋值函数;
  • bool operator ==,用于判断两个String是否相等;
  • const int operator [](int x),用于取出s的第x位,但不可修改;
  • void inc(int x),第x位加一;
  • void dec(int x),第x位减一;
  • void swap(int x, int y),交换x和y位置的数字。

具体实现略过。
剪枝的策略是显然的。如果某一位是0,那么不能↓;是9,不能↑;光标在0,不能←;光标在5,不能→;第i位已经与目标数的第i位相同,不必↑↓。除此之外,更加复杂的剪枝已经不需要了,随意剪枝反而可能引起错误。
例如,我在其他博客上看到的题解,大部分都提到“光标在1~4时,如果该位和目标串的对应位不同,不应该进行←和→操作”。理由是移动之后不能进行直接修改,还要再移回来,做了无用功。事实上,这种剪枝策略是错误的,上述理由并不成立。因为我们可以通过交换操作,通过0位或5位间接地把不在0或5的两个数交换。当这两个数相差较大时,规避←→的直接加减显然比利用←→的间接交换耗费的次数更多。如这样一组数据:

109444
190444

按前文错误的剪枝策略,得到的答案是14。但实际上的最佳操作序列是

→ swap0 → swap0 ← swap0

仅需6次操作。
核心部分如下:

//已经定义了长度为1000000的循环队列q和访问标记v[i][j]
//v[i][j]表示数字为i、光标在j的状态是否访问过
//push(x)当x未被访问时将x加进队列尾,否则什么都不做
int bfs(){
    push(s);
    while (front != rear){
        front = (front + 1) % MAXS;
        String t = q[front];
        if (t == a){ //a是目标串
            return t.step;
        }
        String n;
        int i = t.cursor;
        if (i != 0){ //swap0
            n = t;
            ++n.step;
            n.swap(i, 0);
            push(n);
        }
        if (i != 5){ //swap5
            n = t;
            ++n.step;
            n.swap(i, 5);
            push(n);
        }
        if (t[i] != a[i]){
            if (t[i] != 0){ //↑
                n = t;
                ++n.step;
                n.dec(i);
                push(n);
            }
            if (t[i] != 9){ //↓
                n = t;
                ++n.step;
                n.inc(i);
                push(n);
            }
        }
        if (i != 0){ //←
            n = t;
            ++n.step;
            --n.cursor;
            push(n);
        }
        if (i != 5){ //→
            n = t;
            ++n.step;
            ++n.cursor;
            push(n);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值