搜索 题目

Sudoku(9*9)

link1:数据强度弱
link2:数据强度强

两个优化:

  1. 按照人类直觉,可以填的数越少的位置越先填
  2. 使用二进制数字记录一行、列、宫中哪些数字用过,不使用数组,常数优化
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std ;

typedef long long ll ;
const int N = 1000010 ;
const int M = (1 << 11) ;

bool flag ;
int n, T, tot ;
char s[20][20] ;
int row[20], col[20], blk[20] ; // block -> int
int belong[20][20] ;
int cnt[M], dig[M] ;

void pre() {
	for (int i = 0; i <= 2; i++)
	for (int j = 0; j <= 2; j++) {
		belong[i][j] = 0 ;
		belong[i][j + 3] = 1 ;
		belong[i][j + 6] = 2 ;
		belong[i + 3][j] = 3 ;
		belong[i + 3][j + 3] = 4 ;
		belong[i + 3][j + 6] = 5 ;
		belong[i + 6][j] = 6 ;
		belong[i + 6][j + 3] = 7 ;
		belong[i + 6][j + 6] = 8 ;
	}
	for (int i = 0; i <= (1 << 9); i++) 
	for (int j = i; j; j -= j & -j) cnt[i]++ ;
	for (int i = 0; i <= 9; i++) dig[1 << i] = i ;
	
}

void init() {
	tot = 0 ;
	for (int i = 0; i < n; i++) row[i] = col[i] = blk[i] = (1 << 9) - 1 ;
}

void flip(int x, int y, int z) {
	row[x] ^= (1 << z) ;
	col[y] ^= (1 << z) ;
	blk[belong[x][y]] ^= (1 << z) ;
}

int dfs(int step) {
	if (step == 0) {
		flag = true ;
		return 1 ;
	}
	int ans = (1 << 9) - 1, dx, dy ;
	for (int i = 0; i < 9; i++)
	for (int j = 0; j < 9; j++)
	if (s[i][j] == '0') {
		int t = row[i] & col[j] & blk[belong[i][j]] ;
		if (cnt[t] < cnt[ans]) {
			ans = t ;
			dx = i, dy = j ; 
		}
	}
	while (ans) {
		int t = ans & -ans ; ans -= ans & -ans ; t = dig[t] ;
		s[dx][dy] = t + '1' ;
		flip(dx, dy, t) ;
		if (dfs(step - 1)) return 1 ;
		s[dx][dy] = '0' ;
		flip(dx, dy, t) ;
	}
	return 0 ; 
}

int main() {
	n = 9 ;
	scanf("%d", &T) ;
	pre() ;
	while (T--) {
		init() ;
		for (int i = 0; i < n; i++) scanf("%s", s[i]) ;
		for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
		if (s[i][j] == '0') tot++ ;
		else flip(i, j, s[i][j] - '1') ;
		dfs(tot) ;
		for (int i = 0; i < n; i++) printf("%s\n" , s[i]) ;
	} 
}

Sticks

link

64根棒子 直接搜索时间复杂度太高,需要剪枝优化

  1. 搜索顺序:对于每个 \(stick\) 尽量先把大个的部分加进来,不行再用小的
  2. 冗余:(确保AB和BA不出现):
    2.1 记录上次棒子取出位置,只取比他小的不取大的(跟搜索)
    2.2 因为棒子长度可能相同,要记录上次失败时使用的棒子长度,再次枚举时不使用这个长度的
  3. 可行性剪枝 (确保失败的不找了)
    ·3.1 如果一次枚举是从空的开始的,而且失败了,那么直接放弃整个方案,因为这个失败棒放哪都是等效的,都注定失败
    ·3.2 如果一次枚举加入一根棒子,一段完成,但是之后的枚举失败了,同样放弃整个方案
    因为后面放的都是用较小的棒子拼起来塞进去,根据贪心,相比于多个小棒子,大棒子肯定更优
    大棒子都失败了,小棒子更不可能成功
  4. 最优性剪枝:不需要,只需要答案从小往大就行
  5. 记忆化:不需要,没有明确的状态可言
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std ;

typedef long long ll ;
const int N = 1000010 ;

int n, L, sum, val, cnt ;
int a[N], v[N] ;

bool cmp(int a, int b) {
	return a > b ;
}

void init() {
	sum = val = 0 ;
}

int dfs(int num, int len, int lst) {
	if (num > cnt) return 1 ;
	if (len == L) return dfs(num + 1, 0, 1) ;
	int fail = 0 ;
	for (int i = lst; i <= n; i++) 
	if (!v[i] && len + a[i] <= L && fail != a[i]) {
		v[i] = 1 ; 
		if (dfs(num, len + a[i], i + 1)) return true ;
		fail = a[i] ;
		v[i] = 0 ;
		if (len == 0 || len + a[i] == L) return false ;
	}
	return false; 
}

int main() {
	while(scanf("%d", &n) != EOF && n) {
		init() ;
		for (int i = 1; i <= n; i++) 
		scanf("%d", &a[i]), sum += a[i], val = max(val, a[i]) ;
		sort(a + 1, a + n + 1, cmp) ;
		for (L = val; L <= sum; L++) 
		if (sum % L == 0) {
			cnt = sum / L ;
			memset(v, 0, sizeof(v)) ;
			if (dfs(1, 0, 1)) {
				printf("%d\n", L) ;
				break ;
			}
		}
	}
}

posted @ 2023-09-09 15:40  哈奇莱特  阅读(63)  评论(0)    收藏  举报