单纯的暴力搜索就不讲了。
这个东西有另外一个名称叫 M e e t i n t h e m i d d l e \mathrm{Meet~in~the~middle} Meet in the middle 。
这个东西可以解决整个规模比搜索可以解决的规模大一倍的题目,这样我们可以将枚举分成前一部分和后一部分,然后将前一部分和后一部分的答案结合起来就可以了。当然了,为了将答案合起来,我们记住的东西一定要有类似结合率的东西(废话)
有 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 N≤40,wi≤M≤109
可以发现,直接搜索是 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 合并。
这个东西最为玄妙,用得好的话可以拿到不错的暴力分。一般认为剪枝有两种,一种是可行性剪枝,另一种是最优化剪枝。
可行性剪枝比较简单易懂,比如 E x a m p l e 1 \mathrm{Example~1} Example 1 中,当枚举到的总体积一定大于背包容量的时候,退掉这一部分的搜索。
最优化剪枝用下面的例子进行讲解。
给定一个总体积 π N \pi N πN 和层数 M M M,构建一个蛋糕,每一层都是圆柱形,你需要规定由下往上看,每一层的半径 R i R_i Ri 和高度 H i H_i Hi,要求对于所有 1 ≤ i < m 1 \leq i < m 1≤i<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} 10~20pts。
考虑剪枝:
首先,第 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] [M−i+1,min{ ⌊N−sV⌋,Ri−1−1}]
第 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] [M−i−1,min{ ⌊Ri2N−sV⌋,Hi−1−1}]
还有一个,当 s V > N sV > N sV>N ,也是可以直接剪枝的(废话)。
还有另一个可行性剪枝:当前面积 + + +之后可以得到的最大面积 < N
接下来是最优化剪枝,这个东西有个套路:
当 前 的 答 案 + 之 后 的 最 优 答 案 ( 忽 略 某 些 限 制 条 件 ) ≤ 当 前 得 到 的 最 优 答 案 当前的答案+之后的最优答案(忽略某些限制条件) \leq 当前得到的最优答案 当前的答案+之后的最优答案(忽略某些限制条件)≤当前得到的最优答案
如果不满足这个条件,就直接剪枝。
在这道题中,当前答案 + + +之后的最小侧面积 > > >已经得到的最小面积,就直接剪枝。
形式化讲 s S + M − i + 1 > a n s sS + M - i + 1 > ans sS+M−i+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=1∑iRj2HjsS=2×j=1∑iRjHj
那么对于 [ 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 N−sV=j=i+1∑MRj2HjsS′=2×j=i+1∑MRjHj
那么可以得到:
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+1∑MRjRiHj≥Ri2j=i+1∑MRj2Hj=Ri2(N−sV)
也就是说,当 s S + 2 ( N − s V ) R i > a n s sS + \frac{2(N - sV)}{R_i} > ans sS+Ri2(N−sV)>ans ,那么就直接剪枝。
这个东西的名字暗示了这篇 B l o g \mathrm{Blog} Blog的本质。
灌水法可以用于无向图联通块的信息统计,最直接的应用就是网格图的染色。
随便口胡一道题。
在一个 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,M≤103,1≤颜色编号≤K≤104
可以发现,就是每次找到一个之前没有深搜到的不是障碍的点开始,然后向四周扩散,然后记住拓展了多少个联通块,如果存在一个这个联通块中没有的颜色,就颜色数加一。
但是每次向新的联通块中灌水之前似乎需要清空记住颜色的数组。但是如果使用 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);
}