$ACM$ 课第三次作业-搜索

\(ACM\) 课第三次作业-搜索


\(A.\ Sum\ It\ Up\)

题意

给定 \(t\)\(n\) 个整数 \(x_{i}\),从 \(n\) 个整数中挑任意个数,使得它们的和为 \(t\)

输入格式

多组数据,\(n\leq 12,\ t\leq 1000,\ x_{i}\leq 100\)

输出格式

数字之间添 \(+\) 号,不考虑顺序(\(2 + 2 + 1\)\(2 + 1 + 2\) 看作同一个),按照字典序列从大到小输出所有组合

题解

由于 \(n\) 很小,故考虑二进制枚举,然后 \(check\)

由于不考虑顺序,所以对于每一个枚举的情况都要用 \(map\) 记忆化一下,即 map<multiset<int>, int>

输出结果用字符串拼接,放到容器里面排序即可

\(code\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int, int>
#define arrayDebug(a, l, r) for(int i = l; i <= r; ++i) printf("%d%c", a[i], " \n"[i == r])
typedef long long LL;
typedef unsigned long long ULL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int DX[] = {0, -1, 0, 1, 0, -1, -1, 1, 1};
const int DY[] = {0, 0, 1, 0, -1, -1, 1, 1, -1};
const int MOD = 1e9 + 7;
const int N = 4e2 + 7;
const double PI = acos(-1);
const double EPS = 1e-6;
using namespace std;

inline int read()
{
    char c = getchar();
    int ans = 0, f = 1;
    while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
    while(isdigit(c)) {ans = ans * 10 + c - '0'; c = getchar();}
    return ans * f;
}

int t, n, cnt, a[20];
set<string, greater<string> > st;
map<multiset<int>, int> mp;
bool check(int x)
{
    int res = 0;
    for(int i = 0; i < n; ++i) {
        if(x & (1 << i)) res += a[i];
    }
    return res == t;
}
int main()
{
    while(scanf("%d %d", &t, &n) && t && n) {
        mp.clear(), st.clear();
        for(int i = 0; i < n; ++i) scanf("%d", &a[i]);
        printf("Sums of %d:\n", t);
        for(int i = 0; i < (1 << n); ++i) {
            if(check(i)) {
                string temp = "";
                multiset<int> mlst;
                for(int j = 0; j < n; ++j)
                    if(i & (1 << j)) temp += a[j] + '0', mlst.insert(a[j]);
                if(!mp[mlst]) {
                    mp[mlst] = 1;
                    st.insert(temp);
                }
            }
        }
        int cnt = st.size();
        if(!cnt) puts("NONE");
        else {
            for(auto i: st) {
                int len = i.length();
                for(int j = 0; j < len; ++j)
                    printf("%d%c", i[j] - '0', "+\n"[j == len - 1]);
            }
        }
    }
    return 0;
}

/*
4 6 4 3 2 2 1 1
5 3 2 1 1
400 12 50 50 50 50 50 50 25 25 25 25 25 25
0 0
*/

时间复杂度

\(O(2^{n}\cdot n)\),极限也就 \(2^{12}\cdot 12\)


\(B.\ Fling\)

题意

给定一个 \(7\times 8\) 的地图,上面分布着至多 \(12\) 个小球,每个小球可以向上下左右四个方向移动('U', 'L', 'R', 'D'

对于每一个小球,只有两种情况它无法移动

  • 其当前移动方向上没有小球

  • 其当前移动方向上紧挨着一个小球

小球会传递动能

  • 对于每一个小球 \(i\),若它在移动过程中撞到另一个小球 \(j\),那么 \(i\) 会立刻停在 \(j\) 的前一个位置,而 \(j\) 继续按当前方向移动
  • \(j\) 与 另一个小球 \(k\) 紧邻,那么 \(j\) 不动,\(k\) 按当前方向移动,直到它掉落,以此类推

输入格式

多组数据

输出格式

输出 \(i,\ j,\ k\) ,分别代表小球的横坐标、纵坐标和移动方向

若有多个答案,按照字典序最小的输出

题解

这是一个游戏,玩一玩就能理解题意

对于计算机而言,最好的策略就是一步一步尝试

任取一个球,向四个方向搜索,若搜索成功,则更新图,继续搜索;若搜索失败,则回溯,把图复原,换方向继续搜索

\(dfs\) 实际上就是一个穷尽所有可能,搜索、回溯的过程

  • 往下搜索更新状态
  • 往上回溯复原状态

考虑可行性剪枝

用一个字符串维护答案,若当前得到的答案字符串已经比我们之前的答案大,那么没必要继续搜索,返回即可

注意开一个临时数组 \(temp\_g\) 存储地图,便于回溯的时候复原地图,但是 \(temp\_g\) 得设置成局部变量

\(code\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int, int>
#define arrayDebug(a, l, r) for(int i = l; i <= r; ++i) printf("%d%c", a[i], " \n"[i == r])
typedef long long LL;
typedef unsigned long long ULL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int DX[] = {0, -1, 0, 1, 0, -1, -1, 1, 1};
const int DY[] = {0, 0, 1, 0, -1, -1, 1, 1, -1};
const int MOD = 1e9 + 7;
const int N = 4e2 + 7;
const double PI = acos(-1);
const double EPS = 1e-6;
using namespace std;

inline int read()
{
    char c = getchar();
    int ans = 0, f = 1;
    while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
    while(isdigit(c)) {ans = ans * 10 + c - '0'; c = getchar();}
    return ans * f;
}
const int n = 7, m = 8;
const int D1[] = {-1, 0, 0, 1};
const int D2[] = {0, -1, 1, 0};
const char D[] = {'U', 'L', 'R', 'D'};
int vis[20][20], caseNum;
char g[20][20];
string ans;
struct node
{
    int x, y;
    char d;
    node() {}
    node(int _x, int _y, char _d) {x = _x, y = _y, d = _d;}
};


bool check(int s, int t) {return s >= 0 && s < n && t >= 0 && t < m;}

void dfs(int num, string &str)
{
    if(num == 1) {
        if(ans != "" && ans > str || ans == "") ans = str;
        return;
    }
    for(int i = 0; i < n; ++i) {
        for(int j = 0; j < m; ++j) {
            if(g[i][j] == 'O') {
                for(int k = 0; k < 4; ++k) {
                    if(check(i + D1[k], j + D2[k]) && g[i + D1[k]][j + D2[k]] == 'O') continue;
                    int f = 0;
                    for(int s = i + D1[k], t = j + D2[k]; check(s, t); s += D1[k], t += D2[k]) {
                        if(g[s][t] == 'O') {
                            f = 1;
                            break;
                        }
                    }
                    if(!f) continue;
                    else {//进入这个分支,就保证了从 i, j 往 k 方向移动球,一定可以移动
                        char temp_g[20][20] = {0}; //这个 temp 不能设为全局变量,否则会出错
                        memcpy(temp_g, g, sizeof(g));
                        g[i][j] = 'X';
                        for(int s = i + D1[k], t = j + D2[k]; check(s, t); s += D1[k], t += D2[k]) {
                            if(g[s][t] == 'O') {
                                g[s - D1[k]][t - D2[k]] = 'O';
                                g[s][t] = 'X';
                            }
                        }
                        str += i + '0', str += j + '0', str += D[k];
                        //cout<<str<<endl;
                        if(ans != "" && str > ans) { // 可行性剪枝,字典序小的答案不继续搜索
                            for(int it = 0; it < 3; ++it) str.erase(--str.end());
                            return;
                        }
                        dfs(num - 1, str);
                        for(int it = 0; it < 3; ++it) str.erase(--str.end()); //删除迭代器比赋值更快一点
                        memcpy(g, temp_g, sizeof(temp_g));
                    }
                }
            }
        }
    }
    return;
}

int main()
{
    while(~scanf("%s", g[0])) {
        for(int i = 1; i < n; ++i)
            scanf("%s", g[i]);
        int cnt = 0;
        for(int i = 0; i < n; ++i)
            for(int j = 0; j < m; ++j)
                if(g[i][j] == 'O') ++cnt;
        string res = "";
        dfs(cnt, res);
        if(caseNum) puts(""); //诡异的输出格式
        printf("CASE #%d:\n", ++caseNum);
        if(ans != "") {
            int len = ans.length();
            for(int i = 0; i < len; i += 3) {
                putchar(ans[i]), putchar(' ');
                putchar(ans[i + 1]), putchar(' ');
                putchar(ans[i + 2]), puts("");
            }
        }
        memset(g, 0, sizeof(g));
        ans = "";
    }
    return 0;
}

后记

我一开始打算穷尽所有可能,把可行的答案字符串全部存起来,最后排序输出最小的

不幸得,代码 \(T\)

迫不得已考虑可行性剪枝,结果时间不仅减下来了,代码长度也缩减了很多


\(C.\ N\) 皇后

题意

\(N\times N\) 的棋盘上放置 \(N\) 个皇后,任意两个皇后需要满足

  • 不在同一行
  • 不在同一列
  • 不处在与棋盘呈 \(45^{\circ}\) 角的直线上

输入格式

多组数据,\(N\leq 10\)

输出格式

输出方案数

题解

经典题,考虑 \(dfs\)

枚举行,用 \(col[i],\ d1[i],\ d2[i]\) 维护列,两个对角线

对于处在斜率为 \(-1\) 的同一条直线上的两个皇后 \(i_{1},\ j_{1},\ i_{2},\ j_{2}\),满足 \(i_{1} - j_{1} = i_{2} - j_{2}\)

对于处在斜率为 \(1\) 的同一条直线上的两个皇后 \(i_{1},\ j_{1},\ i_{2},\ j_{2}\),满足 \(i_{1} + j_{1} = i_{2} + j_{2}\)

用上述条件进行可行性剪枝即可

对于此题,先预处理出答案,然后回答查询

\(code\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int, int>
#define arrayDebug(a, l, r) for(int i = l; i <= r; ++i) printf("%d%c", a[i], " \n"[i == r])
typedef long long LL;
typedef unsigned long long ULL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int DX[] = {0, -1, 0, 1, 0, -1, -1, 1, 1};
const int DY[] = {0, 0, 1, 0, -1, -1, 1, 1, -1};
const int MOD = 1e9 + 7;
const int N = 4e2 + 7;
const double PI = acos(-1);
const double EPS = 1e-6;
using namespace std;

inline int read()
{
    char c = getchar();
    int ans = 0, f = 1;
    while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
    while(isdigit(c)) {ans = ans * 10 + c - '0'; c = getchar();}
    return ans * f;
}

int n, col[20];
map<int, int> mp1, mp2;

void dfs(int x, int n, int &ans)
{
    if(x == n + 1) {
        ans++;
        return;
    }
    for(int i = 1; i <= n; ++i) {
        if(!col[i] && !mp1[i - x] && !mp2[i + x]) {
            mp1[i - x] = mp2[i + x] = 1;
            col[i] = 1;
            dfs(x + 1, n, ans);
            mp1[i - x] = mp2[i + x] = 0;
            col[i] = 0;
        }
    }
}
int main()
{
    int ans[20] = {0};
    for(int i = 1; i <= 10; ++i) {
        memset(col, 0, sizeof(col));
        mp1.clear(), mp2.clear();
        dfs(1, i, ans[i]);
    }

    while(scanf("%d", &n) && n) {
        printf("%d\n", ans[n]);
    }
    return 0;
}

/*
1
8
5
0
*/

后记

这题一开始没有预处理,狂 \(T\)


\(D.\ Find\ a\ way\)

题意

给定一个 \(n\times m\) 的地图,上面分布了多个 \(KFC\)# 表示不可走,其他均表示可走,走一个单位耗时 \(11\) 分钟

两个小憨批 \(Y,\ M\) 约着在某个 \(KFC\) 相遇,假设他们同时出发,求他们的最短相遇时间

输入格式

多组数据,\(2\leq n,\ m\leq 200\)

输出格式

输出最短相遇时间

题解

\(Y,\ M\) 为起点分别作 \(1\)\(bfs\),对每个 \(KFC\) 维护一个俩人相遇所耗费的时间,最后取最小值即可

\(code\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int, int>
#define arrayDebug(a, l, r) for(int i = l; i <= r; ++i) printf("%f%c", a[i], " \n"[i == r])
typedef long long LL;
typedef unsigned long long ULL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int DX[] = {0, -1, 0, 1, 0, -1, -1, 1, 1};
const int DY[] = {0, 0, 1, 0, -1, -1, 1, 1, -1};
const int MOD = 1e9 + 7;
const int N = 4e2 + 7;
const double PI = acos(-1);
const double EPS = 1e-6;
using namespace std;

inline int read()
{
    char c = getchar();
    int ans = 0, f = 1;
    while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
    while(isdigit(c)) {ans = ans * 10 + c - '0'; c = getchar();}
    return ans * f;
}

int n, m, dis[207][207], vis[207][207];
char g[207][207];
struct node
{
    int x, y, step;
    node() {}
    node(int _x, int _y, int _step) {x = _x, y = _y, step = _step;}
};

void bfs(int s, int t)
{
    queue<node> q;
    q.push(node(s, t, 0));
    vis[s][t] = 1;
    while(!q.empty()) {
        node temp = q.front(); q.pop();
        if(g[temp.x][temp.y] == '@') dis[temp.x][temp.y] += temp.step;
        for(int i = 1; i <= 4; ++i) {
            int sx = temp.x + DX[i], sy = temp.y + DY[i];
            if(sx >= 1 && sx <= n && sx >= 1 && sy <= m && g[sx][sy] != '#' && !vis[sx][sy]) {
                q.push(node(sx, sy, temp.step + 1));
                vis[sx][sy] = 1;
            }
        }
    }
}

int main()
{
    while(~scanf("%d %d", &n, &m)) {
        memset(dis, 0, sizeof(dis));
        for(int i = 1; i <= n; ++i)
            scanf("%s", g[i] + 1);
        for(int i = 1; i <= n; ++i) {
            for(int  j = 1; j <= m; ++j) {
                if(g[i][j] == 'Y' || g[i][j] == 'M') memset(vis, 0, sizeof(vis)), bfs(i, j);
            }
        }
        int ans = inf;
        for(int i = 1; i <= n; ++i)
            for(int j = 1; j <= m; ++j)
                if(g[i][j] == '@') ans = min(ans, dis[i][j]);
        printf("%d\n", ans * 11);
    }
    return 0;
}
posted @ 2020-04-09 14:27  徐摆渡  阅读(159)  评论(0编辑  收藏  举报