算法提高-搜索-迭代加深、双向DFS、IDA*

算法提高-搜索-迭代加深、双向DFS、IDA*

  • 迭代加深
    • AcWing 171. 加成序列
  • 双向DFS
    • AcWing 170. 送礼物
  • IDA*
    • AcWing 180. 排书
    • AcWing 181. 回转游戏

迭代加深

AcWing 171. 加成序列

#include 
#include 

using namespace std;

const int N = 110;

int path[N];
int n;

bool dfs(int u, int k)//u是当前层数,k是最多允许迭代的层数
{
    
    if (u == k) return path[u - 1] == n;//保证只有在迭代k层的时候才返回结果,保证path数组的大小就是k,只不过下标从0开始
    
    bool st[N] = {0};//每次迭代都会重置st数组,st数组用来排除等效冗余
    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] || st[s]) continue; //排除等效冗余,同时保证更新path[u]的数都要大于之前的
            
            path[u] = s;
            st[s] = true;
        
            if (dfs(u + 1, k)) return true;//因为我们用的是优化的搜索顺序,从大的数开始枚举,所以只要走到这一步就说明我们之前已经找到了一个最合适的,可以去下一层了
        }
        
    return false;
}

int main ()
{
    path[0] = 1;//记得初始化

    while (cin >> n, n)//正常多组测试数据是给T的,题目故意恶心我们
    {
        int k = 1;//最多递归的层数
        while (!dfs(1, k)) k ++ ;
        
        for (int i = 0; i < k; i ++ )//dfs的时候控制每一层都得迭代k次,因为k从1开始,如果找到结果了那么一定迭代了k次,因为k++的条件就是dfs迭代没找到结果
            cout << path[i] << " ";
        cout << endl;
    }    
    return 0;
}

双向DFS

AcWing 170. 送礼物

#include 
#include 
#include 

using namespace std;
//G[i]≤2^31−1,不能用背包,int数组存不了这么大的数字
typedef long long LL; 
const int N = 46;

int w, n, k;
int weights[1 << (N / 2)], cnt;//枚举k = N/2个物品,每个物品选或者不选2种选择,因此理论上最多有2^N/2种重量组合,
int g[N];
int ans;//收集结果

void dfs1(int u, int s)//当前枚举的是第u个物品,到目前枚举的所有物品的重量和为s
{
    //收集结果
    if(u == k) 
    {
        weights[cnt ++ ] = s;
        return ;
    }
    //不选择第u个物品
    dfs1(u + 1, s);
    //选择第u个物品
    if ((LL)s + g[u] <= w)//可行性优化
    dfs1(u + 1, s + g[u]);
}

void dfs2(int u, int s)//当前枚举的是第u个物品,到目前枚举的所有物品的重量和为s
{
    //收集结果
    if (u == n)
    {
        int l = 0, r = cnt - 1;
        while (l < r)//从之前打的表(前n/2个物品组合可以组成的所有重量组合)直接取数据,题目要求最大的重量,取max即可
        {
            int mid = l + r + 1 >> 1;
            if ((LL)s + weights[mid] <= w) l = mid;
            else r = mid - 1;//说明mid不满足性质,r不用取mid
        }
        if (weights[l] + (LL)s <= w) 
        ans = max(ans, weights[l] + s);//这里不能写成ans = max(ans, weights[l] + (LL)s),因为max函数的两个参数数据类型得一样
        return ;
    }
    
    //不选择第u个物品
    dfs2(u + 1, s);
    //选择第u个物品
    if ((LL)s + g[u] <= w)//可行性优化
    //这里ans = max(ans, weights[l] + (LL)s)和dfs2(u + 1, s + g[u])都能过,不知道有啥区别
  
    dfs2(u + 1, s + g[u]);
}
int main()
{
    cin >> w >> n;
    for (int i = 0; i < n; i ++ ) cin >> g[i];
    sort(g, g + n);//优化搜索顺序
    reverse(g, g + n);
    
    k = n / 2;//将前n/2个物品可以组合成的重量打表记录
    
    dfs1(0, 0);//枚举到第k - 1个物品,因为下标从0开始
    
    sort(weights, weights + cnt);//优化搜索顺序
    
    //判重
    //int t = 0;//如果判重的时候t从0开始枚举,那么要特判一下weights[cnt - 1] 和 weights[cnt - 2],并且i + 1 < cnt
    int t = 1;
    for (int i = 1; i < cnt; i ++ )
        if (weights[i] != weights[i - 1])
            weights[t ++ ] = weights[i];//t 从1开始赋值更新weights数组 默认weights[0]是不重复的
    cnt = t;//更新weights数组的大小
    
    dfs2(k, 0);//从第k个物品开始枚举
    cout << ans;
    return 0;
}

IDA*

和A*算法挺像的

AcWing 180. 排书

结合了迭代加深 和 IDA*

#include 
#include 

using namespace std;

const int N = 16;
int n;
int q[N];
int w[5][N];//存储每一层,方便回溯 

int f()//每一次调换两端图书的顺序会改变三个后继关系,因此这个启发函数用来发现当前图书有多少个不正确的后继关系
{
    int res = 0;
    for (int i = 0; i + 1< n; i ++ )
    {
        if (q[i + 1] != q[i] + 1) res ++ ;//一开始写成了q[i + 1] != q[i],后来写成了q[i] != q[i] + 1,debug半天
    }
    return (res + 2) / 3;
}

bool check()
{
    for (int i = 0; i + 1 < n; i ++ )
        if (q[i + 1] != q[i] + 1)
            return false;
    return true;
}

bool dfs(int u, int depthMax) //u为当前层数,depthMax为最多迭代的层数
{
    if (u + f() > depthMax) return false;//启发函数判断是否有继续下去的必要
    if (check()) return true;//不能直接用 !f()来判断,因为如果有1或2个逆序的!f()返也是0
    
    for (int length = 1; length <= n; length ++ )
        for (int l = 0; l + length - 1 < n; l ++ )
        {
            int r = l + length - 1;
            for (int k = r + 1; k < n; k ++ )
                {
                   
                    memcpy(w[u], q, sizeof q);
                    int x, y;
                    for (x = r + 1, y = l; x <= k; x ++ , y ++ ) q[y] = w[u][x];
                    for (x = l; x <= r; x ++ , y ++ ) q[y] = w[u][x];
                    if (dfs(u + 1, depthMax)) return true;
                    memcpy(q, w[u], sizeof q);//走到这一步说明之前那个替换方法失败了,回溯
                }
        }
    return false ;
} 

int main()
{
    int T;
    cin >> T;
    while (T -- )
    {
        cin >> n;
        for(int i = 0; i < n; i ++ ) cin >> q[i];
        
        int depthMax = 0;
        while (depthMax < 5 && !dfs(0, depthMax)) depthMax ++ ;
        if (depthMax >= 5) cout << "5 or more" << endl;
        else cout << depthMax << endl;
    }
    return 0;
}

AcWing 181. 回转游戏

/*
      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 
#include  

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 opposite[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];



//通过简单分析,我们可以发现每次拉扯只可以在中间多增加一个相同的数
//(拉进去一个出来一个,最理想状态为拉进去一个我们需要的数,扯出来不需要的数,所以每次最多增加一个目标数),
//所以估价函数设置为8减去中间最多的数个数就行了
int f()
{
    int maxV = 0;
    static int sum[4];
    memset(sum, 0, sizeof sum);
    
    for (int i = 0; i < 8; i ++ ) sum[q[center[i]]] ++ ;
    
    for (int i = 0; i < 4; i ++ ) maxV = max(maxV, sum[i]);
    
    return 8 - maxV;
}

void operate(int x)// x表示要进行的操作
{
    int t = q[op[x][0]];
    for (int i = 0;i + 1 < 7; i ++ )
    {
        q[op[x][i]] = q[op[x][i + 1]];
    }
    q[op[x][6]] = t;
}

bool dfs(int u, int depthMax, int last)
{
    if (u + f() > depthMax) return false;
    
    if (f() == 0) return true;//这题可以直接用!f()当作check函数,判断是否符合题意
    
    for (int i = 0; i < 8; i ++ )
    {
        if (opposite[i] == last) continue; //之前写的i == last,debug好久,如果当前操作是上一步的反操作就continue=下一个操作i
        operate(i);
        path[u] = i;//不用回溯,每一层都会自己覆盖掉
        if (dfs(u + 1, depthMax, i)) return true;
        operate(opposite[i]);//如果走到这一步,说明操作i无法得到一个正确的结果,因此要回溯回去,遍历下一个操作是否可以得到一个正确的结果
    }
    return false;
}

int main ()
{
    while (cin >> q[0], q[0])//新型输入输出,因为这里输入了一个,所以下面得从1开始并且循环到23
    {
        for (int i = 1; i <= 23; i ++ ) cin >> q[i];
        int depthMax = 0;
        while (!dfs(0, depthMax, -1)) depthMax ++;
        
        if (!depthMax) cout << "No moves needed";
        else
        {
            for (int i = 0; i < depthMax; i ++ )
            printf("%c", path[i] + 'A');
        }
        cout << endl << q[6] << endl;
    }
    return 0;
}

你可能感兴趣的:(深度优先,算法,迭代加深,蓝桥杯,图论)