浅谈搜索的亿些优化 ---- 2
MoonCake2011
·
·
个人记录
浅谈搜索的亿些优化 ---- 1.
第三个优化:双向DFS
双向搜索是一种常见策略,一般用于优化枚举子集问题,主要适用于有主要初始状态与最终状态的时候。
方法如下,可以先搜索前半段,在搜索后半段,再对后半段产生的答案去匹配前半段产生最终答案。
例题:[CEOI2015 Day2] 世界冰球锦标赛.
此题是一个子集和问题,可以采用双向搜索。
先搜索前半段产生的答案,再搜索后半段产生的答案,分别存数组里(设前半段为fr,后半段为bk)。
接下来是一个最难环节,统计答案。
我们来想一下,只要 fr_i+bk_i \le m 就行,可以枚举,但时间复杂度太高了,于是,二分,它来了。
我们将 fr_i+bk_i \le m 变换为 fr_i \le m-bk_i.
我们可以对fr排序.
在fr里二分查找在 m-bk_i 即可。
我们用upper_bound寻找最小的大于 m-bk_i 的前一个的排名就是 fr_i+bk_i \le m 的数量。
这波批量统计NB吧。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,mid;
int a[110];
int dp[signed(1e6)+10],ans;
int fr[signed(1e7)+10],tot1;
int bk[signed(1e7)+10],tot2;
void dfs1(int x,int sum){
if(sum>m) return;
if(x>mid){
fr[++tot1]=sum;
return;
}
dfs1(x+1,sum);
dfs1(x+1,sum+a[x]);
}
void dfs2(int x,int sum){
if(sum>m) return;
if(x>n){
bk[++tot2]=sum;
return;
}
dfs2(x+1,sum);
dfs2(x+1,sum+a[x]);
}
signed main() {
cin>>n>>m;
mid=n>>1;
for(int i=1;i<=n;i++)
cin>>a[i];
if(m<=1e6){
dp[0]=1;
for(int i=1;i<=n;i++)
for(int j=m;j>=a[i];j--)
dp[j]+=dp[j-a[i]];
for(int i=0;i<=m;i++)
ans+=dp[i];
cout<<ans;
return 0;
}
dfs1(1,0);
dfs2(mid+1,0);
sort(fr+1,fr+tot1+1);
for(int i=1;i<=tot2;i++)
ans+=upper_bound(fr+1,fr+tot1+1,m-bk[i])-fr-1;
cout<<ans;
return 0;
}
例题:Anya and Cubes.
此题是一个子集和问题的变种。
现在有三个选择:选,不选,选并把它阶乘一下。
这样,时间复杂度高达 O(3^{26})。
因为 S 不超过long long,所以只有 a_i \le 20 才有权利去阶乘。
我们与上一题一样,采用双向搜索。
合并是个难题。
我们尝试建立一个unordered_map(C++11)数组把前半段的结果标记(注意重复)下来,第 i 个unordered_map表示使用 i 个阶乘得到的集合产生的标记。
接着,把后半段得到的结果对其进行匹配。
设后半段的某个结果需用到的阶乘数为p,选到的数的总和为sum。
我们设 1 \le x \le k-p。
我们统计第 x 个unordered_map的 S-sum 项的个数.
提前预处理阶乘,我们成功AC。
代码.
第四个优化:A^* 搜索.
谈了那么久,终于到广搜优化了。
它适用于一些最优解问题或 $k$ 优解问题。
它可以用优先队列将保证从终点第 $x$ 次得到的一定是第 $x$ 优解。
我们设一个函数 $f(x)$ 是预估的价值总和,让优先队列里的状态以 $f(x)$ 排序。
设已算的价值为 $h(x)$ ,那我们需要预估 剩下的价值 $g(x)$。
$f(x)=h(x)+g(x)$.
$g(x)$ 一般是 最优解.
我们需要使 $f(x) \le ans$.
到终点时,$f(x)=ans$,所以答案是能正确的。
剩下的和广搜一样一样的。
例题:[八数码问题](https://www.luogu.com.cn/problem/P1379).
我们设计一下 $A^*$ 算法的 $g(x)$.
最优解肯定为到它们该在的位置的曼哈顿距离。
我们就可以设计出 $A^*$ 算法。
代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
struct node{
int a[3][3];
int step;
const int* operator [] (const int &x)const{
return a[x];
}
void input(){
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
scanf("%1d",&a[i][j]);
step=0;
}
node(){
int p[3][3]={1,2,3,8,0,4,7,6,5};
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
a[i][j]=p[i][j];
step=0;
}
int val(){
node c=node();
int ans=0;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
for(int k=0;k<3;k++)
for(int p=0;p<3;p++)
if(a[i][j]==c[k][p] && a[i][j]!=0)
ans+=abs(i-k)+abs(j-p);
return ans;
}
string to_string(){
string s="";
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
s+=a[i][j]+'0';
return s;
}
}st;
bool operator > (node x,node y){
return x.val()+x.step>y.val()+y.step;
}
unordered_map<string,bool>mp;
bool operator == (node x,node y){
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
if(x[i][j]!=y[i][j])
return 0;
return 1;
}
int d[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
priority_queue<node,vector<node>,greater<node> >q;
void A_star(){
q.push(st);
while(!q.empty()){
node u=q.top();
q.pop();
if(u.to_string()=="123804765"){
cout<<u.step;
return;
}
int x,y;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
if(u[i][j]==0){
x=i,y=j;
break;
}
for(int i=0;i<4;i++){
int tx=x+d[i][0],ty=y+d[i][1];
if(tx<0 || ty<0 || tx>=3 || ty>=3) continue;
node t=u;
swap((int&)t[x][y],(int&)t[tx][ty]);
t.step++;
string s=t.to_string();
if(mp.count(s)) continue;
mp[s]=1;
q.push(t);
}
}
}
int main() {
st.input();
A_star();
return 0;
}
```
例题:[【模板】k 短路 / [SDOI2010] 魔法猪学院](https://www.luogu.com.cn/problem/P2483).
**这道题用 $A^*$ 不能得满分,只是用来过一下 Subtask#0,练习 $A^*$ ,~~但可以用 $A^*$+打表水过~~**.
此题我们设计如下 $g(x)$.
$g(x)$ 为此节点到终点的最短路,可以在返图跑迪杰斯特求出。
不懂迪杰斯特拉算法可以到[这里](https://www.cnblogs.com/char7/p/17214592.html).
每求出一个路径长度 $l$,就可以将`e-=l`,并`ans++`.
最后输出`ans`即可。
[代码](https://www.luogu.com.cn/paste/c3p5pte7).
当然,还有迭代加深 $A^*$ 算法,也称 $IDA^*$.
它是用估价函数对 DFS 进行可行性剪枝,在这里就不讲了。
**[搜索题单](https://www.luogu.com.cn/training/330884#problems)**.