1.18闲话
同桌是弱智火p,天天启动,今天上英语课在哪里开局就是霸体加螺旋丸直接拿到你的先手替身我就飞雷神然后走过去捡标再次拿到先手哎呀我真是太有实力了然后被英语老师D了,鉴定为玩青水玩的
推歌不想推太大众的,但是不大众的都想不起来了,只能想好久才想起来几个
推歌:得过且过的勇者/洛天依,言和 by ilem
今天物理老师又一次布置了初中物理题,忘了,大概就是自由落体的一个伞兵然后去开伞分析运动状态还是啥的,顺便分析其动能
英语老师不愧是高三师德标兵下到初三的,讲了半节课高中课文,\(\frac 14\)时间用来讲高中维克多,然后还D我为啥用初中的维克多,说以后中考考到高中维克多和牛津词典上的别怪她没提醒我
啊啊啊我把代码存到洛谷上了没有洛谷导致我没有代码了!哈哈哈哈
今天是搜索专题?好像还有DP,但是都没啥意义
-
DFS
深搜没啥好说的,复杂度一般指数级
伪代码如下
int ans=最差情况,now; void dfs(int q){ if(满足条件){ ans=ans和当前的解中较优秀 return; } for(遍历所有满足的){ if(符合条件){ 修改 dfs(缩小规模) 回溯 } } }
-
BFS
好像一般是图的遍历之类的?好像也没啥好说的,就是空间比较大
-
剪枝
-
记忆化搜索
空间换时间,定义记忆化数组,若访问过直接暴力跳即可
int g[MAXN]; int ans = 最坏情况, now; void dfs(int q){ if(g[规模]!=-1) return; if(满足条件){ ans=ans和当前的解中较优秀 return; } for(遍历所有可能性) if (可行) { 操作; dfs(缩小规模); 回溯; } }
-
最优性剪枝
搜索哪怕遇到比之前已经求解过的还要差的解依然会进行搜索
我们可以对其进行剪枝
int ans=最坏情况, now; void dfs(int q){ if(当前解更差) return; if(满足条件){ ans=ans和当前的解中较优秀 return; } for(遍历所有可能性) if (可行) { 操作; dfs(缩小规模); 回溯; } }
-
可行性剪枝
当前解不适用了还搜啥,直接返回
int ans=最坏情况, now; void dfs(int q){ if(当前解不适用) return; if(满足条件){ ans=ans和当前的解中较优秀 return; } for(遍历所有可能性) if (可行) { 操作; dfs(缩小规模); 回溯; } }
-
减去冗余子树
有的时候有好多结果一样的子树,那么直接只访问一个即可了
-
修改搜索顺序
比如采药这道背包题我们可以选择算出性价比,接着去按照性价比去排序
-
-
双向搜索
直接对其进行折半,比如一个图只看左半边去搜再只看右半边去搜,最后去找能互补的结果即可
复杂度可以从\(O(n^m)\)降到\(O(n^{\frac m2})\)
之前打 ABC 好像用到了
-
启发式搜索
忘了之前啥时候学的了,现在就记得有个估价函数
启发式搜索就是对所有子树进行判断去看两边估出来的价,哪个比较好就去选取更优解或者删掉不好的解
-
迭代加深
一般是找最优解,方案数还是不要用迭代加深了
就是在正常的搜索里加入一个深度变量,如果达到了规定的深度就返回
如果找了一圈都没找到那就让规定的深度+1继续从头开始
使用前提:至少要有解
对于迭代加深有一个比较特殊的剪枝方法:如果是最优情况下搜索到当前的深度上限都不能达到条件就直接跳出了
int ans=最差情况 inline void DFS(int u,int d){ if(f(u,d)达不到)//估价,判断最优能不能达到条件 return if(d>limit) return if(满足条件) ans去选更优的 for(遍历所有满足的) if(满足条件){ 修改 DFS(缩小规模,d+1) } }
DP
你说的对,但是我推出来的式子我都不知道是什么DP,但是还是根据学校OJ上的DP去乱写一下
-
背包DP
比较无意义,直接贺背包九讲了
-
01背包
\[f_{i,j} = \max(f_{i-1,j}, f_{i-1,j-w_i} + v_i) \]这个不是非常好理解吗,就是说\(f_{i,j}\)表示取前\(i\)件物品还剩\(j\)的容量
发现能滚一下,然后发现可以用一维数组完成
\[f_j=\max(f_j,f_{j-{w}_i}+v_i) \]-
代码
看起来比较无意义,只要记住是
--j
就行for(int i=1;i<=n;i++){ for(int j=c;j>=w[i];--j){ f[j]=max(f[j],f[j-w[i]]+v[i]); } } cout<<f[c];
-
-
完全背包
比较无意义,就是把
--j
变成++j
即可-
代码
for(int i=1;i<=n;i++){ for(int j=w[i];j<=c;++j){ f[j]=max(f[j],f[j-w[i]]+v[i]); } } cout<<f[c];
-
-
多重背包
这个有一个比较暴力的思路就是枚举多次,但是可能会TLE
for(int i=1;i<=n;++i){ for(int j=c;j>=w[i];--j){ for(int k=1;k*w[i]<=j&&k<=num[i];++k){ f[j]=max(f[j],f[j-k*w[i]]+k*v[i]); } } }
二进制优化是万万不可的,单调队列优化是非常好的
朴素的转移方程是
\[f_{i,j}=\max_{k=0}^{k_i}(f_{i-1,j-k\times w_i}+v_i\times k) \]复杂度明显不好,设\(g_{x,y}=\{f_{i,x\times w_i+y}\},g'_{x,y}=\{f_{i-1,x\times w_i+y}\}\),
转移方程就是
\[g_{x,y}=\max_{k=0}^{k_i}(g'_{x-k,y}+v_i\times k) \]设\(G_{x,y}=g'_{x,y}-v_i\times x\)
那不就转化成人尽皆知的优化方式了吗?
\[g_{x,y}=\max_{k=0}^{k_i}(G_{x-k,y})+v_i\times x \]看起来意义比二进制要大一些
-
分组背包
看起来不太有意义
for(int i=1;i<=n;++i){ for(int j=c;j>=0;--j){ for(int k=1;k<=t;++k){ if(j<w[k]) continue; f[j]=max(f[j],f[j-w[k]]+v[k]); } } }
-
线性
好像也没啥意义
-
最长上升子序列
这不无意义吗,放个转移方程得了
\[f_{i}=\max\begin{cases}f_j+1(f_i<f_j)\\f_i\end{cases} \] -
最长公共子序列
更无意义的一个
\[f_{i,j}=\begin{cases}f_{i-1,j-1}+1&s1[i]=s2[i]\\\max (f_{i-1,j},f_{i,j-1})&s1[i]\not= s2[i]\end{cases} \] -
数字三角形
最无意义的一集
\[f_{i,j}=\max(f_{i,j-1},f_{i-1,j-1}+a[i][j]) \]
-
-
-
区间dp
放个合并石子基础版
如果有环就直接拆成两倍的长度
for(int i=1;i<=n;i++){ sum[i]=read(); sum[i]+=sum[i-1]; f2[i][i]=0; } for(int q=2;q<=n;q++){ for(int i=1,j;(j=i+q-1)<=n;i++){ for(int k=i;k<j;k++){ f1[i][j]=max(f1[i][j],f1[i][k]+f1[k+1][j]+sum[j]-sum[i-1]); f2[i][j]=min(f2[i][j],f2[i][k]+f2[k+1][j]+sum[j]-sum[i-1]); } } }
-
坐标dp
非常无意义的
-
树形dp
没学