[kuangbin带你飞]专题二 搜索进阶

A - Eight

康托展开 + bfs

题意:

给你一个图形,由1,2,3…8和x构成,其中x可以与上下左右的方块相互交换位置,给出的一个图形,求把它通过最少的交换操作从而摆回指定位置的做法。

思路:

首先这道题有多组数据,如果每次都用bfs进行搜索绝对会爆时间。但是由于最终的状态相同,所以可以尝试倒着搜索,这样就只需要bfs一次。
由于每次的状态都是一个矩阵所以不好标记。那么就需要对这些矩阵进行映射,把x设为0,那么矩阵就是由0-8这九个数字构成的排列。所以可以用康托展开来计算。

代码
#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<set>
#define ll long long
#define eps 1e-8
#define INF 0x3f3f3f3f
#define pb push_back
#define endl '\n'
#define IO ios::sync_with_stdio(false)
using namespace std;
const ll maxn = 1e6 + 5;
const ll mod = 1e9 + 7;
struct node{
    int a[10],hsh;
    int p;
};
struct Path{//用来记录路径
    int nex;
    char c;
}path[maxn];
int f[10],x[4] = {0,0,1,-1},y[4] = {1,-1,0,0},vis[maxn];
char op[4] = {'l','r','u','d'};//由于是反向搜索,所以操作方向也是反着的
int getHash(node x){//康托展开
    int res = 0;
    for(int i = 0; i < 9; i++){
        int t = 0;
        for(int j = i + 1; j < 9; j++)
            if(x.a[j] > x.a[i])t++;
        res += f[8 - i] * t;
    }
    return res;
}
void print(int x){//打印路径
    if(x != -1)
        cout << path[x].c;
    else{
        cout << endl;
        return;
    }
    print(path[x].nex);
}
void bfs(){
    node st,nex;
    for(int i = 0; i < 9; i++)st.a[i] = (i + 1) % 9;//构造最终状态矩阵
    st.hsh = getHash(st);
    path[st.hsh].nex = -1;
    st.p = 8;
    queue<node> qu;
    qu.push(st);
    vis[st.hsh] = 1;
    while(!qu.empty()){
        node t = qu.front();
        qu.pop();
        int tx = t.p / 3,ty = t.p % 3;
        for(int i = 0; i < 4; i++){
            int xx = x[i] + tx,yy = ty + y[i];
            if(xx >= 0 && xx <= 2 && yy >= 0 && yy <= 2){
                nex = t;
                int g = xx * 3 + yy;
                swap(nex.a[g],nex.a[t.p]);
                nex.hsh = getHash(nex),nex.p = g;
                if(!vis[nex.hsh]){
                    vis[nex.hsh] = 1;//如果没被访问就标记
                    path[nex.hsh].nex = t.hsh;//记录路径
                    path[nex.hsh].c = op[i];
                    qu.push(nex);
                }
            }
        }
    }
}

int main(){
    IO,f[0] = 1;
    char c;
    node st;
    for(int i = 1; i <= 8; i++)
        f[i] = f[i - 1] * i;
    bfs();
    //cout << "---end---" << endl;
    while(cin >> c){
        if(c == 'x')st.a[0] = 0;
        else st.a[0] = c - '0';
        for(int i = 1; i < 9; i++){
            cin >> c;
            if(c == 'x')st.a[i] = 0;
            else st.a[i] = c - '0';
        }
        st.hsh = getHash(st);
        if(vis[st.hsh])
            print(st.hsh);
        else
            cout << "unsolvable" << endl;
    }
    return 0;
}

关于这道题,也有AA^*的做法,但是从时间上来说,没倒着bfs快,但是也是一种做法,以后学了AA^*会补出这种做法

B - Eight II

康托展开 + bfs + 预处理映射

题意

与上一题相同,但是最终的状态变成了指定的状态,也就是说倒着bfs不好使了

思路

此时就需要一些奇淫巧计了,经过观察可以发现所有的排列无外乎这几种

"012345678","102345678","120345678","123045678",
"123405678","123450678","123456078","123456708","123456780"

即按照x的位置,进行了分类,我们可以先处理一下这几种的bfs,把他们的路径预处理一下。
当新输入排列后,可以根据x的位置进行映射,从而把其实排列变成上面的一种并且得到一个映射关系,再用这个关系把最终的状态进行映射,然后就可以用已有的路径进行输出了

代码
#include<bits/stdc++.h>
#define ll long long
#define eps 1e-8
#define INF 0x3f3f3f3f
#define pb push_back
#define endl '\n'
#define IO ios::sync_with_stdio(false)
using namespace std;
const ll maxn = 5e5 + 5;
const ll mod = 1e9 + 7;
int fec[10],x[4] = {1,0,0,-1},y[4] = {0,-1,1,0},t;
int cnt = 0,cnt2;
char op[4] = {'d','l','r','u'};
string str[10] = {"012345678","102345678","120345678","123045678",
"123405678","123450678","123456078","123456708","123456780"};
char str2[maxn];

struct node{
    int loc,a[12],hsh;
};
struct path{
    int lst = -1;
    char c;
}vis[10][maxn];//储存所有路径

void read(node &x){//输入函数
    char str[12];
    scanf("%s",str);
    for(int i = 0; i < 9; i++)
        if(str[i] == 'X')x.loc = i,x.a[i] = 0;
        else x.a[i] = str[i] - '0';
}
int getHash(node x){
    int res = 0;
    for(int i = 0; i < 9; i++){
        int t = 0;
        for(int j = i + 1; j < 9; j++)
            if(x.a[j] > x.a[i])t++;
        res += t * fec[8 - i];
    }
    return res;
}

void bfs(node st,int k){//普通的bfs,与上题一致
    queue<node> qu;
    qu.push(st);
    while(!qu.empty()){
        node t = qu.front();
        qu.pop();
        int gx = t.loc / 3,gy = t.loc % 3;
        for(int i = 0; i < 4; i++){
            int xx = gx + x[i],yy = gy + y[i];
            node nex = t;
            if(xx >= 0 && yy >= 0 && xx < 3 && yy < 3){
                nex.loc = xx * 3 + yy;
                swap(nex.a[nex.loc],nex.a[t.loc]);
                nex.hsh = getHash(nex);
                if(vis[k][nex.hsh].lst == -1){
                    qu.push(nex);
                    vis[k][nex.hsh].lst = t.hsh;
                    vis[k][nex.hsh].c = op[i];
                }
            }
        }
    }
}
void init(){
    fec[0] = 1;//初始化函数,计算所有的状态的路径
    for(int i = 1; i <= 9; i++)
        fec[i] = fec[i - 1] * i;
    for(int i = 0; i < 9; i++){
        node st;
        st.loc = i;
        for(int j = 0; j < 9; j++)
            st.a[j] = str[i][j] - '0';
        st.hsh = getHash(st);
        bfs(st,i);
    }
}
void print(int lst,int endd,int c){
    if(lst == endd)return;
    else print(vis[c][lst].lst,endd,c);
    str2[cnt2++] = vis[c][lst].c;
}
int main(){
    int mp[10];//储存映射
    init();
    scanf("%d",&t);
    while(t--){
        int core = 0;
        node st,ed;
        read(st);
        for(int i = 0; i < 9; i++)
            mp[st.a[i]] = str[st.loc][i] - '0',st.a[i] = mp[st.a[i]];//进行映射
        st.hsh = getHash(st);
        read(ed);
        for(int i = 0; i < 9; i++){//对终状态也进行映射
            ed.a[i] = mp[ed.a[i]];
            if(ed.a[i] == 0)ed.loc = i;
        }
        ++cnt;//case++
        cnt2 = 0;
        ed.hsh = getHash(ed);
        print(ed.hsh,st.hsh,st.loc);//打印
        str2[cnt2] = '\0';
        printf("Case %d: %d\n",cnt,cnt2);
        printf("%s\n",str2);
    }
    return 0;
}

C - 哈密顿绕行世界问题

dfs + 建图

思路:

很直白的一道题,直接dfs就可以了,用一个数组储存路径,建一下图

代码:
#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<set>
#define ll long long
#define eps 1e-8
#define INF 0x3f3f3f3f
#define pb push_back
#define endl '\n'
#define IO ios::sync_with_stdio(false)
using namespace std;
const ll maxn = 1e6 + 5;
const ll mod = 1e9 + 7;
int h[maxn],nex[maxn],e[maxn],cnt = 1;
int m,vis[maxn],ans[maxn],tl;
void add(int a,int b){
    e[cnt] = b;
    nex[cnt] = h[a];
    h[a] = cnt++;
}
void print(){
    printf("%d:  %d",tl + 1,m);
    for(int i = 1; i <= 19; i++)
        printf(" %d",ans[i]);
    printf(" %d\n",m);
}
void dfs(int x,int cnt){
    int nexx[5],b = 0;
    for(int i = h[x]; i != 0; i = nex[i])
        nexx[++b] = e[i];
    sort(nexx + 1,nexx + 4);//排一下顺序,因为要字典序
    for(int i = 1; i <= 3; i++){
        if(!vis[nexx[i]]){
            ans[cnt + 1] = nexx[i];
            vis[nexx[i]] = 1;
            dfs(nexx[i],cnt + 1);
            vis[nexx[i]] = 0;
        }else if(m == nexx[i] && cnt == 19){
            print();
            tl++;
            return;
        }
    }
}
int main(){
    int a,b,c;
    for(int k = 1; k <= 20; k++){
        scanf("%d %d %d",&a,&b,&c);
        add(k,a),add(k,b),add(k,c);//建图
    }
    while(scanf("%d",&m) && m){
        vis[m] = 1,tl = 0;
        dfs(m,0);
        vis[m] = 0;
    }
    return 0;
}

D - Escape

预处理 + bfs

题意:

意思就是,有两个队在打架,A必须从左上角跑到右下角,但是过程中存在着一些炮塔,会以一定间隔和速度进行射击,A有一定的精力,每过一个单位时间都会耗费精力,不论是耗尽精力还是被射中都会使A失败,问你A是否存在可能成功,炮塔进行射击时射出去的子弹当且仅当和人在同一时刻重合时才算射中。

思路

我最怕这种题了,所以借鉴了一下别人的思路,意思就是要预处理一下每个时刻哪里会有子弹

代码
#include<bits/stdc++.h>
#define ll long long
#define eps 1e-8
#define INF 0x3f3f3f3f
#define pb push_back
#define endl '\n'
#define IO ios::sync_with_stdio(false)
using namespace std;
const ll maxn = 5e5 + 5;
const ll mod = 1e9 + 7;
int n,m,k,d,x[5] = {0,0,0,-1,1},y[5] = {0,-1,1,0,0};
bool vis[1005][105][105];
bool vis2[1005][105][105];
bool mp[105][105];
struct cas{
    int x,y,t,v;
    int dx,dy;
}P[maxn];
struct node{
    int x,y,tim;
};
int bfs(){
    memset(vis2,0,sizeof vis2);
    queue<node> qu;//普通的bfs
    qu.push({0,0,0});
    vis2[0][0][0] = 1;
    while(!qu.empty()){
        node t = qu.front();
        qu.pop();
        if(t.x == n && t.y == m)return t.tim;
        if(t.tim >= d)break;
        for(int i = 0; i < 5; i++){
            int xx = t.x + x[i],yy = t.y + y[i];
            if(xx >= 0 && xx <= n && yy >= 0 && yy <= m && !mp[xx][yy] && !vis[t.tim + 1][xx][yy] && !vis2[t.tim + 1][xx][yy]){
                vis2[t.tim + 1][xx][yy] = true;
                qu.push({xx,yy,t.tim + 1});
            }
        }
    }
    return -1;
}
void ini(){//预处理
    memset(vis,0,sizeof vis);
    for(int i = 1; i <= k; i++)//枚举炮台
        for(int t = 0; t <= d; t += P[i].t){//时间每次加上炮台发射的时间间隔
            int f = 1;//枚举当前和炮台的距离
            while(true){
                int xx = P[i].x + f * P[i].dx,yy = P[i].y + P[i].dy * f;//当前枚举到的格子
                if(xx < 0 || xx > n || yy > m || yy < 0 || mp[xx][yy])break;//越界或打到炮台都不行
                if(f % P[i].v == 0)vis[t + f / P[i].v][xx][yy] = true;//标记哪一时刻会有子弹
                f++;
            }
        }
}

int main(){
    char c;
    while(~scanf("%d%d%d%d",&n,&m,&k,&d)){
        memset(mp,0,sizeof mp);
        for(int i = 1; i <= k; i++){
            getchar();
            scanf("%c%d%d%d%d",&c,&P[i].t,&P[i].v,&P[i].x,&P[i].y);
            if(c == 'W')P[i].dx = 0,P[i].dy = -1;
            else if(c == 'E')P[i].dx = 0,P[i].dy = 1;
            else if(c == 'N')P[i].dx = -1,P[i].dy = 0;
            else P[i].dx = 1,P[i].dy = 0;//处理子弹的方向
            mp[P[i].x][P[i].y] = true;
        }
        ini();
        int ans = bfs();
        if(ans == -1)printf("Bad luck!\n");
        else printf("%d\n",ans);
    }
    return 0;
}

E - DNA sequence

迭代加深搜索

思路:

就是迭代加深搜索,如果正常的dfs的话是会爆炸的,因为会一直搜下去没有底,所以要加入一种估计函数,估计当前的深度到达目标时还需要多少步,如果得到的结果比我们枚举的结果要大的话就可以结束当前的层。

#include<bits/stdc++.h>
#define ll long long
#define eps 1e-8
#define INF 0x3f3f3f3f
#define pb push_back
#define endl '\n'
#define IO ios::sync_with_stdio(false)
using namespace std;
const ll maxn = 5e5 + 5;
const ll mod = 1e9 + 7;
int t,n,pos[10],dep;//pos表示当前每个序列被匹配的位置
string str = "ACGT";
struct node{
    string s;
    int len;
}a[10];
int predict(){//乐观估计函数
    int res = 0;
    for(int i = 1; i <= n; i++)
        res = max(res,a[i].len - pos[i]);//找出最长未匹配的段
    return res;
}
int dfs(int x){
    if(x + predict() > dep)return 0;//大于枚举的答案就退出
    if(!predict())return 1;//如果预测是0则说明匹配完成
    int pre[10];
    for(int i = 0; i < 4; i++){//枚举四种核酸
        int mark = 0;
        for(int j = 1; j <= n; j++)pre[j] = pos[j];//储存当前匹配的位置用于回溯
        for(int j = 1; j <= n; j++)
            if(a[j].s[pos[j]] == str[i])
                mark = 1,pos[j]++;//如果匹配就加一
        if(mark){//只要能匹配就往下走走看
            if(dfs(x + 1))return 1;//下一层循环
            for(int j = 1; j <= n; j++)pos[j] = pre[j];//回溯
        }
    }
    return 0;
}
int main(){
    IO;
    cin >> t;
    while(t--){
        cin >> n;
        dep = 0;//dep就是枚举的答案
        for(int i = 1; i <= n; i++){
            cin >> a[i].s;
            a[i].len = a[i].s.size();
            dep = max(a[i].len,dep);//算所有子序列中最长的
            pos[i] = 0;
        }
        while(true){
            if(dfs(0))//从深度为0开始搜索
                break;
            dep++;
        }
        cout << dep << endl;
    }
    return 0;
}

FGH三题没写

I - A计划

bfs

思路:

没啥好说的,就是bfs,遇到#就跳一层,然后扔到队列里搜就完了

代码:
#include<bits/stdc++.h>
#define ll long long
#define eps 1e-8
#define INF 0x3f3f3f3f
#define pb push_back
#define endl '\n'
#define IO ios::sync_with_stdio(false)
using namespace std;
const ll maxn = 1e6 + 5;
const ll mod = 1e9 + 7;
char mp[3][15][15];
int n,m,t,c,sx,sy,sz,ex,ey,ez;
int y[4] = {0,0,1,-1},z[4] = {1,-1,0,0};
string str;
struct node{
    int x,y,z,cnt;
};
void bfs(){
    int tim = -1;
    queue<node> qu;
    qu.push({sx,sy,sz,0});
    while(qu.size()){
        node t = qu.front();
        qu.pop();
        if(t.x == ex && t.y == ey && t.z == ez){
            tim = t.cnt;
            break;
        }
        for(int i = 0; i < 4; i++){
            int yy = t.y + y[i],zz = t.z + z[i],xx = t.x;
            if(yy >= 1 && yy <= n && zz >= 1 && zz <= m){

                if(mp[xx][yy][zz] == '.'){
                    mp[xx][yy][zz] = '@';
                    qu.push({xx,yy,zz,t.cnt + 1});
                }else if(mp[xx][yy][zz] == '#' && mp[3 - xx][yy][zz] == '.'){
                    mp[3 - xx][yy][zz] = '@';
                    mp[xx][yy][zz] = '@';
                    qu.push({3 - xx,yy,zz,t.cnt + 1});
                }
            }
        }
    }
    if(tim > t || tim == -1)cout << "NO" << endl;
    else cout << "YES" <<endl;
}
int main(){
    IO;
    cin >> c;
    while(c--){
        cin >> n >> m >> t;
        for(int i = 1; i <= 2; i++)
            for(int j = 1; j <= n; j++)
                for(int k = 1; k <= m; k++){
                    cin >> mp[i][j][k];
                    if(mp[i][j][k] == 'S')
                        sx = i,sy = j,sz = k;
                    if(mp[i][j][k] == 'P')
                        ex = i,ey = j,ez = k,mp[i][j][k] = '.';
                }
        bfs();
    }
    return 0;
}

J - Travelling

状压dp

题意:

有一个人要去旅行,他可以选一个起始位置,然后走遍所有的城市,每一个城市最多走两次,问你走完所有的城市的的最少花费

思路:

一看输入数据范围才10,就想着这题是水题,然后成功TLE。如果爆搜的话假设这道题是一个完全图,那么最坏情况下要搜(10!)2(10!)^2次,妥妥超时。看在数据范围这么低,那么就是状压没跑了。
首先用dp[i][j]dp[i][j]表示在jj状态下到达ii所需要花费的费用, 然后用三进制表示状态然后转移就可以了,复杂度为O(3nn2)O(3^n * n^2)比阶乘要低

代码
#include<bits/stdc++.h>
#define ll long long
#define eps 1e-8
#define INF 0x3f3f3f3f
#define pb push_back
#define endl '\n'
#define IO ios::sync_with_stdio(false)
using namespace std;
const ll N = 2e5;
const ll mod = 1e9 + 7;
int n,m,G[12][12],dp[12][N];
int bit[12],f[12];
int getBit(int x){
    int cnt = 0;
    for(int i = 0; i < n; i++){
        bit[i] = x % 3;
        if(bit[i])cnt++;
        x /= 3;
    }
    return cnt;
}
int main(){
    f[0] = 1;
    int a,b,c,ans = INF;
    for(int i = 1; i <= 11; i++)
        f[i] = f[i - 1] * 3;
    while(~scanf("%d %d",&n,&m)){
        ans = INF;
        memset(dp,0x3f,sizeof dp);
        memset(G,0x3f,sizeof G);
        for(int i = 1; i <= m; i++){
            scanf("%d %d %d",&a,&b,&c);
            a--,b--;
            if(a != b)G[a][b] = G[b][a] = min(G[a][b],c);
        }
        for(int i = 1; i < f[n]; i++){
            int num = getBit(i);
            for(int j = 0; j < n; j++){
                if(bit[j]){
                    if(num == 1)dp[j][i] = 0;
                    if(dp[j][i] == INF)continue;
                    if(num == n)ans = min(ans,dp[j][i]);
                }
                for(int k = 0; k < n; k++){
                    if(bit[k] <= 1 && G[j][k] != INF && j != k){
                        dp[k][i + f[k]] = min(dp[j][i] + G[k][j],dp[k][i + f[k]]);
                    }
                }
            }
        }
        if(ans == INF)printf("-1\n");
        else printf("%d\n",ans);
    }
    return 0;
}
展开阅读全文
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值