该算法拼图代码(USACO)的良好解决方案?

时间:2019-01-19 05:30:40

标签: java algorithm

我是计算机科学专业的一年级学生,目前正在参加一些算法竞赛。我在下面编写的代码存在一个缺陷,我不确定如何修复

这是问题说明: http://www.usaco.org/index.php?page=viewproblem2&cpid=811

在声明中,我想念了农夫约翰只能在两个靴子都可以站立的瓷砖上切换靴子的地方。我尝试在不同的地方添加约束,但是似乎没有一个可以完全解决该问题。我真的没有找到一种方法,而无需保留代码

基本上,问题在于约翰不断在无法支撑新靴子的瓷砖上切换靴子,而我似乎无法修复

这是我的代码(抱歉一个字母的变量):

import java.io.*;
import java.util.*;
public class snowboots {
    static int n,k;
    static int[] field,a,b; //a,b --> strength, distance
    static int pos;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("snowboots.in"));
        PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter("snowboots.out")));
        StringTokenizer st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        k = Integer.parseInt(st.nextToken());
        st = new StringTokenizer(br.readLine());
        field = new int[n];
        a = new int[k];
        b = new int[k];
        for (int i = 0; i < n; i++)
            field[i] = Integer.parseInt(st.nextToken());
        for (int i = 0; i < k; i++) {
            st = new StringTokenizer(br.readLine());
            a[i] = Integer.parseInt(st.nextToken());
            b[i] = Integer.parseInt(st.nextToken());
        }

        pw.println(solve());
        pw.close();

    }
    static int solve() {
        pos = 0;
        int i = 0; //which boot are we on?
        while(pos < n-1) {

            while(move(i)); //move with boot i as far as possible

            i++; //use the next boot

        }
        i--;
        return i;
    }
    static boolean move(int c) {
        for (int i = pos+b[c]; i > pos; i--) {
            if (i < n && field[i] <= a[c]) { //snow has to be less than boot strength
                pos = i;
                return true;
            }
        }
        return false;
    }
}

我尝试在“ move”方法中添加一个约束,并在更新I时添加一个约束,但是它们都太严格了,并且会在不需要的时间激活

可抢救吗?

3 个答案:

答案 0 :(得分:2)

是的,可以通过添加额外的for循环来挽救您的解决方案。

您需要做的是,如果您发现前一双靴子可以一直带到雪深的地块,而下双靴子则不行,那么您需要尝试“回溯”到最新的地砖那不是很深。最终给出了在最坏情况下 O N · B )时间和 O (1)的解决方案多余的空间。

回溯到该图块的原因为什么还不错,毕竟这仅仅是因为您可以到达给定的图块,但这并不一定意味着您能够到达它之前的所有图块,所以让我解释一下为什么可以正常。

maxReachableTileNum为您之前启动时可以到达的最后一个图块的数字(介于1和 N 之间),让lastTileNumThatsNotTooDeep为该数字maxReachableTileNum或之前的最后一个图块(在1和 N 之间),对于您的下一对来说,雪覆盖的区域不太深。 (我们知道有 这样的图块,因为图块#1根本没有积雪,因此,如果没有别的,我们知道我们可以追溯到一开始。)现在,因为我们能够进入maxReachableTileNum,然后 some 以前的启动必须踩到lastTileNumThatsNotTooDeep(在这种情况下,没问题,可以到达),或者跳过它再到以后的某个磁贴(在或maxReachableTileNum之前)。但是后面的图块必须比lastTileNumThatsNotTooDeep更深(因为后面的图块的深度大于 s currentBootNum ,至少lastTileNumThatsNotTooDeep的深度),这意味着跳过lastTileNumThatsNotTooDeep的靴子肯定 可以踩在lastTileNumThatsNotTooDeep上:这意味着要走更短的一步(确定)到比实际覆盖程度更深的图块(确定)上。因此,无论哪种方式,我们都知道lastTileNumThatsNotTooDeep是可以到达的。因此,对我们来说,尝试回溯到lastTileNumThatsNotTooDeep是安全的。 (请注意:以下代码使用名称reachableTileNum代替了lastTileNumThatsNotTooDeep,因为它继续使用reachableTileNum变量向前搜索以找到可到达的图块。)

但是,我们仍然必须坚持以前的maxReachableTileNum:回溯可能对您没有帮助(因为它可能无法使我们取得比已有的任何进一步进步),在这种情况下,我们只需丢弃这些引导程序,然后继续使用maxReachableTileNum为其下一个值的下一个对。

因此,总的来说,我们有这个:

public static int solve(
    final int[] tileSnowDepths,           // tileSnowDepths[0] is f_1
    final int[] bootAllowedDepths,        // bootAllowedDepths[0] is s_1
    final int[] bootAllowedTilesPerStep   // bootAllowedTilesPerStep[0] is d_1
) {
    final int numTiles = tileSnowDepths.length;
    final int numBoots = bootAllowedDepths.length;
    assert numBoots == bootAllowedTilesPerStep.length;

    int maxReachableTileNum = 1; // can reach tile #1 even without boots

    for (int bootNum = 1; bootNum <= numBoots; ++bootNum) {
        final int allowedDepth = bootAllowedDepths[bootNum-1];
        final int allowedTilesPerStep = bootAllowedTilesPerStep[bootNum-1];

        // Find the starting-point for this boot -- ideally the last tile
        // reachable so far, but may need to "backtrack" if that tile is too
        // deep; see explanation above of why it's safe to assume that we
        // can backtrack to the latest not-too-deep tile:

        int reachableTileNum = maxReachableTileNum;
        while (tileSnowDepths[reachableTileNum-1] > allowedDepth) {
            --reachableTileNum;
        }

        // Now see how far we can go, updating both maxReachableTileNum and
        // reachableTileNum when we successfully reach new tiles:

        for (int tileNumToTry = maxReachableTileNum + 1;
             tileNumToTry <= numTiles
                 && tileNumToTry <= reachableTileNum + allowedTilesPerStep;
             ++tileNumToTry
        ) {
            if (tileSnowDepths[tileNumToTry-1] <= allowedDepth) {
                maxReachableTileNum = reachableTileNum = tileNumToTry;
            }
        }

        // If we've made it to the last tile, then yay, we're done:

        if (maxReachableTileNum == numTiles) {
            return bootNum - 1; // had to discard this many boots to get here
        }
    }

    throw new IllegalArgumentException("Couldn't reach last tile with any boot");
}

(我在USACO的示例数据上对此进行了测试,并按预期返回了2。)

这可能会进一步优化,例如使用逻辑来跳过对靴子显然无济于事的靴子(因为它们既没有以前成功的靴子强,也没有敏捷),或者具有额外的数据结构来跟踪最新的最小值(优化回溯)流程),或避免回溯的逻辑超出了想像的有用范围;但考虑到 N · B ≤250 2 = 62,500,我认为没有必要进行任何此类优化。


编辑后添加(2019-02-23):我已经作了进一步考虑,我想到实际上可以在最坏的情况下编写解决方案 O N + B log N )时间(渐近性优于 O N · B ))和 O N )多余的空间。但这要复杂得多。它涉及三个额外的数据结构(一个用于跟踪最新最小值的位置,以允许在 O (log N )时间内回溯;一个用于跟踪 future 最小值的位置,以便在回溯确实有帮助的情况下检查(em> N (log N )时间)相关的最小值);以及一个用于维护必要的前瞻性信息,以便在amortized O (1)时间中维护第二个)。解释为什么保证该解决方案保证在 O N + B log N )时间之内也很复杂(因为它涉及很多amortized analysis,并且进行细微的更改似乎是一种优化,例如,用二进制搜索替换线性搜索,可能会破坏分析并实际上增加最糟糕的情况是时间复杂度。由于已知 N B 最多不超过250个,因此我认为所有复杂性都不值得。

答案 1 :(得分:0)

使用BFS解决问题的一种方法。您可以参考下面的代码以获取详细信息。希望这会有所帮助。

import java.util.*;
import java.io.*;

public class SnowBoots {

    public static int n;
    public static int[] deep;
    public static int nBoots;
    public static Boot[] boots;

    public static void main(String[] args) throws Exception {

        // Read the grid.
        Scanner stdin = new Scanner(new File("snowboots.in"));

        // Read in all of the input.
        n = stdin.nextInt();
        nBoots = stdin.nextInt();
        deep = new int[n];
        for (int i = 0; i < n; ++i) {
            deep[i] = stdin.nextInt();
        }
        boots = new Boot[nBoots];
        for (int i = 0; i < nBoots; ++i) {
            int d = stdin.nextInt();
            int s = stdin.nextInt();
            boots[i] = new boot(d, s);
        }

        PrintWriter out = new PrintWriter(new FileWriter("snowboots.out"));
        out.println(bfs());
        out.close();
        stdin.close();
    }

    // Breadth First Search Algorithm [https://en.wikipedia.org/wiki/Breadth-first_search]
    public static int bfs() {

        // These are all valid states.
        boolean[][] used = new boolean[n][nBoots];
        Arrays.fill(used[0], true);

        // Put each of these states into the queue.
        LinkedList<Integer> q = new LinkedList<Integer>();
        for (int i = 0; i < nBoots; ++i) {
            q.offer(i);
        }

        // Usual bfs.
        while (q.size() > 0) {

            int cur = q.poll();
            int step = cur / nBoots;
            int bNum = cur % nBoots;

            // Try stepping with this boot...
            for (int i = 1; ((step + i) < n) && (i <= boots[bNum].maxStep); ++i) {
                if ((deep[step+i] <= boots[bNum].depth) && !used[step+i][bNum]) {
                    q.offer(nBoots * (step + i) + bNum);
                    used[step + i][bNum] = true;
                }
            }

            // Try switching to another boot.
            for (int i = bNum + 1; i < nBoots; ++i) {
                if ((boots[i].depth >= deep[step]) && !used[step][i]) {
                    q.offer(nBoots * step + i);
                    used[step][i] = true;
                }
            }
        }

        // Find the earliest boot that got us here.
        for (int i = 0; i < nBoots; ++i) {
            if (used[n - 1][i]) {
                return i;
            }
        }

        // Should never get here.
        return -1;
    }
}

class Boot {

    public int depth;
    public int maxStep;

    public Boot(int depth, int maxStep) {
        this.depth = depth;
        this.maxStep = maxStep;
    }
}

答案 2 :(得分:0)

您可以通过动态编程解决此问题。您可以在link中看到概念(只需阅读计算机编程部分)。 它分为以下两个步骤。

  • 首先以递归方式解决问题。
  • 记住状态。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mx 100005
#define mod 1000000007
int n, b;
int f[333], s[333], d[333];
int dp[251][251];
int rec(int snowPos, int bootPos)
{
    if(snowPos == n-1){
        return 0;

    int &ret = dp[snowPos][bootPos];
    if(ret != -1) return ret;
    ret = 1000000007;
    for(int i = bootPos+1; i<b; i++)
    {
        if(s[i] >= f[snowPos]){
            ret = min(ret, i - bootPos + rec(snowPos, i));
        }
    }
    for(int i = 1; i<=d[bootPos] && snowPos+i < n; i++){
        if(f[snowPos + i] <= s[bootPos]){
            ret = min(ret, rec(snowPos+i, bootPos));
        }
    }
    return ret;
}
int main()
{
    freopen("snowboots.in", "r", stdin);
    freopen("snowboots.out", "w", stdout);
    scanf("%d %d", &n, &b);
    for(int i = 0; i<n; i++)
        scanf("%d", &f[i]);
    for(int i = 0; i<b; i++){
        scanf("%d %d", &s[i], &d[i]);
    }
    memset(dp, -1, sizeof dp);
    printf("%d\n", rec(0, 0));

    return 0;
}

这是我针对此问题的解决方案(在C ++中)。 这只是递归。正如问题所言,

  • 您可以更改启动方式,或者
  • 您可以通过当前引导进行跳转。

记忆部分由二维数组dp [] []完成。