洛谷P1074 靶形数独 Java解法

题目点这里
在这里插入图片描述
思路:我选择一行一行搜,搜到每一行的最后一列就到下一行
原则:①能优化就优化 ②能剪枝就剪枝③少用循环~~呜呜呜~
注意:因为我们用的是Java,所以要务必小心随时TLE的风险~
1、玩过数独的都知道从数字多的那一行开始填
2、尽量别用for循环来求和,判断行列宫是否重复啥的
3、求和可以打表,虽然还是要for循环,但少了判断也是极好的
4、可以用布尔数组hang[i][shuzi]表示第i行是否有shuzi,列同理,宫我实在懒得想
5、如果你用for循环判断是否行列重复,极大可能会T(我就T了),用O(1)不香吗
6、代码简单易懂

package search;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

class pa {
	int no;
	int co;

	public pa(int no, int co) {
		this.no = no;
		this.co = co;
	}

}

public class P1074 {
	static int map[][] = new int[9][9], max = -1, mxx, s, pai[] = new int[10],
			score[][] = {
				     { 6, 6, 6, 6, 6, 6, 6, 6, 6 }, 
			    	{ 6, 7, 7, 7, 7, 7, 7, 7, 6 },
					 { 6, 7, 8, 8, 8, 8, 8, 7, 6 },
					{ 6, 7, 8, 9, 9, 9, 8, 7, 6 }, 
					{ 6, 7, 8, 9, 10, 9, 8, 7, 6 }, 
					{ 6, 7, 8, 9, 9, 9, 8, 7, 6 },
					{ 6, 7, 8, 8, 8, 8, 8, 7, 6 },
					 { 6, 7, 7, 7, 7, 7, 7, 7, 6 },
					  { 6, 6, 6, 6, 6, 6, 6, 6, 6 } },
			hang[][] = new int[9][9], lie[][] = new int[9][9];
	static ArrayList<pa> list = new ArrayList<pa>();

	public static void main(String[] args) throws IOException {
		StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
		for (int i = 0; i < 9; i++) {
			int xx = 0;
			for (int j = 0; j < 9; j++) {
				st.nextToken();
				int temp = (int) st.nval;
				if (temp != 0) {
					hang[i][temp - 1] = 1;//代表第i行已经有数字temp
					lie[j][temp - 1] = 1;//代表第i列已经有数字temp
				}
				map[i][j] = temp;
				if (temp > 0) {
					xx++;
				}
			}
			list.add(new pa(i, xx));
		}
		Collections.sort(list, new Comparator<pa>() {//按每一行非零个数从大到小排序
			@Override
			public int compare(pa o1, pa o2) {
				// TODO Auto-generated method stub
				return o2.co - o1.co;// 降序排列
			}
		});
		for (int i = 0; i < 9; i++) {//存数组 存数组
			pai[i] = list.get(i).no;//pai[]大小为10是为了防止越界报错
		}
		list.clear();//清掉免得占空间,不清也可以没影响
		dfs(pai[0], 0, 0, 0);
		System.out.println(max);

	}

	// dfs
	/**
	 * 
	 * @param x   横坐标
	 * @param y   纵坐标
	 * @param num 填了几个数
	 */
	public static void dfs(int x, int y, int num, int index) {
		if (num == 81) {// 填满了
			max = Math.max(max, calc(map));//取最大
			return;
		}
		if (x == -1) {//防止越界
			dfs(pai[index + 1], 0, num, index + 1);
			return;
		}
		// 一行一行的填
		if (map[x][y] == 0) {// 如果这一点没有值
			for (int i = 1; i <= 9; ++i) {// 开始填数
				if (isRepeated(x, y, i)) {// 没重复
					if (y + 1 < 9) {// 如果不是最后一列
						map[x][y] = i;
						hang[x][i-1] = 1;//现在第x行有了数字i
						lie[y][i-1] = 1;//现在第y列有了数字i
						dfs(x, y + 1, num + 1, index);// 接着填下一列
						map[x][y] = 0;// 回溯
						hang[x][i-1] = 0;//现在第x行没有了数字i
						lie[y][i-1] = 0;//现在第y列没有了数字i
					} else {// 如果是最后一列
						map[x][y] = i;
						hang[x][i-1] = 1;//现在第x行有了数字i
						lie[y][i-1] = 1;//现在第y列有了数字i
						dfs(pai[index + 1], 0, num + 1, index + 1);// 接着填下一行
						map[x][y] = 0;// 回溯
						hang[x][i-1] = 0;//现在第x行没有了数字i
						lie[y][i-1] = 0;//现在第y列没有了数字i
					}

				}
			}
		} else {// 如果这一点有值
			if (y + 1 < 9) {// 如果不是最后一列
				dfs(x, y + 1, num + 1, index);
			} else {// 如果是最后一列
				dfs(pai[index + 1], 0, num + 1, index + 1);
			}
		}
	}

	// 是否重复
	// true代表没重复,false代表重复
	public static boolean isRepeated(int x, int y, int c) {
		//呜呜呜不要写循环
//		for (int i = 0; i < 9; i++) {
//			if (map[i][y] == c) {// 判断一列是否重复
//				return false;
//			}
//			if (map[x][i] == c) {// 判断一行是否重复
//				return false;
//			}
//		}
		//尽量写O(1)
		if(hang[x][c-1] != 0) {//第x行有数字c
			return false;
		}
		if (lie[y][c-1] != 0) {//第y列有数字c
			return false;
		}
		// 判断所在方格是否重复
		//其实判断九宫格是否重复也可以用数组,但我不会!!!
		int xx = x / 3;// 行
		int yy = y / 3;// 列
		for (int i = 3 * xx; i <= 3 * xx + 2; i++) {
			for (int j = 3 * yy; j <= 3 * yy + 2; j++) {
				if (map[i][j] == c) {
					return false;
				}
			}
		}
		return true;
	}
	//计算和
	public static int calc(int[][] arr) {
		int sum = 0;
		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) {
				sum = sum + arr[i][j] * score[i][j];
			}
		}
		return sum;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值