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;
}

  

 

原文地址:https://www.cnblogs.com/Iamcookieandyou/p/14731777.html