注:下述题意都来自AcWing
poj 1011 Sticks
http://poj.org/problem?id=1011
题意:
一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过50个长度单位。
把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。
请你设计一个程序,计算木棒的可能最小长度。
因为答案没有二分的性质,而需要我们计算最小长度,因此可以从短到长逐个长度判断是否可行
剪枝方案:
1.优化搜索顺序: 将所有木棒降序排列,先选择长的来拼
2.排除等效冗余:假如拼入该长度的木棍失败,则其他相同长度的木棒不需要拼了
(然后后面的我没想到)
3.假如当前的木棒拼入最后一根木棍之后就被拼接完整,又发现接下来的递归失败,则不需要为次木棒拼入更短的木棍。
因为: 用较长的木棍拼好一根木棒,就会剩下更多较短的木棍,而较短的木棍更有可能在接下来的递归中成功。所以较长的木棍失败之后就不需要试更短的木棍了
4.假如当前拼入的是第一根木棍,并且递归返回失败,则不需要往下拼了。
因为:当前的木棍失败了,放到以后拼也一样会失败,因为放到以后拼也一样,是等效的
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#define ll long long
#define ull unsigned long long
using namespace std;
const int maxn = 100;
int num[maxn], vis[maxn];
int n, len, aim;
bool cmp(int a, int b) {
return a > b;
}
bool dfs(int hav,int now) { //hav:当前拼好多少根木棍 now:当前这根的长度
if (hav == aim) {
return 1;
}
int no = 0;
for (int i = 1; i <= n; i++) {
if (vis[i]) continue;
if (now + num[i] > len) continue;
if (num[i] == no) continue; //不需要选择一样长度的木棍来拼
vis[i] = 1;
if (now + num[i] == len) { //最后一根
if (dfs(hav + 1, 0)) {
return 1;
}
else {
vis[i] = 0;//!
return 0;
}
}
else {
if (now == 0) { //第一根
if (dfs(hav, num[i]))
return 1;
else {
vis[i] = 0;
return 0;
}
}
else {
if (dfs(hav, num[i] + now))
return 1;
else no = num[i];
}
}
vis[i] = 0;
}
return 0;
}
int main() {
while (cin >> n, n) {
int mx = 0, sum = 0;
for (int i = 1; i <= n; i++){
scanf("%d", num + i);
mx = max(mx, num[i]);
sum += num[i];
}
sort(num + 1, num + 1 + n, cmp);
for (len = mx; len <= sum; len++) {
if (sum%len != 0) continue;
aim = sum / len;
memset(vis, 0, sizeof(vis));
if (dfs(0, 0))
break;
}
printf("%d\n", len);
}
}
poj 2248 Addition Chains
http://poj.org/problem?id=2248
迭代加深搜索。。
对于第i位数,它的值的范围从i~n,最多能填100个数,每个数都需要经过搜索,那么搜索树最深会有100层,这是不可接受的
可以大概判断答案所在的层数比较浅,比如n=128 , 序列可以为: 1,2,4,8,16,32,64,128 ,只要8层,由此猜测答案所在层数较浅,搜索树深+答案浅,这个时候可以使用迭代加深。
预估队列长度从1开始,假如递归深度超过预估的队列长度,则返回,若是无解,则预估队列长度+1.
从题目要求来看,找到长度m最小的序列,此时也在暗示从小到大预估队列长度。
搜索优化:
1.搜索顺序优化: 每试填一个数的时候,从已经填过的数中找两个数相加试填,为了能更快逼近n,每次找的数应该尽可能大,也就是从大到小试填
2.在找两个数试填的时候,有可能出现重复填的情况,比如 1 2 3 4 , 1+4=2+3,此时会重复填,需要避免这种情况
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<iostream>
#define ll long long
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 105;
int num[maxn], vis[maxn][maxn];
//vis数组:防止重复填同一个数,没有这个会tle
int n, flag;
bool dfs(int cnt) {
memset(vis[cnt], 0, sizeof(vis[cnt]));
if (cnt == flag) {
num[cnt] = n;
int ok = 0;
for (int i = cnt - 1; i; i--) {
for (int j = cnt - 1; j; j--) {
if (num[i] + num[j] == n) {
ok = 1;
break;
}
}
if (ok == 1) break;
}
if (ok == 1) return 1;
else return 0;
}
//优化搜索顺序
for (int i = cnt - 1; i; i--) {
for (int j = cnt - 1; j; j--) {
num[cnt] = num[i] + num[j];
//if (num[cnt] >= n) continue;
if (num[cnt] >= n || num[cnt]<=num[cnt-1]) continue;
if (vis[cnt][num[cnt]]) continue;
vis[cnt][num[cnt]] = 1;
if (dfs(cnt + 1))
return 1;
}
}
return 0;
}
int main() {
while (scanf("%d",&n),n) {
if (n == 1) {
cout << 1 << endl;
continue;
}
num[1] = 1;
for (flag = 2; flag <= n; flag++) {
if (dfs(2)) {
for (int i = 1; i <= flag; i++)
printf("%d ", num[i]);
printf("\n");
break;
}
}
}
return 0;
}
poj 3074 数独
http://poj.org/problem?id=3074
之前我试过用舞蹈链解决,速度比暴力搜索快很多,但是还是要懂搜索怎么做啊。。
搜索优化:
1.挑软柿子捏,每次从所有可填的地方找出可填可能性最小的开始填
2.常数优化: (不然会tle)
a.使用二进制保存可以填的数
b.通过数组预处理使得得到一个二进制就可以马上得出这个二进制有多少个位数是1,以及这个二进制代表的是哪个数
c.这题用string会tle。。。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#define ll long long
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 14;
int col[maxn], row[maxn], blo[maxn], ori[1500], hav[1500];
//string s; 用string会超时
char s[1500];
void init() {
for (int i = 0; i < 9; i++)
col[i] = row[i] = blo[i] = (1 << 9) - 1;
}
#define get_blo(i,j)(((i/3)*3)+(j/3))
#define lowbit(x) (x&-x)
void change(int i, int j, int val) {
row[i] ^= (1 << val);
col[j] ^= (1 << val);
blo[get_blo(i, j)] ^= (1 << val);
}
bool dfs(int lef) {
if (lef == 0)
return 1;
int minn = INF, xx, yy, sto;
for (int i = 0; i < 9; i++) //选软柿子
for (int j = 0; j < 9; j++)
if (s[i * 9 + j] == '.') {
sto = col[j] & row[i] & blo[get_blo(i, j)];
if (hav[sto] < minn) {
minn = hav[sto];
xx = i;
yy = j;
}
}
sto = col[yy] & row[xx] & blo[get_blo(xx, yy)];
while (sto) { //试填
int number = ori[lowbit(sto)];
sto -= lowbit(sto);
change(xx, yy, number);
s[xx * 9 + yy] = '1' + number;
if (dfs(lef - 1)) return 1;
change(xx, yy, number);
s[xx * 9 + yy] = '.';
}
return 0;
}
int main() {
for (int i = 0; i < (1 << 9); i++)
for (int j = i; j; j -= lowbit(j))
hav[i]++; //hav: 这个二进制有多少个1
for (int i = 0; i < 9; i++)
ori[1 << i] = i; //ori:得到一个二进制就要知道代表哪个数
while (cin >> s) {
if (s[0] == 'e') break;
init();
int cnt = 0;
for (int i = 0; i < 9; i++) {//保存每行,每列,每块中可以填的数
for (int j = 0; j < 9; j++) {
if (s[i * 9 + j] != '.') {
change(i, j, s[i * 9 + j] - '1');
}
else cnt++;
}
}
dfs(cnt);
cout << s << endl;
}
return 0;
}