搜索——迭代加深&&双向DFS&&IDA*(DFS)

迭代加深

加成序列

原题链接

这里是引用

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 1e9
using namespace std;
typedef pair<int, int>pii;

const int N = 110;

int n;
int path[N];

bool dfs(int u,int k){
    if(u==k) return path[u-1]==n;
    bool st[N]={0};
    for(int i=u-1;i>=0;i--)
        for(int j=i;j>=0;j--){
            int s=path[i]+path[j];
            if(s>n||s<=path[u-1]) continue;
            st[s]=true;
            path[u]=s;
            if(dfs(u+1,k)) return true;
        }
    
    return false;
}

signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    path[0]=1;//树的第0层
    while(cin>>n,n){
        int k=1;//一共搜索k层
        while(!dfs(1,k)) k++;
        for(int i=0;i<k;i++) cout<<path[i]<<" ";
        cout<<endl;
    }

    return 0;
}

思路分析

  1. 因为往下搜索会搜索很深,并且解空间很大。但是根据题意可知,答案所在层数很浅,所以设置搜索的最大深度进行搜索。
  2. 理解 解元素 x[N]——放的是路径

总结

迭代加深的思路非常的重要。一步一步扩大搜索的深度进行搜索。

双向DFS

送礼物

原题链接

这里是引用

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 1e9
using namespace std;
typedef pair<int, int>pii;

const int N = 1<<24;
int n,m,k;
int g[50],w[N];
int cnt=0;
int ans;

void dfs(int u,int s){
    if(u==k){
        w[cnt++]=s;
        return;
    }
    if(s+g[u]<=m) dfs(u+1,s+g[u]);
    dfs(u+1,s);
}

void dfs2(int u,int s){
    if(u==n){
        //二分这里注意
        int l=0,r=cnt-1;
        while(l<r){
            int mid=l+r+1>>1;
            if(s+w[mid]<=m) l=mid;
            else r=mid-1;
        }
        if(w[l]+s<=m) ans=max(ans,w[l]+s);
        return;
    }
    if(s+g[u]<=m) dfs2(u+1,s+g[u]);
    dfs2(u+1,s);
}

signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>m>>n;
    for(int i=0;i<n;i++) cin>>g[i];
    sort(g,g+n);
    reverse(g,g+n);
    k=n/2;
    
    dfs(0,0);
    
    sort(w,w+cnt);
    int t=1;
    for(int i=1;i<cnt;i++)
        if(w[i]!=w[i-1])
            w[t++]=w[i];
    
    cnt=t;
    dfs2(k,0);
    cout<<ans<<endl;

    return 0;
}

思路总结

  1. 本题是一个动态规划的01背包问题,但是n*m会超过时间复杂度。
  2. 如果用不优化的dfs,最大时间复杂度是2的46次方,也会超时
  3. 所以我们想到用双向dfs减少时间复杂度(2的23次方),先搜索前一半的物品,将每种选取方式得到的总量(价值)记录下来,方便后面匹配二分。然后搜索后一半,找到不超过m的最大总重量。

总结

双向dfs,先搜索前面一半,记录起来,然后搜索后一半,搜索的和前面一半记录的进行判断。

IDA*

排书

原题链接

这里是引用

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 1e9
using namespace std;
typedef pair<int, int>pii;

const int N = 20 ;
int n;
int a[N],w[10][N];

int f(){
    int cnt=0;
    for(int i=0;i+1<n;i++)
        if(a[i+1]!=a[i]+1)
            cnt++;
    return (cnt+2)/3;
}
bool dfs(int u,int depth){
    if(u+f()>depth) return false;
    if(f()==0) return true;
    for(int len=1;len<=n;len++){
        for(int l=0;l+len-1<n;l++){
            int r=l+len-1;
            for(int k=r+1;k<n;k++){
                memcpy(w[u],a,sizeof a);
                int x,y;
                for(x=r+1,y=l;x<=k;x++,y++) a[y]=w[u][x];//上面定义了x,y,for循环里面不要再定义临时变量
                for(x=l;x<=r;x++,y++) a[y]=w[u][x];
                if(dfs(u+1,depth)) return true;
                memcpy(a,w[u],sizeof a);
            }
        }
    }
    return false;
}

signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        cin>>n;
        for(int i=0;i<n;i++)
            cin>>a[i];
        int depth=0;
        while(depth<5&&!dfs(0,depth)){
            depth++;
        }
        if(depth>=5) cout<<"5 or more"<<endl;
        else cout<<depth<<endl;
    }

    return 0;
}

思路分析

  1. 因为每一层搜索很大,所以只能搜索很浅才行,并且需要尽量搜索较少的点。因此需要引入估价函数剪枝来减少搜索的点。
  2. 估价函数的定义,估价函数要满足不大于实际步数。一般都定义:到最终答案所需要的最少步数。
  3. IDA一般用来求字典序最小,和减少时间复杂度。写法一般比A更简单。

总结

  1. 搜索顺序方向很重要。先枚举移动的长度大小,然后枚举将其放在哪个数的后面,改变状态(得到这一层的状态),进入下一层,若失败,恢复状态。
  2. 估价函数的定义,对于有序序列,可以用这种方式。
  3. 改变状态的技巧,注意不要再在for循环里面设置临时变量x,y了。y是从左到右开始改变

回转游戏

原题链接

这里是引用

/*
      0     1
      2     3
4  5  6  7  8  9  10
      11    12
13 14 15 16 17 18 19
      20    21
      22    23
*/

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 24;

int op[8][7] = {
    {0, 2, 6, 11, 15, 20, 22},
    {1, 3, 8, 12, 17, 21, 23},
    {10, 9, 8, 7, 6, 5, 4},
    {19, 18, 17, 16, 15, 14, 13},
    {23, 21, 17, 12, 8, 3, 1},
    {22, 20, 15, 11, 6, 2, 0},
    {13, 14, 15, 16, 17, 18, 19},
    {4, 5, 6, 7, 8, 9, 10}
};

int oppsite[8] = {5, 4, 7, 6, 1, 0, 3, 2};
int center[8] = {6, 7, 8, 11, 12, 15, 16, 17};

int q[N];
int path[100];

int f()
{
    static int sum[4];
    memset(sum, 0, sizeof sum);
    for (int i = 0; i < 8; i ++ ) sum[q[center[i]]] ++ ;

    int maxv = 0;
    for (int i = 1; i <= 3; i ++ ) maxv = max(maxv, sum[i]);

    return 8 - maxv;
}

void operate(int x)
{
    int t = q[op[x][0]];
    for (int i = 0; i < 6; i ++ ) q[op[x][i]] = q[op[x][i + 1]];
    q[op[x][6]] = t;
}

bool dfs(int depth, int max_depth, int last)
{
    if (depth + f() > max_depth) return false;
    if (f() == 0) return true;

    for (int i = 0; i < 8; i ++ )
        if (last != oppsite[i])
        {
            operate(i);
            path[depth] = i;
            if (dfs(depth + 1, max_depth, i)) return true;
            operate(oppsite[i]);
        }

    return false;
}

int main()
{
    while (cin >> q[0], q[0])
    {
        for (int i = 1; i < 24; i ++ ) cin >> q[i];

        int depth = 0;
        while (!dfs(0, depth, -1)) depth ++ ;

        if (!depth) printf("No moves needed");
        else
        {
            for (int i = 0; i < depth; i ++ ) printf("%c", 'A' + path[i]);
        }
        printf("\n%d\n", q[6]);
    }

    return 0;
}

思路分析

  1. 这题也需要减少时间复杂度,并且需要字典序最小,所以选择IDA*算法。
  2. 然后设置估价函数和考虑如何扩展方向。

总结

  1. IDA*算法,迭代加深加上估价函数
  2. 扩展方向的技巧,先将要变化的下标存储起来,然后进行八种操作进行扩展。
  3. 解元素 x[N]存放的是搜索的路径,即达到当前层的操作种类,最后也是字典序。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值