暴力搜索初探

单纯的暴力搜索就不讲了。

技巧

1.1 折半搜索

这个东西有另外一个名称叫 M e e t   i n   t h e   m i d d l e \mathrm{Meet~in~the~middle} Meet in the middle

这个东西可以解决整个规模比搜索可以解决的规模大一倍的题目,这样我们可以将枚举分成前一部分和后一部分,然后将前一部分和后一部分的答案结合起来就可以了。当然了,为了将答案合起来,我们记住的东西一定要有类似结合率的东西(废话)

E x a m p l e   1 \mathrm{Example~1} Example 1 大容量背包问题

N N N 个物品,背包有 M M M 的容量,其中第 i i i 个物品的体积和价值分别为 w i , v i w_i, v_i wi,vi,求最大价值。

N ≤ 40 , w i ≤ M ≤ 1 0 9 N \leq 40, w_i \leq M \leq 10^9 N40,wiM109

可以发现,直接搜索是 2 40 2 ^ {40} 240 的,一定会超时,但是众所周知,背包是可以合并的,那么我们可以将物品分成前后两部分,每次 d f s \mathrm{dfs} dfs 时候记住当前已经选择了多少的体积,以及对应的价值。然后将两部分的答案存起来,然后用 T w o   p o i n t e r s \mathrm{Two~pointers} Two pointers 合并。

1.2 剪枝

这个东西最为玄妙,用得好的话可以拿到不错的暴力分。一般认为剪枝有两种,一种是可行性剪枝,另一种是最优化剪枝

可行性剪枝比较简单易懂,比如 E x a m p l e   1 \mathrm{Example~1} Example 1 中,当枚举到的总体积一定大于背包容量的时候,退掉这一部分的搜索。

最优化剪枝用下面的例子进行讲解。

E x a m p l e   2 \mathrm{Example~2} Example 2 [NOI1999]生日蛋糕

给定一个总体积 π N \pi N πN 和层数 M M M,构建一个蛋糕,每一层都是圆柱形,你需要规定由下往上看,每一层的半径 R i R_i Ri 和高度 H i H_i Hi,要求对于所有 1 ≤ i < m 1 \leq i < m 1i<m,都是 R i > R i + 1 , H i > H i + 1 R_i > R_{i+1}, H_i > H_{i+1} Ri>Ri+1,Hi>Hi+1

上述的所有数都是正整数。

现在要求除去底面积之后最小的表面积。

为了书写方便,将所有的 π \pi π 都略掉了,也就是将圆形底面换成正方形底面。

首先,现在要求的表面积等于侧面积 + + +上层面积,而上层面积等于底面积,所以直接在枚举最底层的时候加上底面积就好了。

最简单的暴力就是从下往上枚举每一层的高度和半径,但是这样只能拿 10 ~ 20 p t s 10~20\mathrm{pts} 1020pts

考虑剪枝:

首先,第 i i i 层可以枚举的半径就是: (其中 s V sV sV 表示之前已经枚举的所有层的总体积除以 π \pi π。)
[ M − i + 1 , min ⁡ { ⌊ N − s V ⌋ , R i − 1 − 1 } ] \left[ M-i+1, \min\left\{ \left\lfloor \sqrt{N-sV} \right\rfloor, R_{i-1}-1 \right \} \right] [Mi+1,min{ NsV ,Ri11}]

i i i 层可以枚举的高度也是差不多的:
[ M − i − 1 , min ⁡ { ⌊ N − s V R i 2 ⌋ , H i − 1 − 1 } ] \left[ M-i-1, \min \left\{ \left\lfloor \frac{N-sV}{R_i^2} \right\rfloor, H_{i-1}-1 \right\} \right] [Mi1,min{ Ri2NsV,Hi11}]
还有一个,当 s V > N sV > N sV>N ,也是可以直接剪枝的(废话)。

还有另一个可行性剪枝:当前面积 + + +之后可以得到的最大面积 < N <N,即 s S + R i 2 H i × ( M − i ) < N sS+R_i^2 H_i\times(M-i) < N sS+Ri2Hi×(Mi)<N,就可以退出了(之后所有层的体积都是小于 R i 2 H i R_i^2 H_i Ri2Hi 的)

接下来是最优化剪枝,这个东西有个套路:
当 前 的 答 案 + 之 后 的 最 优 答 案 ( 忽 略 某 些 限 制 条 件 ) ≤ 当 前 得 到 的 最 优 答 案 当前的答案+之后的最优答案(忽略某些限制条件) \leq 当前得到的最优答案 +()
如果不满足这个条件,就直接剪枝。

在这道题中,当前答案 + + +之后的最小侧面积 > > >已经得到的最小面积,就直接剪枝。

形式化讲 s S + M − i + 1 > a n s sS + M - i + 1 > ans sS+Mi+1>ans 就可以剪枝了(令之后的所有层都是半径和高度都是 1 1 1

同时可以利用之前枚举出来的 R i , H i R_i,H_i Ri,Hi,众所周知,当前总体积和总侧面积可以使用下面的公式求出来(废话):

s V = ∑ j = 1 i R j 2 H j s S = 2 × ∑ j = 1 i R j H j sV=\sum\limits_{j=1}^i R_j^2 H_j\\ sS=2\times \sum\limits_{j=1}^i R_j H_j sV=j=1iRj2HjsS=2×j=1iRjHj

那么对于 [ i + 1 , M ] [i+1,M] [i+1,M]的总体积和表面积,就有:

N − s V = ∑ j = i + 1 M R j 2 H j s S ′ = 2 × ∑ j = i + 1 M R j H j N-sV=\sum\limits_{j=i+1}^M R_j^2 H_j\\ sS'=2\times \sum\limits_{j=i+1}^M R_j H_j NsV=j=i+1MRj2HjsS=2×j=i+1MRjHj

那么可以得到:
s S ′ = 2 R i ∑ j = i + 1 M R j R i H j ≥ 2 R i ∑ j = i + 1 M R j 2 H j = 2 ( N − s V ) R i sS'=\frac{2}{R_i}\sum\limits_{j=i+1}^M R_j R_i H_j \ge \frac{2}{R_i}\sum\limits_{j=i+1}^M R_j^2 H_j =\frac{2(N-sV)}{R_i} sS=Ri2j=i+1MRjRiHjRi2j=i+1MRj2Hj=Ri2(NsV)

也就是说,当 s S + 2 ( N − s V ) R i > a n s sS + \frac{2(N - sV)}{R_i} > ans sS+Ri2(NsV)>ans ,那么就直接剪枝。

应用

f l o o d   f i l l \mathrm{flood~fill} flood fill 灌水法

这个东西的名字暗示了这篇 B l o g \mathrm{Blog} Blog的本质。

灌水法可以用于无向图联通块的信息统计,最直接的应用就是网格图的染色。

E x a m p l e   3 \mathrm{Example~3} Example 3 S i m p l e   T a s k \mathrm{Simple~Task} Simple Task

随便口胡一道题。

在一个 N × M N\times M N×M 的网格图中,有一些块是有颜色的,而有一些位置是障碍,现在需要求出至少包含 K K K 种颜色的最大联通块。

N , M ≤ 1 0 3 , 1 ≤ 颜 色 编 号 ≤ K ≤ 1 0 4 N,M\leq 10^3,1 \leq 颜色编号\leq K\leq 10^4 N,M103,1K104

可以发现,就是每次找到一个之前没有深搜到的不是障碍的点开始,然后向四周扩散,然后记住拓展了多少个联通块,如果存在一个这个联通块中没有的颜色,就颜色数加一。

但是每次向新的联通块中灌水之前似乎需要清空记住颜色的数组。但是如果使用 memset 就会 T L E \mathrm{TLE} TLE,如果使用 STL 就会带个 log ⁡ 2 \log_2 log2 (unordered_map N O I P \mathrm{NOIP} NOIP 中不建议使用),这时候可以使用比较没用的时间戳法,记住颜色数组中每一个位置最新一次修改的时间,比如这道题中可以使用当前 d f s \mathrm{dfs} dfs 第几个联通块作为时间点,当某个位置记录的时间点小于当前之间点,就先将这个位置清零,然后再改成新的值。

比较傻

const int maxk = 1e4 + 5;
const int vec[5][2] = {
     {
     0,0},{
     0,1},{
     0,-1},{
     1,0},{
     -1,0}};
int colfl[maxk], tchk[maxk]/*时间戳*/, ti/*总的时间点*/;
int cnt1, cnt2, col[maxn][maxn]/*颜色,如果col[i][j] == -1 就表示(i,j)是障碍*/

int ask_colfl(int pos) {
     
    if (tchk[pos] < ti) tchk[pos] = ti, colfl[pos] = 0;
    return colfl[pos];
}
void chg_colfl(int pos, int val) {
     
    colfl[pos] = val, tchk[pos] = ti;
}

void dfs(int x, int y) {
     
    cnt1++;
    if (!ask_colfl(col[x][y])) 
        chg_colfl(col[x][y], val), cnt2++;
    for (int i = 1; i <= 4; i++) {
     
        int nex_x = x + vec[i][0], nex_y = y + vec[i][1];
        if (!nex_x || !nex_y || nex_x > n || nex_y > m) continue;
        if (col[nex_x][nex_y] == -1) continue;
        dfs(nex_x, nex_y);
    }
}
//main()中的代码
int ans = 0;
for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++)
        if (!vis[i][j] && col[i][j] == -1) {
     
            ti++, cnt1 = cnt2 = 0, dfs(i, j);
            if (cnt2 >= k) ans = max(ans, cnt1);
        }

你可能感兴趣的:(算法,玄学)