牛客网练习赛13部分总结

F-m皇后问题

problem

链接:https://www.nowcoder.com/acm/contest/70/F
来源:牛客网

在一个n*n的国际象棋棋盘上有m个皇后。
一个皇后可以攻击其他八个方向的皇后(上、下、左、右、左上、右上、左下、右下)。
对于某个皇后,如果某一个方向上有其他皇后,那么这个方向对她就是不安全的。
对于每个皇后,我们都能知道她在几个方向上是不安全的。

现在我们想要求出t0,t1,…,t8,其中ti表示恰有i个方向是”不安全的”的皇后有多少个。

输入描述:

第一行两个整数n,m表示棋盘大小和皇后数量。
接下来m行每行两个整数ri,ci表示皇后坐标。
1 <= n, m <= 100,000
1 <= ri, ci <= n
数据保证没有皇后在同一个位置上。

输出描述:

一行九个整数表示答案。
空格隔开,结尾无空格

示例1
输入

8 4
4 3
4 8
6 5
1 6

输出

0 3 0 1 0 0 0 0 0

示例2
输入

10 3
1 1
1 2
1 3

输出

0 2 1 0 0 0 0 0 0

解析

首先这道题不能又常规的做法,即建一个二维数组存储各个皇后的位置,这样会导致内存超限。其次还不能对每个皇后判断其能否攻击到其他皇后,因为m很大,复杂度为$O(n^2)$,TLE.
故转换思路,把思路放在这8个方向上,如果我能每次都考虑一个方向,这样比考虑皇后本身效率要高点。所以我们的思路是:
- 按横坐标排序,纵坐标为第二关键字,这样既可得到左右的不安全的皇后数量
- 按纵坐标排序,横坐标为第二关键字,这样既可得到上下的不安全的皇后数量
- 按横坐标与纵坐标的差值排序,横坐标为第二关键字,这样既可得到左上和右下的不安全的皇后数量
- 按横坐标与纵坐标的和排序,横坐标为第二关键字,这样既可得到右上和左下的不安全的皇后数量

我们抽象出一个类,有横坐标和纵坐标,和不安全次数。
这样我们在上诉的四个排序中,既可统计出每个皇后的不安全的方向。最后通过哈希的思想统计不安全方向的序号对应的皇后的数量。复杂度O(4nlogn + n)

tips: C++ 100多ms,而Java会抖机灵,可见Java的排序慢啊。
总结:二维数组的性质,处于同一个左对角线上的点,横纵差值相等,处于同一个右对角线上的点,横纵的和相等。

package com.special.test14;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.StringTokenizer;

/**
 * F-m皇后问题
 * Create by Special on 2018/4/3 19:16
 */
public class ProF {

    static class Node{
        int x, y, count;
        Node(int x, int y){
            this.x = x;
            this.y = y;
        }
    }

    static class cmp1 implements Comparator<Node>{

        @Override
        public int compare(Node o1, Node o2) {
            if(o1.x != o2.x) return o1.x - o2.x;
            else return o1.y - o2.y;
        }
    }
    static class cmp2 implements Comparator<Node>{

        @Override
        public int compare(Node o1, Node o2) {
            if(o1.y != o2.y) return o1.y - o2.y;
            else return o1.x - o2.x;
        }
    }
    static class cmp3 implements Comparator<Node>{

        @Override
        public int compare(Node o1, Node o2) {
            if((o1.x - o1.y) != (o2.x - o2.y)) return (o1.x - o1.y) - (o2.x - o2.y);
            else return o1.x - o2.x;
        }
    }
    static class cmp4 implements Comparator<Node>{

        @Override
        public int compare(Node o1, Node o2) {
            if((o1.x + o1.y) != (o2.x + o2.y)) return (o1.x + o1.y) - (o2.x + o2.y);
            else return o1.x - o2.x;
        }
    }

    public static void main(String[] args){
        Scanner input = new Scanner();
        PrintWriter out = new PrintWriter(System.out);
        int n = input.nextInt();
        int m = input.nextInt();
        Node[] nodes = new Node[m];
        for(int i = 0; i < m; i++){
            nodes[i] = new Node(input.nextInt(), input.nextInt());
        }
        Arrays.sort(nodes, new cmp1());
        for(int i = 0; i < m - 1; i++){
            if(nodes[i].x == nodes[i + 1].x){
                nodes[i].count++;
                nodes[i + 1].count++;
            }
        }
        Arrays.sort(nodes, new cmp2());
        for(int i = 0; i < m - 1; i++){
            if(nodes[i].y == nodes[i + 1].y){
                nodes[i].count++;
                nodes[i + 1].count++;
            }
        }
        Arrays.sort(nodes, new cmp3());
        for(int i = 0; i < m - 1; i++){
            if(nodes[i].x - nodes[i].y == nodes[i + 1].x - nodes[i + 1].y){
                nodes[i].count++;
                nodes[i + 1].count++;
            }
        }
        Arrays.sort(nodes, new cmp4());
        for(int i = 0; i < m - 1; i++){
            if(nodes[i].x + nodes[i].y == nodes[i + 1].x + nodes[i + 1].y){
                nodes[i].count++;
                nodes[i + 1].count++;
            }
        }
        int[] counts = new int[9];
        for(int i = 0; i < m; i++){
            counts[nodes[i].count]++;
        }
        for(int i = 0; i < 9; i++){
            out.print((i == 0 ? "" : " ") + counts[i]);
        }
        out.println();
        out.close();
    }
}
优化

我们不必排序四次,其实排序一次即可,用空间换时间的做法,按横坐标排序,纵坐标为第二关键字,这样把所有的皇后从左到右,从上到下排列。然后我们再用4个数组,分别为行,纵,左对角线,右对角线。然后我们遍历皇后,如果这些数组的元素为false,我们就对他赋值true,这样第一次赋值的必然是左,上,左上,右上的最小值,后面的皇后判断其对应各个数组的位置如果为true,则说明不安全,计数即可。同理,最后一次赋值的必然是右,下,左下,右下的最大值。在从末尾遍历一次即可。

当然也不用排序,输入时记录最小最大即可。

package com.special.test14;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.StringTokenizer;

/**
 * F-m皇后问题
 * Create by Special on 2018/4/3 19:16
 */
public class ProF {

    static class Node{
        int x, y, count;
        Node(int x, int y){
            this.x = x;
            this.y = y;
        }
    }

    static class cmp1 implements Comparator<Node>{

        @Override
        public int compare(Node o1, Node o2) {
            if(o1.x != o2.x) return o1.x - o2.x;
            else return o1.y - o2.y;
        }
    }

    public static void main(String[] args){
        Scanner input = new Scanner(System.in);
        PrintWriter out = new PrintWriter(System.out);
        int n = input.nextInt();
        int m = input.nextInt();
        Node[] nodes = new Node[m];
        int length = n + 1;
        boolean[] row = new boolean[length];
        boolean[] col = new boolean[length];
        boolean[] left = new boolean[length << 1];
        boolean[] right = new boolean[length << 1];
        for(int i = 0; i < m; i++){
            nodes[i] = new Node(input.nextInt(), input.nextInt());
        }
        Arrays.sort(nodes, new cmp1());
        for(int i = 0; i < m; i++){
            if(row[nodes[i].x]) nodes[i].count++;
            row[nodes[i].x] = true;
            if(col[nodes[i].y]) nodes[i].count++;
            col[nodes[i].y] = true;
            if(left[nodes[i].x - nodes[i].y + length]) nodes[i].count++;
            left[nodes[i].x - nodes[i].y + length] = true;
            if(right[nodes[i].x + nodes[i].y]) nodes[i].count++;
            right[nodes[i].x + nodes[i].y] = true;
        }
        Arrays.fill(row, false);
        Arrays.fill(col, false);
        Arrays.fill(left, false);
        Arrays.fill(right, false);
        for(int i = m - 1; i >= 0; i--){
            if(row[nodes[i].x]) nodes[i].count++;
            row[nodes[i].x] = true;
            if(col[nodes[i].y]) nodes[i].count++;
            col[nodes[i].y] = true;
            if(left[nodes[i].x - nodes[i].y + length]) nodes[i].count++;
            left[nodes[i].x - nodes[i].y + length] = true;
            if(right[nodes[i].x + nodes[i].y]) nodes[i].count++;
            right[nodes[i].x + nodes[i].y] = true;
        }
        int[] counts = new int[9];
        for(int i = 0; i < m; i++){
            counts[nodes[i].count]++;
        }
        for(int i = 0; i < 9; i++){
            out.print((i == 0 ? "" : " ") + counts[i]);
        }
        out.println();
        out.close();
    }
}

E-乌龟跑步

链接:https://www.nowcoder.com/acm/contest/70/E
来源:牛客网

有一只乌龟,初始在0的位置向右跑。
这只乌龟会依次接到一串指令,指令T表示向后转,指令F表示向前移动一个单位。乌龟不能忽视任何指令。
现在我们要修改其中正好n个指令(一个指令可以被改多次,一次修改定义为把某一个T变成F或把某一个F变成T)。
求这只乌龟在结束的时候离起点的最远距离。(假设乌龟最后的位置为x,我们想要abs(x)最大,输出最大的abs(x))

输入描述:

第一行一个字符串c表示指令串。c只由F和T构成。
第二行一个整数n。
1 <= |c| <= 100, 1 <= n <= 50

输出描述:

一个数字表示答案。

解析

知道这个题应该用dp来做,可是我第一次做的时候,完全不知道该怎么构造dp。看了动态规划还是掌握的不彻底。
我首先用dfs + 记忆化搜索来做吧。有时候想不出dp,就用dfs + 记忆化搜索吧,想法在代码注释里,参考这篇博客:
https://blog.csdn.net/albertluf/article/details/79603187

代码
package com.special.test14;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;

/**
 * Create by Special on 2018/4/4 16:09
 */
public class ProE {

     static boolean[][][][] maps;
     static String c;
     static int n, max;

     static void dfs(int index, int n, int pos, int d){
         int f = d == 1 ? 1 : 0;
         //次数用完,直接返回即可,没有考察完全部字符串,次数就用完了,没法知道全局结果
         if(n < 0){
             return;
         }
         if(index == c.length()){ //考察完了全部字符串
             /**
              * 因为如果到末尾了,还有奇数次更改次数,不能直接求当前最大值
              * 因为实际的更改次数全部用完,一定与当前的状态不一致
              * 比如末尾为FFF,如果当前的次数还有3次,直接求最大值的话,是3
              * 而实际3次更改用完,末尾会变为T, 最大值为2
              * 末尾奇数次可以的情况可以由末尾改变一次得到或者末尾不变得到
              */
             if((n & 1) == 0) {
                 max = Math.max(max, Math.abs(pos - c.length()));
             }
             return;
         }
         if(maps[index][n][pos][f]){ //当前状态已考察过,不必再考察了
             return;
         }
         maps[index][n][pos][f] = true;  //记录当前状态
         if(c.charAt(index) == 'F'){
             dfs(index + 1, n - 1, pos, -d); //改变
             dfs(index + 1, n, pos + d, d); //不改变
         }else {
             dfs(index + 1, n - 1, pos + d, d); //改变
             dfs(index + 1, n, pos, -d); //不改变
         }
     }
     public static void main(String[] args){
        FastScanner input = new FastScanner();
        PrintWriter out = new PrintWriter(System.out);
        c = input.next();
        n = input.nextInt();
        max = 0;
        maps = new boolean[c.length()][n + 1][(c.length() + 1) << 1][2];
        dfs(0, n, c.length(), 1);
        out.println(max);
        out.close();
    }
}

看了dp大神的答案,太牛了,dp[i][j][k][d],表示前i个字符执行了j次命令,在k位置上,d表示朝向,是否可达?
最后遍历所有的dp[n][m][k][d],即可找到最大的距离!

package com.special.test14;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;

/**
 * Create by Special on 2018/4/4 23:10
 */
public class ProEImprove1 {
    static int[] s = {-1, 1};

    public static void main(String[] args){
        FastScanner input = new FastScanner();
        PrintWriter out = new PrintWriter(System.out);
        String c = input.next();
        int n = input.nextInt();
        int dis = (c.length() + 1) << 1;
        boolean[][][][] map = new boolean[c.length() + 1][n + 1][dis][2];
        map[0][0][c.length()][1] = true;
        for(int i = 0; i < c.length(); i++){
            for(int j = 0; j <= n; j++){
                for(int k = 0; k <= dis - 1; k++){
                    for(int d = 0; d < 2; d++) {
                        if (map[i][j][k][d]) {
                            if(c.charAt(i) == 'F'){
                                if(j < n) map[i + 1][j + 1][k][d ^ 1] = true; //改变
                                map[i + 1][j][k + s[d]][d] = true; //不改变
                            }else {
                                if(j < n) map[i + 1][j + 1][k + s[d]][d] = true; //改变
                                map[i + 1][j][k][d ^ 1] = true; //不改变
                            }
                        }
                    }
                }
            }
        }
        int max = 0;
        for(int i = 0; i < 2; i++){
            for(int j = 0; j <= dis - 1; j++){
                if(map[c.length()][n][j][i]){
                    max = Math.max(max, Math.abs(j - c.length()));
                }
            }
        }
        out.println(max);
        out.close();
    }
}

另一种思路:
dp[i][j][d] 代表前i个字符执行j次命令,处于d方向的的距离

package com.special.test14;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;

/**
 * Create by Special on 2018/4/5 16:27
 */
public class ProEImprove3 {

    static int[] s = {-1, 1};
    static final int MIN = -1000;
    static final int MAX = 1000;

    public static void main(String[] args){
        FastScanner input = new FastScanner();
        PrintWriter out = new PrintWriter(System.out);
        String c = input.next();
        int n = input.nextInt();
        int[][][] dp1 = new int[c.length() + 1][n + 1][2];
        int[][][] dp2 = new int[c.length() + 1][n + 1][2];
        for(int i = 0; i <= c.length(); i++){
            for(int j = 0; j <= n; j++){
                for(int d = 0; d < 2; d++){
                    dp1[i][j][d] = MIN;
                    dp2[i][j][d] = MAX;
                }
            }
        }
        dp1[0][0][1] = dp2[0][0][1] = 0;
        dp1[0][0][0] = dp2[0][0][0] = 0;
        for(int i = 1; i <= c.length(); i++){
            for(int j = 0; j <= n; j++){
                for(int e = 0; e <= j; e++){
                    int t = j - e;
                    if((t & 1) == 0){
                        if(c.charAt(i - 1) == 'F') {
                            dp1[i][j][0] = Math.max(dp1[i - 1][e][0] - 1, dp1[i][j][0]);
                            dp1[i][j][1] = Math.max(dp1[i - 1][e][1] + 1, dp1[i][j][1]);
                            dp2[i][j][0] = Math.min(dp2[i - 1][e][0] - 1, dp2[i][j][0]);
                            dp2[i][j][1] = Math.min(dp2[i - 1][e][1] + 1, dp2[i][j][1]);
                        }else {
                            dp1[i][j][0] = Math.max(dp1[i - 1][e][1], dp1[i][j][0]);
                            dp1[i][j][1] = Math.max(dp1[i - 1][e][0], dp1[i][j][1]);
                            dp2[i][j][0] = Math.min(dp2[i - 1][e][1], dp2[i][j][0]);
                            dp2[i][j][1] = Math.min(dp2[i - 1][e][0], dp2[i][j][1]);
                        }
                    }else{
                        if(c.charAt(i - 1) == 'F') {
                            dp1[i][j][0] = Math.max(dp1[i - 1][e][1], dp1[i][j][0]);
                            dp1[i][j][1] = Math.max(dp1[i - 1][e][0], dp1[i][j][1]);
                            dp2[i][j][0] = Math.min(dp2[i - 1][e][1], dp2[i][j][0]);
                            dp2[i][j][1] = Math.min(dp2[i - 1][e][0], dp2[i][j][1]);
                        }else {
                            dp1[i][j][0] = Math.max(dp1[i - 1][e][0] + 1, dp1[i][j][0]);
                            dp1[i][j][1] = Math.max(dp1[i - 1][e][1] - 1, dp1[i][j][1]);
                            dp2[i][j][0] = Math.min(dp2[i - 1][e][0] - 1, dp2[i][j][0]);
                            dp2[i][j][1] = Math.min(dp2[i - 1][e][1] + 1, dp2[i][j][1]);
                        }
                    }
                }
            }
        }
        int max = 0;
        max = Math.max(Math.abs(dp1[c.length()][n][0]), max);
        max = Math.max(Math.abs(dp1[c.length()][n][1]), max);
        max = Math.max(Math.abs(dp2[c.length()][n][0]), max);
        max = Math.max(Math.abs(dp2[c.length()][n][1]), max);
        out.println(max);
        out.close();
    }
}
疑惑

此题我又想了很多,发现上述的dp有问题,考虑的情况则仅仅是命令数小于等于字符串本身情况,且没有自循环。意思是没有在一个命令重复执行。反驳上诉的答案最好的离职是:FF 2, 正确答案应该是2,但是上述却给的是0.
下面的代码是正确的(充分考虑自循环):

package com.special.test14;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;

/**
 * Create by Special on 2018/4/5 14:22
 */
public class ProEImprove2 {

    static int[] s = {-1, 1};

    public static void main(String[] args){
        FastScanner input = new FastScanner();
        PrintWriter out = new PrintWriter(System.out);
        String c = input.next();
        int n = input.nextInt();
        int dis = (c.length() + 1) << 1;
        boolean[][][][] map = new boolean[c.length() + 1][n + 1][dis + 5][2];
        map[0][0][c.length()][1] = true;
        for(int i = 1; i <= c.length(); i++){
            for(int j = 0; j <= n; j++){
                for(int e = 0; e <= j; e++) { //前i- 1执行命令的次数
                    for (int k = 1; k <= dis - 1; k++) {
                        for (int d = 0; d < 2; d++) {
                            int t = j - e;
                            if ((t & 1) == 0) {
                                if (c.charAt(i - 1) == 'F') {
                                    map[i][j][k + s[d]][d] |= map[i - 1][e][k][d];
                                } else {
                                    map[i][j][k][d ^ 1] |= map[i - 1][e][k][d];
                                }
                            }else {
                                if (c.charAt(i - 1) == 'F') {
                                    map[i][j][k][d ^ 1] |= map[i - 1][e][k][d];
                                } else {
                                    map[i][j][k + s[d]][d] |= map[i - 1][e][k][d];
                                }
                            }
                        }
                    }
                }
            }
        }
        int max = 0;
        for(int i = 0; i < 2; i++){
            for(int j = 0; j <= dis - 1; j++){
                if(map[c.length()][n][j][i]){
                    max = Math.max(max, Math.abs(j - c.length()));
                }
            }
        }
        out.println(max);
        out.close();
    }
}

D-幸运数字

链接:https://www.nowcoder.com/acm/contest/70/D
来源:牛客网

定义一个数字为幸运数字当且仅当它的所有数位都是4或者7。
比如说,47、744、4都是幸运数字而5、17、467都不是。
现在想知道在1…n的第k小的排列(permutation,https://en.wikipedia.org/wiki/Permutation)中,有多少个幸运数字所在的位置的序号也是幸运数字。

输入描述:

第一行两个整数n,k。
1 <= n,k <= 1000,000,000

输出描述:

一个数字表示答案。
如果n没有k个排列,输出-1。

解析

第10亿大的排列属于13的全排列的范围,故最大只考虑后13位即可。然后对于前面,值跟序号是一样的值,因为未曾改变,所以我们dfs一下求出所有的幸运数字即可。后段的第K大的排列形式则用康托逆展开来做即可。
康托展开与逆展开,参考:https://blog.csdn.net/dawn_after_dark/article/details/79830523

本题考查了dfs + 阶乘 + 康托逆展开

代码
package com.special.test14;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;

/**
 * 康托展开与康托逆展开
 * Create by Special on 2018/4/3 9:49
 */
public class ProD {

    static final int MAX = 1000000000;
    static long[] fac = new long[15];
    static int limit;
    static List<Long> lists = new ArrayList<>();

    static void dfs(long num){
        lists.add(num);
        if(num < 1000000000){
            dfs(num * 10 + 4);
            dfs(num * 10 + 7);
        }
    }

    static void init(){
        fac[0] = 1;
        for(int i = 1; i < 15; i++){
            long temp = fac[i - 1] * i;
            fac[i] = temp;
            if(temp > MAX){
                limit = i;
                break;
            }
        }
    }

    static boolean isValid(int num){
        String str = String.valueOf(num);
        for(int i = 0; i < str.length(); i++){
            if(str.charAt(i) != '4' && str.charAt(i) != '7'){
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args){
        Scanner input = new Scanner();
        PrintWriter out = new PrintWriter(System.out);
        init();
        dfs(0);
        Collections.sort(lists);

        int n = input.nextInt();
        int k = input.nextInt();
        if(n <= limit && k > fac[n]){
            out.println(-1);
        }else{
            int begin = n > limit ? n - limit + 1 : 1;
            int length = Math.min(n, limit);
            int[] nums = new int[length];
            boolean[] vis = new boolean[length];
            k--;
            int div, num;
            //以下为康托逆展开
            for(int i = 0; i < length; i++){
                div = k / (int) fac[length - i - 1];
                for(num = 0; num < length; num++){
                    if(!vis[num]){
                        if(div == 0){
                            break;
                        }
                        div--;
                    }
                }
                vis[num] = true;
                nums[i] = num + begin;
                k %= (int) fac[length - i - 1];
            }
            int sum = 0;
            for(int i = 1; i < lists.size(); i++){
                long item = lists.get(i);
                if(item >= begin){
                    break;
                }
                sum++;
            }
            for(int i = 0; i < length; i++){
                if(isValid(begin + i) && isValid(nums[i])){
                    sum++;
                }
            }
            out.println(sum);
        }
        out.close();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值