浅谈搜索的亿些优化 ---- 2

· · 个人记录

浅谈搜索的亿些优化 ---- 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)数组把前半段的结果标记(注意重复)下来,第 iunordered_map表示使用 i 个阶乘得到的集合产生的标记。

接着,把后半段得到的结果对其进行匹配。

设后半段的某个结果需用到的阶乘数为p,选到的数的总和为sum

我们设 1 \le x \le k-p

我们统计第 xunordered_mapS-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)**.