zoukankan      html  css  js  c++  java
  • AcWing算法提高课【第二章搜索】DFS之联通性模型、DFS之搜索顺序、DFS之剪枝与优化、迭代加深、双向DFS、IDA*

    DSF要不要恢复现场,要看题目是什么样子了,看是内部搜索还是外部搜索

    如果有所点都在棋盘内部搜索,我们需要保证每个点只能被搜一次,因此就不需要恢复现场。

    如果把棋盘当作一个整体的状态,看棋盘不同状态的变化,就需要恢复现场了。

    DFS之联通性模型:【内部搜索】

     1112. 迷宫

    题目:

    一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由 n∗n 的格点组成,每个格点只有2种状态,.和#,
    前者表示可以通行后者表示不能通行。 同时当Extense处在某个格点时,他只能移动到东南西北(或者说上下左右)四个方向之一的相邻格点上,Extense想要从点A走到点B,
    问在不走出迷宫的情况下能不能办到。 如果起点或者终点有一个不能通行(为#),则看成无法办到。 注意:A、B不一定是两个不同的点。 输入格式 第1行是测试数据的组数 k,后面跟着 k 组输入。 每组测试数据的第1行是一个正整数 n,表示迷宫的规模是 n∗n 的。 接下来是一个 n∗n 的矩阵,矩阵中的元素为.或者#。 再接下来一行是 4 个整数 ha,la,hb,lb,描述 A 处在第 ha 行, 第 la 列,B 处在第 hb 行, 第 lb 列。 注意到 ha,la,hb,lb 全部是从 0 开始计数的。 输出格式 k行,每行输出对应一个输入。 能办到则输出“YES”,否则输出“NO”。 数据范围 1≤n≤100 输入样例: 2 3 .## ..# #.. 0 0 2 2 5 ..... ###.# ..#.. ###.. ...#. 0 0 4 0 输出样例: YES NO

    分析:

    简单的DFS搜索路径问题,DFS只可以说明是否存在路径,但是不能说明最短路。
    在写DFS的时候,还是要想象它的递归搜索树的样子,这更形象,更具体。
    

    代码:

    #include <cstdio>
    
    using namespace std;
    
    const int N = 110;
    
    int n;
    char g[N][N];
    int sx, sy, ex, ey;
    bool st[N][N];
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    
    bool dfs(int sx, int sy)
    {
        if (g[sx][sy] == '#') return false;
        if (sx == ex && sy == ey) return true;
        
        st[sx][sy] = true;
        
        for (int i = 0; i < 4; i ++ ) 
        {
            int x = sx + dx[i], y = sy + dy[i];
            if (x < 0 || x >= n || y < 0 || y >= n) continue;//我就有个疑惑了,这里return false 和continue有什么区别
            // if (x < 0 || x >= n || y < 0 || y >= n) return false;//这里,如果return false,直接返回给了调用它的函数,而不会是一个状态的末尾
            if (st[x][y]) continue;
            
            // printf("sx: %d, sy: %d || x: %d, y: %d
    ", sx, sy, x, y);
            if (dfs(x, y)) return true;
        }
        return false;
    }
    
    void work()
    {
        scanf("%d", &n);
        for (int i = 0; i < n; i ++ ) scanf("%s", g[i]);
        scanf("%d%d%d%d", &sx, &sy, &ex, &ey);
        
        // for (int i = 0; i < n; i ++ ) printf("%s
    ", g[i]);
        
        for (int i = 0; i <= n; i ++ ) 
            for (int j = 0; j <= n; j ++ ) 
                st[i][j] = false;
                
        if (dfs(sx, sy)) puts("YES");
        else puts("NO");
        return;
    }
    int main()
    {
        int T; scanf("%d", &T);
        while (T -- )
        {
            work();
        }
        return 0;
    }
    

    1113. 红与黑

    题目:

    有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。
    
    你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动。
    
    请写一个程序,计算你总共能够到达多少块黑色的瓷砖。
    
    输入格式
    输入包括多个数据集合。
    
    每个数据集合的第一行是两个整数 W 和 H,分别表示 x 方向和 y 方向瓷砖的数量。
    
    在接下来的 H 行中,每行包括 W 个字符。每个字符表示一块瓷砖的颜色,规则如下
    
    1)‘.’:黑色的瓷砖;
    2)‘#’:红色的瓷砖;
    3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。
    
    当在一行中读入的是两个零时,表示输入结束。
    
    输出格式
    对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。
    
    数据范围
    1≤W,H≤20
    输入样例:
    6 9 
    ....#. 
    .....# 
    ...... 
    ...... 
    ...... 
    ...... 
    ...... 
    #@...# 
    .#..#. 
    0 0
    输出样例:
    45
    

    分析:

    这里面的,有返回值的DFS还是理解的不到位的,还要再看看的。
    

    代码:

    #include <cstdio>
    
    using namespace std;
    
    const int N = 30;
    
    int m, n;
    char g[N][N];
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    bool st[N][N];
    
    int dfs(int sx, int sy)
    {
        int ans = 1;
        
        st[sx][sy] = true;
        for (int i = 0; i < 4; i ++ )
        {
            int x = sx + dx[i], y = sy + dy[i];
            if (x < 0 || x >= n || y < 0 || y >= m) continue;
            if (st[x][y]) continue;
            if (g[x][y] == '#') continue;
            // printf("before:x= %d || y= %d || ans= %d
    ", x, y, ans);
            ans += dfs(x, y);
            // printf("after:x: %d || y= %d || ans= %d
    ", x, y, ans);
        }
        // printf("last: x= %d || y = %d
    ", x, y);
        // printf("last: ans= %d
    ", ans);
        return ans;
    }
    void work()
    {
        for (int i = 0; i < n; i ++ ) scanf("%s", g[i]);
        
        for (int i = 0; i <= n; i ++ ) 
            for (int j = 0; j <= m; j ++ )  
                st[i][j] = false;
        // for (int i = 0; i < n; i ++ ) printf("%s
    ", g[i]);
        for (int i = 0; i < n; i ++ ) 
            for (int j = 0; j < m; j ++ ) 
                if (g[i][j] == '@')
                {
                    printf("%d
    ", dfs(i, j));
                    return;
                }
    }
    int main()
    {
        while (~scanf("%d%d", &m, &n))
        {
            if (!n && !m) break;
            work();
        }
        return 0;
    }
    

    【从这开始就是外部搜索了

    外部搜索:采取一个搜索顺序,使得我们可以将所有方案全部枚举到。

    恢复现场:我下去的时候什么样子,回来的时候要还是什么样子

    DFS之搜索顺序:

    1116. 马走日 

     题目:

    马在中国象棋以日字形规则移动。
    
    请编写一段程序,给定 n∗m 大小的棋盘,以及马的初始位置 (x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。
    
    输入格式
    第一行为整数 T,表示测试数据组数。
    
    每一组测试数据包含一行,为四个整数,分别为棋盘的大小以及初始位置坐标 n,m,x,y。
    
    输出格式
    每组测试数据包含一行,为一个整数,表示马能遍历棋盘的途径总数,若无法遍历棋盘上的所有点则输出 0。
    
    数据范围
    1≤T≤9,
    1≤m,n≤9,
    0≤x≤n−1,
    0≤y≤m−1
    输入样例:
    1
    5 4 0 0
    输出样例:
    32
    

    分析: 

    这一题,让我们计算棋盘有多少种方案数,无疑了,一定需要恢复现场。
    当我们到了当前层的时候,将当前层状态标记,然后dfs下面一层,然后紧跟着就要将状态恢复现状。
    注意,我们记方案数的时候,就是看叶子节点能否到达我们预设状态,也就是可不可以走完整个棋盘,也就是可不可以拓展n*m个点,如果可以就给他记个方案数。
    

    代码:

    #include <cstdio>
    
    using namespace std;
    
    const int N = 20;
    
    int n, m, x, y;
    bool st[N][N];
    int ans;
    int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2}, dy[8] = {-1, -2, -2, -1, 1, 2, 2, 1};
    
    void dfs(int sx, int sy, int u)
    {
        if (u == n * m ) 
        {
            ans ++;
            return;
        }
        
        //当前节点向下搜素,将当前节点制成true
        st[sx][sy] = true;
        
        for (int i = 0; i < 8; i ++ ) 
        {
            int x = dx[i] + sx, y = dy[i] + sy;
            if (x < 0 || x >= n || y < 0 || y >= m) continue;
            if (st[x][y]) continue;
            dfs(x, y, u + 1);
        }
        st[sx][sy] = false;
    }
    
    void work()
    {
        scanf("%d%d%d%d", &n, &m, &x, &y);
        for (int i = 0; i <= n; i ++ ) 
            for (int j = 0; j <= m; j ++ ) 
                st[i][j] = false;
        ans = 0;
        dfs(x, y, 1);
        printf("%d
    ", ans);
        return;
    }
    int main()
    {
        int T; 
        scanf("%d", &T);
        while (T -- )
        {
            work();
        }
        return 0;
    }
    

    1117. 单词接龙

    题目:

    单词接龙是一个与我们经常玩的成语接龙相类似的游戏。
    
    现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”,每个单词最多被使用两次。
    
    在两个单词相连时,其重合部分合为一部分,例如 beast 和 astonish ,如果接成一条龙则变为 beastonish。
    
    我们可以任意选择重合部分的长度,但其长度必须大于等于1,且严格小于两个串的长度,例如 at 和 atide 间不能相连。
    
    输入格式
    输入的第一行为一个单独的整数 n 表示单词数,以下 n 行每行有一个单词(只含有大写或小写字母,长度不超过20),输入的最后一行为一个单个字符,表示“龙”开头的字母。
    
    你可以假定以此字母开头的“龙”一定存在。
    
    输出格式
    只需输出以此字母开头的最长的“龙”的长度。
    
    数据范围
    n≤20
    输入样例:
    5
    at
    touch
    cheat
    choose
    tact
    a
    输出样例:
    23
    提示
    连成的“龙”为 atoucheatactactouchoose。

    分析:

    一个字符串可以使用最多2次。
    搜的时候,先处理好重叠部分就会很好写了。
    

    代码:  

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 30;
    
    int n;
    string s[N];
    int pub[N][N];//两个字符串i与j的最短的公共部分为多少,只有公共部分越短,总字符串才能越长
    int cnt[N];
    int ans;
    
    void dfs(string str, int last)
    {
        ans = max(ans, (int)str.size());
        
        cnt[last] ++;
        for (int i = 0; i < n; i ++ ) 
            if (pub[last][i] && cnt[i] < 2)
                dfs(str + s[i].substr(pub[last][i]), i);
        cnt[last] --;
    }
    
    int main()
    {
        cin >> n;
        for (int i = 0; i < n; i ++ ) cin >> s[i];
        char c; cin >> c;
        
        for (int i = 0; i < n; i ++ ) 
            for (int j = 0; j < n; j ++ ) 
                for (int k = 1; k < (int)min(s[i].size(), s[j].size()); k ++ )
                    if (s[i].substr(s[i].size() - k, k) == s[j].substr(0, k)) 
                    {
                        pub[i][j] = k;
                        break;
                    }
        
        for (int i = 0; i < n; i ++ )
            if (s[i][0] == c)
                dfs(s[i], i);
        
        cout << ans << "
    ";
        return 0;
    }
    

    1118. 分成互质组

    题目:

    给定 n 个正整数,将它们分组,使得每组中任意两个数互质。
    
    至少要分成多少个组?
    
    输入格式
    第一行是一个正整数 n。
    
    第二行是 n 个不大于10000的正整数。
    
    输出格式
    一个正整数,即最少需要的组数。
    
    数据范围
    1≤n≤10
    输入样例:
    6
    14 20 33 117 143 175
    输出样例:
    3 

    分析:

    我觉得这题是一道好题,可以很好的那他来练手,练DFS
    可以通过多个角度去搜索,可以通过不同的方法去搜索。
    可以一组一组的搜,可以一个一个的搜。
    可以以每个元素为状态,判断当前元素可以放到那一组中去。
    可以以组为状态,判断每个元素剩余元素是否可以放到当前组,(这里的尝试就说明我们需要恢复现场)没有被放入的,如果可以放,就将它尝试放入,、
    如果可以放入,那我们就不需要再新开一个数组,如果对于当前数组来说,剩余的所有元素都不能放入当前数组了,那我们才新开一个数组存放。
    优先将所有能加的数都加到当前数组,加满为止。
    枚举那个数可以放到当前组是一个组合问题,而不是一个排列问题,放入当前数组的元素不需要考虑顺序,所以我们可以搜索状态的时候,可以加一个last

    代码:

    //先搜第一组,组内没有元素,当前一共搜了0个元素,从0号下标开始搜
    //start,对于当前组来说,start前面的元素都被搜索过了,从当前位置开始搜索
    #include <cstdio>
    
    using namespace std;
    
    const int N = 11;
    
    int n;
    int a[N];
    
    int g[N][N];
    int ans = N;
    bool st[N];
    
    int gcd(int a, int b)
    {
        return b ? gcd(b, a % b) : a;
    }
    
    bool check(int cnt, int ct, int i)
    {
        for (int j = 0; j < ct; j ++ )
            if (gcd(g[cnt][j], a[i]) > 1) return false;
        return true;
    }
    
    void dfs(int u, int cnt, int ct, int last)
    {
        if (cnt >= ans) return;    
        if (u == n) 
        {
            ans = cnt;
            return;
        }
        
        //枚举所有元素,判断是否可以放入最后一组(当前组),前面的元素都被枚举过了,上一个搜索的位置开始搜last
        bool flag = true;
        for (int i = last; i < n; i ++ )
            if (!st[i] && check(cnt, ct, i))
            {
                st[i] = true;
                g[cnt][ct] = a[i];
                dfs(u + 1, cnt, ct + 1, i);
                st[i] = false;
                flag = false;
            }
        //我们的当前组(最后一组)已经没有元素可以加进去了
        if (flag) dfs(u, cnt + 1, 0, 0);
    }
    int main()
    {
        scanf("%d", &n);
        for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
        
        dfs(0, 1, 0, 0);
        
        printf("%d
    ", ans);
        return 0;
    }
    

     

    #include <cstdio>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 11;
    
    int n;
    int a[N];
    int g[N][N], p[N];
    
    int gcd(int a, int b)
    {
        return b ? gcd(b, a % b) : a;
    }
    
    bool check(int g[], int t, int ct)
    {
        for (int i = 0; i < ct; i ++ )
            if (gcd(g[i], t) > 1) return false;
        return true;
    }
    
    int ans = N;
    int cnt;
    void dfs(int u)
    {
        if (u == n + 1) 
        {
            ans = min(ans, cnt);
            return;
        }
        
        //将当前元素尝试加入每一个组
        for (int i = 0; i < cnt; i ++ ) 
            if (check(g[i], a[u], p[i]))
            {
                g[i][p[i] ++ ] = a[u];
                dfs(u + 1);
                g[i][-- p[i]];
            }
        //尝试为这个元素新开一个组
        g[cnt ++ ][p[cnt - 1] ++ ] = a[u];
        dfs(u + 1);
        g[-- cnt][-- p[cnt]];
    }
    int main()
    {
        scanf("%d", &n);
        for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
        
        dfs(1);
        
        printf("%d
    ", ans);
        
        return 0;
    }
    

      

    #include <cstdio>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    
    const int N = 11;
    
    int n;
    ll a[N];
    
    inline ll gcd(ll a, ll b)
    {
        return b ? gcd(b, a % b) : a;
    }
    
    int ans = N, cnt;
    ll g[N];
    
    void dfs(int u)
    {
        if (u == n)
        {
            ans = min(ans, cnt);
            return;
        }
        
        for (int i = 0; i < cnt; i ++ )
            if (gcd(g[i], a[u]) == 1)
            {
                g[i] *= a[u];
                dfs(u + 1);
                g[i] /= a[u];
            }
        
        g[cnt ++ ] = a[u];
        dfs(u + 1);
        g[-- cnt] /= a[u];
    }
    int main()
    {
        scanf("%d", &n);
        for (int i = 0; i < n; i ++ ) scanf("%lld", a + i);
        for (int i = 0; i < n; i ++ ) g[i] = 1;
        dfs(0);
        
        printf("%d
    ", ans);
        
        return 0;
    }
    

    DFS之剪枝与优化:

    165. 小猫爬山

    题目:

    翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。
    
    经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。
    
    翰翰和达达只好花钱让它们坐索道下山。
    
    索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2……CN。
    
    当然,每辆缆车上的小猫的重量之和不能超过 W。
    
    每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?
    
    输入格式
    第 1 行:包含两个用空格隔开的整数,N 和 W。
    
    第 2..N+1 行:每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。
    
    输出格式
    输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。
    
    数据范围
    1≤N≤18,
    1≤Ci≤W≤108
    输入样例:
    5 1996
    1
    2
    1994
    12
    29
    输出样例:
    2
    

    分析:

    小猫爬山这题,用DFS来搜索所有状态空间,找到题目所求性质MIN。让车辆的个数最少。
    思路:搜索运送的小猫数量作为我们的状态空间。
    优化:1.给小猫降序处理,先安放体重较大的。2.如果搜索小车数量大于等于当前答案,及时剪枝,因为这个搜索分支最好的情况也不比ans优。
    

    代码:

    #include <cstdio>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 20;
    
    int n, m;
    int a[N];
    int ans = N;
    int s[N], cnt;
    
    void dfs(int u)
    {
        //如果此时的cnt已经大于等于我们的ans了,一定不会比现在的情况更优,可以及时剪枝
        if (cnt >= ans) return; 
        if (u == n)
        {
            ans = cnt;
            for (int i = 0; i < cnt; i ++ )
            {
                // printf("%d
    ", s[i]);
            }
            return;
        }
        
        for (int i = 0; i < cnt; i ++ )
            if (s[i] + a[u] <= m)
            {
                s[i] += a[u];
                dfs(u + 1);
                s[i] -= a[u];
            }
        
        s[cnt ++ ] = a[u];
        dfs(u + 1);
        s[-- cnt] = 0;
            
    }
    int main()
    {
        scanf("%d%d", &n, &m);
        for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
        
        sort(a, a + n);
        reverse(a, a + n);
        
        dfs(0);
            
        printf("%d
    ", ans);
        
        return 0;
    }
    

    166. 数独

    题目:

    数独是一种传统益智游戏,你需要把一个 9×9 的数独补充完整,使得图中每行、每列、每个 3×3 的九宫格内数字 1∼9 均恰好出现一次。
    
    请编写一个程序填写数独。
    
    输入格式
    输入包含多组测试用例。
    
    每个测试用例占一行,包含 81 个字符,代表数独的 81 个格内数据(顺序总体由上到下,同行由左到右)。
    
    每个字符都是一个数字(1−9)或一个 .(表示尚未填充)。
    
    您可以假设输入中的每个谜题都只有一个解决方案。
    
    文件结尾处为包含单词 end 的单行,表示输入结束。
    
    输出格式
    每个测试用例,输出一行数据,代表填充完全后的数独。
    
    输入样例:
    4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
    ......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
    end
    输出样例:
    417369825632158947958724316825437169791586432346912758289643571573291684164875293
    416837529982465371735129468571298643293746185864351297647913852359682714128574936
    

     分析:

    这题,难搞,该多敲
    

     代码:

    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 9, M = 1 << N;
    
    int ones[M], map[M];
    int row[N], col[N], cell[3][3];
    char str[100];
    
    void init()
    {
        for (int i = 0; i < N; i ++ )
            row[i] = col[i] = (1 << N) - 1;
    
        for (int i = 0; i < 3; i ++ )
            for (int j = 0; j < 3; j ++ )
                cell[i][j] = (1 << N) - 1;
    }
    
    void draw(int x, int y, int t, bool is_set)
    {
        if (is_set) str[x * N + y] = '1' + t;
        else str[x * N + y] = '.';
    
        int v = 1 << t;
        if (!is_set) v = -v;
    
        row[x] -= v;
        col[y] -= v;
        cell[x / 3][y / 3] -= v;
    }
    
    int lowbit(int x)
    {
        return x & -x;
    }
    
    int get(int x, int y)
    {
        return row[x] & col[y] & cell[x / 3][y / 3];
    }
    
    bool dfs(int cnt)
    {
        if (!cnt) return true;
    
        int minv = 10;
        int x, y;
        for (int i = 0; i < N; i ++ )
            for (int j = 0; j < N; j ++ )
                if (str[i * N + j] == '.')
                {
                    int state = get(i, j);
                    if (ones[state] < minv)
                    {
                        minv = ones[state];
                        x = i, y = j;
                    }
                }
    
        int state = get(x, y);
        for (int i = state; i; i -= lowbit(i))
        {
            int t = map[lowbit(i)];
            draw(x, y, t, true);
            if (dfs(cnt - 1)) return true;
            draw(x, y, t, false);
        }
    
        return false;
    }
    
    int main()
    {
        for (int i = 0; i < N; i ++ ) map[1 << i] = i;
        for (int i = 0; i < 1 << N; i ++ )
            for (int j = 0; j < N; j ++ )
                ones[i] += i >> j & 1;
    
        while (cin >> str, str[0] != 'e')
        {
            init();
    
            int cnt = 0;
            for (int i = 0, k = 0; i < N; i ++ )
                for (int j = 0; j < N; j ++, k ++ )
                    if (str[k] != '.')
                    {
                        int t = str[k] - '1';
                        draw(i, j, t, true);
                    }
                    else cnt ++ ;
    
            dfs(cnt);
    
            puts(str);
        }
    
        return 0;
    }
    

      

    167. 木棒

    题目:

    乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。
    
    然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。
    
    请你设计一个程序,帮助乔治计算木棒的可能最小长度。
    
    每一节木棍的长度都用大于零的整数表示。
    
    输入格式
    输入包含多组数据,每组数据包括两行。
    
    第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。
    
    第二行是截断以后,所得到的各节木棍的长度。
    
    在最后一组数据之后,是一个零。
    
    输出格式
    为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。
    
    数据范围
    数据保证每一节木棍的长度均不大于 50。
    
    输入样例:
    9
    5 2 1 5 2 1 5 2 1
    4
    1 2 3 4
    0
    输出样例:
    6
    5 

     分析:

     代码:

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 70;
    
    int n;
    int a[N];
    int sum, len;
    bool st[N];
    
    bool dfs(int u, int lens, int start){
        // cout << sum << " " << len << " " << u << endl;
        if (u * len == sum) return true;
        if (lens == len) return dfs(u + 1, 0, 0);
        
        int fail = 0;
        for (int i = start; i < n; i ++ )
            if (!st[i] && a[i] + lens <= len && fail != a[i])
            {
                st[i] = true;
                if (dfs(u, lens + a[i], i + 1)) return true;
                st[i] = false;
                fail = a[i];
                if (lens == 0 || lens + a[i] == len) return false;
            }
        return false;
    }
    
    int main()
    {
        while (cin >> n, n)
        {
            sum = 0;
            for (int i = 0; i < n; i ++ ) 
            {
                st[i] = 0;
                scanf("%d", a + i);
                sum += a[i];
            }
            
            sort(a, a + n);
            reverse(a, a + n);
            for (len = a[n - 1]; len <= sum; len ++ ) 
                if (sum % len == 0)
                {
                    if (dfs(0, 0, 0))
                    {
                        printf("%d
    ", len);
                        break;
                    }
                }
        }
        return 0;
    }
    

      168. 生日蛋糕

    题目:

    7 月 17 日是 Mr.W 的生日,ACM-THU 为此要制作一个体积为 Nπ 的 M 层生日蛋糕,每层都是一个圆柱体。
    
    设从下往上数第 i 层蛋糕是半径为 Ri,高度为 Hi 的圆柱。
    
    当 i<M 时,要求 Ri>Ri+1 且 Hi>Hi+1。
    
    由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积 Q 最小。
    
    令 Q=Sπ ,请编程对给出的 N 和 M,找出蛋糕的制作方案(适当的 Ri 和 Hi 的值),使 S 最小。
    
    除 Q 外,以上所有数据皆为正整数。
    
    输入格式
    输入包含两行,第一行为整数 N,表示待制作的蛋糕的体积为 Nπ。
    
    第二行为整数 M,表示蛋糕的层数为 M。
    
    输出格式
    输出仅一行,是一个正整数 S(若无解则 S=0)。
    
    数据范围
    1≤N≤10000,
    1≤M≤20
    输入样例:
    100
    2
    输出样例:
    68
    

     

    代码:

    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 10010, inf = 1 << 30;
    
    int n, m;
    int minv[N], mins[N];
    int r[N], h[N];
    int ans = inf;
    
    void dfs(int u, int s, int v)
    {
        // cout << v << "  " << s << endl;
        if (v + minv[u] > n) return;
        if (s + mins[u] > ans) return;
        if (s + 2 * (n - v) / r[u + 1] >= ans) return;
        if (u < 0) return;
        
        if (u == 0)
        {
            if (v == n) ans = s;
            return;
        }
        
        for (int i = min((int)sqrt(n - v), r[u + 1] - 1); i >= u; i -- ) 
            for (int j = min((n - v) / i / i, h[u + 1] - 1); j >= u; j -- ) 
            {
                int t = 0;
                if (u == m) t = i * i;
                r[u] = i, h[u] = j;
                dfs(u - 1, s + 2 * i * j + t, v + i * i * j);
            }
    }
    int main()
    {
        cin >> n >> m;
        for (int i = 1; i <= n; i ++ ) minv[i] = minv[i - 1] + i * i * i;
        for (int i = 1; i <= n; i ++ ) mins[i] = mins[i - 1] + 2 * i * i;
        r[m + 1] = h[m + 1] = inf;    
        
        dfs(m, 0, 0);
        
        if (ans == 1 << 30) cout << 0 << endl;
        else cout << ans << endl;
        
        return 0;
    }
    

     迭代加深 

     170. 加成序列

    题目:

    满足如下条件的序列 X(序列中元素被标号为 1、2、3…m)被称为“加成序列”:
    
    X[1]=1
    X[m]=n
    X[1]<X[2]<…<X[m−1]<X[m]
    对于每个 k(2≤k≤m)都存在两个整数 i 和 j (1≤i,j≤k−1,i 和 j 可相等),使得 X[k]=X[i]+X[j]。
    你的任务是:给定一个整数 n,找出符合上述条件的长度 m 最小的“加成序列”。
    
    如果有多个满足要求的答案,只需要找出任意一个可行解。
    
    输入格式
    输入包含多组测试用例。
    
    每组测试用例占据一行,包含一个整数 n。
    
    当输入为单行的 0 时,表示输入结束。
    
    输出格式
    对于每个测试用例,输出一个满足需求的整数序列,数字之间用空格隔开。
    
    每个输出占一行。
    
    数据范围
    1≤n≤100
    输入样例:
    5
    7
    12
    15
    77
    0
    输出样例:
    1 2 4 5
    1 2 4 6 7
    1 2 4 8 12
    1 2 4 5 10 15
    1 2 4 8 9 17 34 68 77
    

     

    代码:

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #include <vector>
    
    using namespace std;
    
    const int N = 110;
    
    int n;
    int a[N];
    int depth;
    bool st[N];
    
    //枚举层数
    bool dfs(int u)
    {
        if (u > depth) return false;
        if (u == depth && a[u - 1] == n) return true;
        
        //从当前分支来看,如果已经出现过的和就不要在出现了
        memset(st, 0, sizeof st);
        for (int i = u - 1; i >= 0; i -- ) 
            for (int j = i; j >= 0; j -- )
            {
                int t = a[i] + a[j];
                if (st[t]) continue;
                if (t > n) continue;
                if (a[u - 1] * 2 < t) continue;
                if (t <= a[u - 1]) continue;
                st[t] = true;
                a[u] = t;
                if (dfs(u + 1)) return true;
    
            }
        
        return false;
    }
    int main()
    {
        while (cin >> n, n)
        {
            a[0] = 1;
            for (depth = 1; !dfs(1); depth ++ );
             
            for (int i = 0; i < depth; i ++ ) printf("%d ", a[i]);
            puts("");
            // printf("%d
    ", depth);
        }
        
        return 0;
    }
    

      双向DFS

    171. 送礼物

    题目:

    达达帮翰翰给女生送礼物,翰翰一共准备了 N 个礼物,其中第 i 个礼物的重量是 G[i]。
    
    达达的力气很大,他一次可以搬动重量之和不超过 W 的任意多个物品。
    
    达达希望一次搬掉尽量重的一些物品,请你告诉达达在他的力气范围内一次性能搬动的最大重量是多少。
    
    输入格式
    第一行两个整数,分别代表 W 和 N。
    
    以后 N 行,每行一个正整数表示 G[i]。
    
    输出格式
    仅一个整数,表示达达在他的力气范围内一次性能搬动的最大重量。
    
    数据范围
    1≤N≤46,
    1≤W,G[i]≤231−1
    输入样例:
    20 5
    7
    5
    4
    18
    1
    输出样例:
    19
    

      

    代码:

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long LL;
    
    const int N = 70;
    
    int m, n;
    int a[N];
    int w[1 << 25], cnt;
    int pos;
    
    void dfs(int u, int k, int s)
    {
        if (u >= k)
        {
            w[cnt ++ ] = s;        
            return;
        }
        
        dfs(u + 1, k, s);
        
        if  ((LL)a[u] + s <= m) dfs(u + 1, k, s + a[u]);
    }
    
    int ans;
    
    void dfs(int u, int s)
    {
        if (u >= n)
        {
            int l = 0, r = pos - 1;
            while (l < r)
            {
                int mid = l + r + 1>> 1;
                if ((LL)w[mid] + s <= m) l = mid;
                else r = mid - 1;
            }
            ans = max(ans, w[r] + s);    
            return;
        }
        
        dfs(u + 1, s);
        if ((LL)a[u] + s <= m) dfs(u + 1, s + a[u]);
    }
    int main()
    {
        cin >> m >> n;
        for (int i = 0; i < n; i ++ ) cin >> a[i];
        
        sort(a, a + n);
        reverse(a, a + n);
        
        dfs(0, n / 2 + 2, 0);
        
        sort(w, w + cnt);
        pos = unique(w, w + cnt) - w;
        // cout << pos << endl;
        // for (int i = 0; i < pos; i ++ ) cout << w[i] << " ";
        // cout << endl;
        dfs(n / 2 + 2, 0);
        // exit(0);
        cout << ans << endl;
        
        
        return 0;
    }
    

      IDA*

     180. 排书

    题目:

    给定 n 本书,编号为 1∼n。
    
    在初始状态下,书是任意排列的。
    
    在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。
    
    我们的目标状态是把书按照 1∼n 的顺序依次排列。
    
    求最少需要多少次操作。
    
    输入格式
    第一行包含整数 T,表示共有 T 组测试数据。
    
    每组数据包含两行,第一行为整数 n,表示书的数量。
    
    第二行为 n 个整数,表示 1∼n 的一种任意排列。
    
    同行数之间用空格隔开。
    
    输出格式
    每组数据输出一个最少操作次数。
    
    如果最少操作次数大于或等于 5 次,则输出 5 or more。
    
    每个结果占一行。
    
    数据范围
    1≤n≤15
    输入样例:
    3
    6
    1 3 4 6 2 5
    5
    5 4 3 2 1
    10
    6 8 5 3 4 7 2 9 1 10
    输出样例:
    2
    3
    5 or more
    

     

    代码:

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 20;
    
    int n;
    int a[N];
    int depth;
    int backup[5][N];
    
    int f()
    {
        int ans = 0;
        for (int i = 0; i + 1 < n; i ++ ) 
            if (a[i + 1] != a[i] + 1) ans ++;
        return (ans + 2) / 3;
    }
    
    bool dfs(int u)
    {
        if (u + f() > depth) return false;
        if (!f()) return true;
        
        for (int l = 0; l < n; l ++ ) 
            for (int r = l; r < n; r ++ )
                for (int k = r + 1; k < n; k ++ ) 
                {
                    for (int i = 0; i < n; i ++ ) backup[u][i] = a[i];                
                    int x, y;
                    // for (x = r + 1, y = l; x <= k; x ++, y ++ ) a[y] = backup[u][x];
                    // for (x = l; x <= r; x ++, y ++ ) a[y] = backup[u][x];
                    for (x = l, y = l + k - r; x <= r; x ++, y ++ ) a[y] = backup[u][x];
                    for (x = r + 1, y = l; x <= k; x ++, y ++ ) a[y] = backup[u][x];
                    
                    if (dfs(u + 1)) return true;
                    for (int i = 0; i < n; i ++ ) a[i] = backup[u][i];
                }
        return false;
    }
    
    void work()
    {
        cin >> n;
        for (int i = 0; i < n; i ++ ) cin >> a[i];
        
        
        for (depth = 0; !dfs(0) && depth < 5; depth ++ );
        
        if (depth >= 5) puts("5 or more");
        else cout << depth << endl;
        
        return;
    }
    
    int main()
    {
        int T; cin >> T;
        while (T -- )
        {
            work();
        }
        return 0;
    }
    

      

     181. 回转游戏

    题目:

    如下图所示,有一个 # 形的棋盘,上面有 1,2,31,2,3 三种数字各 88 个。

    给定 88 种操作,分别为图中的 AHA∼H。

    这些操作会按照图中字母和箭头所指明的方向,把一条长为 77 的序列循环移动 11 个单位。

    例如下图最左边的 # 形棋盘执行操作 AA 后,会变为下图中间的 # 形棋盘,再执行操作 CC 后会变成下图最右边的 # 形棋盘。

    给定一个初始状态,请使用最少的操作次数,使 # 形棋盘最中间的 88 个格子里的数字相同。

    2286_1.jpg

    输入格式

    输入包含多组测试用例。

    每个测试用例占一行,包含 2424 个数字,表示将初始棋盘中的每一个位置的数字,按整体从上到下,同行从左到右的顺序依次列出。

    输入样例中的第一个测试用例,对应上图最左边棋盘的初始状态。

    当输入只包含一个 00 的行时,表示输入终止。

    输出格式

    每个测试用例输出占两行。

    第一行包含所有移动步骤,每步移动用大写字母 AHA∼H 中的一个表示,字母之间没有空格,如果不需要移动则输出 No moves needed

    第二行包含一个整数,表示移动完成后,中间 88 个格子里的数字。

    如果有多种方案,则输出字典序最小的解决方案。

    输入样例:

    1 1 1 1 3 2 3 2 3 1 3 2 2 3 1 2 2 2 3 1 2 1 3 3
    1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3
    0
    

    输出样例:

    AC
    2
    DDHH
    2

    代码:

    #include <cstdio>
    #include <cstring>
    #include <sstream>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 110;
    
    //将八个操作的序号记录下来
    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 center[8] = {6, 7, 8, 11, 12, 15, 16, 17};
    int opposite[8] = {5, 4, 7, 6, 1, 0, 3, 2};
    
    int n;
    int a[N];
    int path[N];
    int depth;
    
    //执行当前操作,移动格子
    void work(int u)
    {
        int tmp = a[op[u][0]];
        for (int i = 1; i < 7; i ++ ) 
            a[op[u][i - 1]] = a[op[u][i]];
        a[op[u][6]] = tmp;
    }
    
    int f()
    {
        static int cnt[4] = {0};
        for (int i = 1; i <= 3; i ++ ) cnt[i] = 0;
        for (int i = 0; i < 8; i ++ )
        {
            // printf("%d ", a[center[i]]);
            cnt[a[center[i]]] ++;
        }
        // printf("
    ");
        // int ans = max(cnt[1], max(cnt[2], cnt[3]));
        int ans = 0, id = 0;
        for (int i = 1; i <= 3; i ++ ) if (ans < cnt[i]) ans = cnt[i], id = i;
        // printf("id: %d, ans: %d
    ", id, ans);
        return 8 - ans;
    }
    
    bool dfs(int u, int last)
    {
        // cout << u << " " << last << endl;
        // cout << f() << endl;
        if (u + f() > depth) return false;
        if (!f()) return true;
        // cout << u << " " << last << endl;
        for (int i = 0; i < 8; i ++ )
        {
            if (opposite[i] == last) continue; 
            work(i);
            path[u] = i;    
            if (dfs(u + 1, i)) return true;
            work(opposite[i]);
        }
        return false;
    }
    int main()
    {
        string s;
        while (getline(cin, s))
        {
            n = 0;
            stringstream ssin(s);
            while (ssin >> a[n]) n ++;
            if (!a[0]) break;
            
            for (depth = 0; !dfs(0, -1); depth ++ );
            // exit(0);
            // puts("=-------------------=");
            if (!depth) puts("No moves needed"), printf("%d
    ", a[6]);
            else 
            {
                // for (int i = 0; i < depth; i ++ ) cout << path[i] << " ";
                // cout << endl;
                for (int i = 0; i < depth; i ++ ) printf("%c", 'A' + path[i]);
                printf("
    %d
    ", a[6]);
            }
            // puts("-________________________________-");
        }
        return 0;
    }
    

      

     

  • 相关阅读:
    信件分析实战(五)——数据可视化
    信件分析实战(四)——数据分析以及部分可视化
    剑指offer15题
    剑指offer14题
    剑指offer11题
    剑指offer第9题
    剑指offer第8题--动态规划最简单讲解
    剑指offer第7题
    剑指offer第6题
    剑指offer第5题
  • 原文地址:https://www.cnblogs.com/Iamcookieandyou/p/14731777.html
Copyright © 2011-2022 走看看