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();
}
}