@LittleRewriter
2018-07-15T14:09:06.000000Z
字数 10405
阅读 808
搜索
本节和lyd的顺序略错位。
深搜是一类包括遍历形式、状态记录与检索、剪枝优化等算法整体设计的统称。
深搜会形成一颗搜索树。我们将问题空间中的状态抽象成一个点,将访问两个状态的移动抽象为一条边,形成的树是搜索树。
整个深搜算法基于搜索树完成,为了避免重复访问,我们对状态进行记录检索;为了使程序更加高效,我们进行剪枝。
...第一反应是状压DP我还有救么
状态是第now只猫使用了cnt架缆车,暴力枚举即可。
最优化剪枝是显然的;至于为什么先要从大到小排序?这样可以减少枚举次数。
#include <iostream>#include <cstdio>#include <algorithm>using namespace std;int cat[20], lc[20], W, n, ans;bool cmp(int a, int b) {return a > b;}void dfs(int cnt, int now) {if(cnt >= ans) return;if(now > n) {ans = min(ans, cnt);return;}for(int i = 1; i <= cnt; ++i) {if(lc[i] + cat[now] <= W) {lc[i] += cat[now];dfs(cnt, now + 1);lc[i] -= cat[now];}}lc[cnt + 1] += cat[now];dfs(cnt + 1, now + 1);lc[cnt + 1] -= cat[now];}int main() {scanf("%d%d", &n, &W);for(int i = 1; i <= n; ++i) scanf("%d", &cat[i]);ans = n;sort(cat + 1, cat + 1 + n, cmp);dfs(0, 1);printf("%d", ans);return 0;}
luogu1784数独、POJ2676、POJ3074、luogu1074靶形数独、POJ3076
数据难度是递增的。。这次我们将数独型搜索的重点放在优化剪枝上。所以我先贴一下我好多年以前开O2才A掉的朴素靶形数独。
#include <iostream>#include <cstdio>#include <algorithm>#include <cctype>using namespace std;int map[11][11];int tensu[10][10] ={{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},{0, 6, 6, 6, 6, 6, 6, 6, 6, 6},{0, 6, 7, 7, 7, 7, 7, 7, 7, 6},{0, 6, 7, 8, 8, 8, 8, 8, 7, 6},{0, 6, 7, 8, 9, 9, 9, 8, 7, 6},{0, 6, 7, 8, 9, 10,9, 8, 7, 6},{0, 6, 7, 8, 9, 9, 9, 8, 7, 6},{0, 6, 7, 8, 8, 8, 8, 8, 7, 6},{0, 6, 7, 7, 7, 7, 7, 7, 7, 6},{0, 6, 6, 6, 6, 6, 6, 6, 6, 6}};int dijige[10][10] ={{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},{0, 1, 1, 1, 2, 2, 2, 3, 3, 3},{0, 1, 1, 1, 2, 2, 2, 3, 3, 3},{0, 1, 1, 1, 2, 2, 2, 3, 3, 3},{0, 4, 4, 4, 5, 5, 5, 6, 6, 6},{0, 4, 4, 4, 5, 5, 5, 6, 6, 6},{0, 4, 4, 4, 5, 5, 5, 6, 6, 6},{0, 7, 7, 7, 8, 8, 8, 9, 9, 9},{0, 7, 7, 7, 8, 8, 8, 9, 9, 9},{0, 7, 7, 7, 8, 8, 8, 9, 9, 9}};bool geneivis[11][11],hangvis[11][11], lievis[11][11];inline void read(int& x) {x = 0; char c = getchar();while(!isdigit(c)) c = getchar();while(isdigit(c)) x = x * 10 + c - '0', c = getchar();}int xx, yy, maxn = -1, ori;void printmap() {for(int i = 1; i <= 9; ++i) {for(int j = 1; j <= 9; ++j) {printf("%d", map[i][j]);}printf("\n");}printf("\n");}void printvis() {printmap();for(int i = 1; i <= 9; ++i) {for(int j = 1; j <= 9; ++j) {cout<<hangvis[i][j]<<" ";}cout<<endl;}cout<<endl;for(int i = 1; i <= 9; ++i) {for(int j = 1; j <= 9; ++j) {cout<<hangvis[i][j]<<" ";}cout<<endl;}cout<<endl;for(int i = 1; i <= 9; ++i) {for(int j = 1; j <= 9; ++j) {cout<<geneivis[i][j]<<" ";}cout<<endl;}cout<<endl;}inline void dfs(register int x, register int y, register int ans, register int mark) {if(ans + mark * 90 <= maxn) return;//cout<<x<<" "<<y<<" "<<ans<<endl;//if(x < 1 || y < 1 || x > 9 || y > 9) return;if(x == 0) {//printmap();maxn = max(ans, maxn);return;}if(!map[x][y]){for(register int i = 9; i >= 1; --i) {//if(xx < 1 || yy < 1 || xx > 9 || yy > 9) continue;//if(x == 7 && y == 9)printvis();if(!hangvis[x][i] && !lievis[y][i] && !geneivis[dijige[x][y]][i]) {hangvis[x][i] = 1, lievis[y][i] = 1, geneivis[dijige[x][y]][i] = 1;map[x][y] = i;//cout<<"i:"<<i<<" "<<x<<" "<<y<<endl;//printmap();if(y > 1) dfs(x, y-1, ans + tensu[x][y] * i, mark - 1);else dfs(x-1, 9, ans + tensu[x][y] * i, mark - 1);hangvis[x][i] = 0, lievis[y][i] = 0, geneivis[dijige[x][y]][i] = 0;map[x][y] = 0;}}}else {if(y > 1) dfs(x, y-1, ans + tensu[x][y] * map[x][y], mark - 1);else dfs(x-1, 9, ans + tensu[x][y] * map[x][y], mark - 1);}return;}int main() {//freopen("test.txt", "w", stdout);for(int i = 1; i <= 9; ++i) {for(int j = 1; j <= 9; ++j) {read(map[i][j]);}}for(int i = 1; i <= 9; ++i) {for(int j = 1; j <= 9; ++j) {if(map[i][j]) {hangvis[i][map[i][j]] = 1;lievis[j][map[i][j]] = 1;geneivis[dijige[i][j]][map[i][j]] = 1;}}}dfs(9, 9, 0, 81);printf("%d\n", maxn);return 0;}
...是不是觉得非常壮观。这道题非常的经典,因此我联赛前专门做了它锻炼码力,虽说毫无卵用。
这道题有一个玄学优化:就是倒着搜。这样做的原因是出题人会专门卡正着搜;但是即便如此,除非结合卡时,否则还是会T在11点。即便开了O2,11点还是跑了956ms。可见非常玄学。
所以我们迫切的需要一些不那么玄学的剪枝来A掉。当然,有种数据结构叫做Dancing links可以解决这类问题;但是我个人是很讨厌数据结构而且这个东西不好学,所以我们不做讨论。
首先,我们应当有一个原则,就是从可能性最小的分支开始搜索。我们可以在一开始就扫一遍,把能填上的先填上;然后考虑每一行、每一列、每个九宫格,看看能不能填。
其次,对于某些点,我们可以用位运算优化。将行列九宫格的数用九位二进制数保存,这样的话按位与之后得到的结果就是可填的东西,枚举起来非常方便。
然后luogu那道题我们就可以这么0msA掉了
#include <iostream>#include <cstdio>#include <algorithm>using namespace std;#define ri register intint belong[9][9] = {{0, 0, 0, 1, 1, 1, 2, 2, 2},{0, 0, 0, 1, 1, 1, 2, 2, 2},{0, 0, 0, 1, 1, 1, 2, 2, 2},{3, 3, 3, 4, 4, 4, 5, 5, 5},{3, 3, 3, 4, 4, 4, 5, 5, 5},{3, 3, 3, 4, 4, 4, 5, 5, 5},{6, 6, 6, 7, 7, 7, 8, 8, 8},{6, 6, 6, 7, 7, 7, 8, 8, 8},{6, 6, 6, 7, 7, 7, 8, 8, 8}};int sodoku[10][10], row[10], col[10], gri[10], tot;int cnt[1 << 10], num[1 << 10];inline void cha(int x, int y, int z) {row[x] ^= 1 << z;col[y] ^= 1 << z;gri[ belong[x][y] ] ^= 1 << z;}inline bool dfs(int now) {if(!now) return 1;ri val, tag = 10; int x, y; //tag表示可能性,可能性不可能超过10个for(ri i = 0; i < 9; ++i) {for(ri j = 0; j < 9; ++j) { //选取可能性最低的点//cout<<i<<" "<<j<<" "<<sodoku[i][j]<<endl;if(sodoku[i][j] != 0) continue;val = row[i] & col[j] & gri[ belong[i][j] ];//if(now == 60) cout<<i<<" "<<j<<" "<<row[i]<<" "<<col[j]<<" "<<belong[i][j]<<" "<<gri[ belong[i][j] ]<<endl;if(!val) return 0; //如果某一个点不能放,该决策错误if(cnt[val] < tag) {tag = cnt[val];x = i, y = j;}}}val = row[x] & col[y] & gri[ belong[x][y] ];ri tmp;for(int qwq = val; qwq; qwq -= qwq & -qwq) {tmp = num[qwq & -qwq];sodoku[x][y] = tmp + 1;cha(x, y, tmp);if(dfs(now - 1)) return 1;cha(x, y, tmp);sodoku[x][y] = 0;}return 0;}int main() {for(int i = 0; i < 9; ++i)row[i] = col[i] = gri[i] = (1 << 9) - 1,num[1 << i] = i;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)for(int j = 0; j < 9; ++j)cin>>sodoku[i][j];for(int i = 0; i < 9; ++i) {for(int j = 0; j < 9; ++j) {if(sodoku[i][j]) cha(i, j, sodoku[i][j] - 1);else ++tot;}}/*for(int i = 0; i < (1 << 9); ++i) cout<<cnt[i]<<" ";puts("");for(int i = 0; i < (1 << 9); ++i) cout<<num[i]<<" ";puts("");for(int i = 0; i < 9; ++i) cout<<row[i]<<" ";puts("");for(int i = 0; i < 9; ++i) cout<<col[i]<<" ";puts("");for(int i = 0; i < 9; ++i) cout<<gri[i]<<" ";puts("");*/dfs(tot);for(int i = 0; i < 9; ++i) {for(int j = 0; j < 9; ++j)cout<<sodoku[i][j]<<" ";cout<<endl;}return 0;}
。。至于POJ3076什么的无能为力我直接扔lyd写了6k的代码了
#include <stdio.h>#define EMPTY -1char map[16][16];unsigned short table[16][16];int filled;void fill (int i, int j, int a){int r, c, k, m;filled++;map[i][j] = a;for (k = 0; k < 16; k++)table[i][k] |= (1 << (a - 1)), table[k][j] |= (1 << (a - 1));r = (int)(i / 4) * 4;c = (int)(j / 4) * 4;for (k = 0; k < 4; k++){for (m = 0; m < 4; m++)table[r + k][c + m] |= (1 << (a - 1));}return;}int search (){char t_map[16][16];unsigned short t_table[16][16]; int t_filled;int i, j, k, m, r, c, r1, r2, t0, t, flag;if (filled == 256) return 1;for (i = 0; i < 16; i++){for (j = 0; j < 16; j++){if (map[i][j]) continue;r = -1;for (k = 0; k < 16; k++){if (((table[i][j] & (1 << k)) == 0) && r == -1) r = k;else if (((table[i][j] & (1 << k)) == 0) && r != -1) { r = -2; break; }}if (r == -1) return 0;else if (r != -2) fill(i, j, r + 1);}}for (i = 0; i < 16; i++){for (k = 0; k < 16; k++){r = -1;for (j = 0; j < 16; j++){if (map[i][j] == k + 1) { r = -2; break; }if (map[i][j]) continue;if (((table[i][j] & (1 << k)) == 0) && r == -1) r = j;else if (((table[i][j] & (1 << k)) == 0) && r != -1) { r = -2; break; }}if (r == -1) return 0;else if (r != -2) fill(i, r, k + 1);}}for (j = 0; j < 16; j++){for (k = 0; k < 16; k++){r = -1;for (i = 0; i < 16; i++){if (map[i][j] == k + 1) { r = -2; break; }if (map[i][j]) continue;if (((table[i][j] & (1 << k)) == 0) && r == -1) r = i;else if (((table[i][j] & (1 << k)) == 0) && r != -1) { r = -2; break; }}if (r == -1) return 0;else if (r != -2) fill(r, j, k + 1);}}for (r = 0; r < 16; r += 4){for (c = 0; c < 16; c += 4){for (k = 0; k < 16; k++){r1 = -1, r2 = -1;for (i = 0; i < 4; i++){for (j = 0; j < 4; j++){if (map[r + i][c + j] == k + 1) { r1 = r2 = -2; goto outerloop1; }if (map[r + i][c + j]) continue;if (((table[r + i][c + j] & (1 << k)) == 0) && r1 == -1) r1 = r + i, r2 = c + j;else if (((table[r + i][c + j] & (1 << k)) == 0) && r1 != -1){ r1 = r2 = -2; goto outerloop1; }}}outerloop1:if (r1 == -1) return 0;else if (r1 != -2) fill(r1, r2, k + 1);}}}if (filled == 256) return 1;for (i = 0; i < 16; i++){for (j = 0; j < 16; j++)t_map[i][j] = map[i][j];}for (i = 0; i < 16; i++){for (j = 0; j < 16; j++)t_table[i][j] = table[i][j];}t_filled = filled;t0 = 256;for (i = 15; i >= 0; i--){for (j = 15; j >= 0; j--){if (map[i][j]) continue;t = 0;for (k = 0; k < 16; k++){if ((table[i][j] & (1 << k)) == 0) t++;if (t >= t0) break;}if (t < t0){t0 = t;r1 = i, r2 = j;}}}outerloop2:for (m = 0; m < 16; m++){if ((table[r1][r2] & (1 << m)) == 0){fill(r1, r2, m + 1);flag = search();if (flag == 1) return 1;else{for (i = 0; i < 16; i++){for (j = 0; j < 16; j++)map[i][j] = t_map[i][j];}for (i = 0; i < 16; i++){for (j = 0; j < 16; j++)table[i][j] = t_table[i][j];}filled = t_filled;}}}return 0;}int main (){int i, j, k, m, r, c, a;char ar[60];while (1){memset(map, 0, sizeof(map));memset(table, 0, sizeof(table));filled = 0;for (i = 0; i < 16; i++){if (scanf("%s", ar) != 1) goto end;for (j = 0; j < 16; j++){a = ar[j];if (a == '-') continue;else fill(i, j, a - 64);}}search();for (i = 0; i < 16; i++){for (j = 0; j < 16; j++)printf("%c", map[i][j] + 64);printf("\n");} printf("\n");}end: return 0;}
有六种常用剪枝方法。
第一种是卡时。卡时对最优化的深搜问题的非常有效的。
第二种是优化搜索顺序。我们可以去搜索玄学意义上更好的分支来达到效果;一般来说,可能性越小的分支应该越早的搜索。
第三章是排除等效冗余。下面会见到,一般来说对于重复子问题可以说这是很好用的。
第四种是可行性剪枝。如果说一个分支届到了边界,或者是不可能到达边界,我们就直接剪掉。比如刚才我们在数独型搜索中剪的那个。
第五种是最优性剪枝。比如在靶形数独的时候,我们搜到一个状态大于当前的最小状态,我们可以直接扔掉。
最后一种是记忆化。
我们考虑这样一个状态设计。设木棒长度为k,那么,换言之我们的枚举范围是sum的所有约数。
我们的搜索状态是三维的代表第now个容器在对第wit进行搜寻,并且已经放了cap个。
先排序。
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <vector>using namespace std;int n, stick[80], sum, mas;vector<int> v;int cer[80];bool vis[80];bool cmp(int a, int b) {return a > b;}bool dfs(int val, int cap, int wit, int now) {//if(val == 227) cout<<cap<<" "<<wit<<" "<<now<<endl;if(now > (sum / val)) {return true;}if(cap == val) return dfs(val, 0, 1, now + 1);int fail = 0;for(int i = wit; i <= n; ++i) { /*第一处*/if(!vis[i] && cap + stick[i] <= val && stick[i] != fail) {vis[i] = 1;if(dfs(val, cap + stick[i], i + 1, now))return true;fail = stick[i]; /*第二处*/vis[i] = 0;if(cap == 0) return false; /*第三处*/}}return false;}int main() {//freopen("biao.txt", "w", stdout);while(cin>>n) {if(!n) break; sum = 0; mas = 0;for(int i = 1; i <= n; ++i)scanf("%d", &stick[i]), sum += stick[i], mas = max(mas, stick[i]);sort(stick + 1, stick + 1 + n, cmp);//for(int i = 1; i <= n; ++i) cout<<stick[i]<<" ";int ans;for(int i = mas; i <= sum; ++i) {//cout<<i<<" ";if(sum % i) continue;memset(vis, 0, sizeof vis);if(dfs(i, 0, 1, 1)) {ans = i;break;}}cout<<ans<<endl;}}
关注我专门圈住的三处。
第一处的含义是,对于递减排列的棍子来说,某一个格不能放就代表这个格以前的都不能放。
第二处的含义是,如果在搜索某一状态时有相同的a,b两个木棍且a不能放那么b一定不能放。
第三处的含义是,如果一个也放不进去,就说明该状态一定会出锅,直接减掉。
很棒的搜索题。
首先,我们从下往上倒序递归。
当然有个结论:所有的表面积=最下层蛋糕的表面积+所有层侧面积。
我们制定状态表示在搜索dep层时候的外表面积为S、体积为V。那么我们可以发现:
1,搜索范围
对于当前状态而言,首先搜索范围一定是
,
考虑到对于当前状态有,显然有,,然后就得到了第一个剪枝。
注意这里搜索应该倒序搜索。
2,可行性剪枝
假定对于第层,我们预处理一个最小的表面积和体积,维护h与r均取时前dep层达到的最小值。如果就代表不可行。
3,最优性剪枝
这个略复杂...
首先如果已经比搜到的答案大我们直接剪枝。
考虑公式。我们知道从1~dep - 1层的体积有,表面积为
我们稍微变形一下。考虑到
因此我们直接比较与当前最优解即可。
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <cmath>#include <vector>using namespace std;typedef long long LL;const LL INF = 0x3f3f3f;LL smin[18], vmin[18];LL pr[18], ph[18];LL N, M, ans = INF;void preope() {for(int i = 1; i <= 16; ++i)smin[i] = smin[i - 1] + 2 * i * i,vmin[i] = vmin[i - 1] + i * i * i;pr[M + 1] = ph[M + 1] = INF;}void dfs(LL cnt, LL V, LL S) {if(cnt < 1) {if(V == N) ans = min(ans, S);return;}if(vmin[cnt] + V > N) return;if(S + smin[cnt] > ans) return;if(2 * (N - V) / pr[cnt + 1] + S > ans) return;for(LL r = min((LL)sqrt(1.0 * N - V), pr[cnt + 1] - 1) ; r >= cnt; --r) {for(LL h = min((N - V - vmin[cnt - 1]) / r / r, ph[cnt + 1] - 1); h >= cnt; --h) {pr[cnt] = r, ph[cnt] = h;if(cnt == M) S = r * r;dfs(cnt - 1, V + r * r * h, S + 2 * r * h);}}}int main() {cin>>N>>M;preope();dfs(M, 0, 0);if(ans == INF) return puts("0"), 0;cout<<ans<<endl;return 0;}