简单stl和搜索题单

做题记录

  • 简单stl
    • set
    • string
  • 搜索
    • 简单搜索
      • [洛谷P3956 [NOIP2017 普及组] 棋盘](https://www.luogu.com.cn/problem/P3956)
      • [洛谷P1379 八数码难题](https://www.luogu.com.cn/problem/P1379)
    • 剪枝优化
      • [AcWing 165. 小猫爬山](https://www.acwing.com/problem/content/description/167/)
    • 记忆化搜索
      • [洛谷P1434 [SHOI2002]滑雪](https://www.luogu.com.cn/problem/P1434)
      • [P1535 [USACO08MAR]Cow Travelling S](https://www.luogu.com.cn/problem/P1535)
      • 洛谷P1514引水入城
    • IDA*
      • 洛谷P2534铁盘整理
      • [洛谷 P2324 [SCOI2005]骑士精神](https://www.luogu.com.cn/problem/P2324)

简单stl

set

了解set 4个集合间的操作

#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
void test(){
	set x1,x2;
	//do something ...
	set_union(ALL(x1), ALL(x2), INS(x));//并集操作
	set_intersection(ALL(x1), ALL(x2), INS(x));//交集操作
	set_difference(ALL(x1), ALL(x2), INS(x))//取差集
	set_symmetric_difference(ALL(x1), ALL(x2), INS(x))//取对称差集
}

string

UVA-1593
代码对齐水题
水得晕厥,但是还被坑了,最后一列不可以再补上空格。这种格式题就是瞻前顾后,边界最容易出错。而且题意要看清不能马马虎虎过,对输出格式完全理解才开始写。

#include
using namespace std;
string str[1005][1000];
int cnt[2000];
int mx_col;
int mx_len[2000];
int main(){
    string s;
    int num=0;
    while(getline(cin,s)){
        stringstream ss(s);
        while(ss>>str[num][cnt[num]]){
            cnt[num]++;mx_col=max(mx_col,cnt[num]);
        }
        num++;
    }
    for(int i=0;i<mx_col;i++){
        for(int j=0;j<num;j++){
            mx_len[i]=max(mx_len[i],(int)str[j][i].length());
        }
    }
    for(int i=0;i<num;i++){
        for(int j=0;j<cnt[i];j++){
            if(j!=0)cout<<' ';
            cout<<str[i][j];
            if(j==cnt[i]-1){
                cout<<endl;break;
            }
            for(int k=0;k<mx_len[j]-str[i][j].length();k++)cout<<' ';
        }
    }
    return 0;
}

搜索

简单搜索

洛谷P3956 [NOIP2017 普及组] 棋盘

一开始想直接dp,dp[i][j][k]表示到达第i行第j列染色为k的格子最少花费的金币,但当k=0时,不知道它上次是保持了何种颜色,从没有颜色的格子到有颜色的格子这个状态比较难转移,还要记录上一次的选择颜色感觉太麻烦,需要回溯的样子,所以还是选择dfs写。忘记一个小小的剪枝和回溯位置放错又tle又wa,然后换bfs。写完ac之后顺便把dfs也检查出来了。
其实想用dp是因为看错题了以为只能从左上角向右下角移动,导致写dfs也是一样写法…所以完全理解题意再行动!(何况这是中文题呀!)

#include
using namespace std;
const int N=1e3+10;
const int inf=0x3f3f3f3f;
int c[N][N],ans,m,n;
int dx[4]={0,0,-1,1},dy[4]={1,-1,0,0};
int res[N][N];
void dfs(int x,int y,int cur,int add){
    if(cur>=res[x][y])return;//一开始忘记判断是否在当前情况还需要继续回溯 一直tle
    res[x][y]=cur;
    if(x==m&&y==m)ans=min(ans,cur);
    //dfs用于更新下一个状态 所以当前必定有颜色 但是可以回溯
    for(int i=0;i<4;i++){
        int nx=x+dx[i],ny=y+dy[i];
        if(nx<1||ny<1||nx>m||ny>m)continue;
        if(c[nx][ny]){
            if(c[x][y]==c[nx][ny])dfs(nx,ny,cur,0);
            else dfs(nx,ny,cur+1,0);
        }
        else if(!add){
            c[nx][ny]=c[x][y];
            dfs(nx,ny,cur+2,c[x][y]);
            c[nx][ny]=0;//一开始忘记在这里回溯了 只有30分
        }
    }
    if(add)c[x][y]=0;
}
int main(){
    cin>>m>>n;
    ans=inf;
    memset(res,0x3f,sizeof res);
    for(int i=1;i<=n;i++){
        int x,y,z;cin>>x>>y>>z;
        c[x][y]=z+1;
    }
    dfs(1,1,0,0);
    if(ans>=inf)cout<<-1<<endl;
    else cout<<ans<<endl;
    return 0;
}

就是个bfs的水题。情况有点多,把一样的操作归并在一起,把不同的一点点挑出来,可能代码量比较少,看起来也比较清晰。

#include
using namespace std;
const int N=1e3+10;
const int inf=0x3f3f3f3f;
int c[N][N],ans,m,n,dis[N][N];
int dx[4]={-1,1,0,0},dy[4]={0,0,1,-1};
int add[N];
struct Node{
    int x,y,dis,c;
    bool operator<(const Node a)const{return a.dis<dis;}
};
priority_queue<Node>q;
void bfs(){
    q.push({1,1,0,c[1][1]});
    while(!q.empty()){
        Node cur=q.top();q.pop();
        int x=cur.x,y=cur.y;
        if(x==m&&y==m){
            cout<<cur.dis<<endl;
            return ;
        }
        for(int i=0;i<4;i++){
            int nx=x+dx[i],ny=y+dy[i];
            if(nx<1||ny<1||nx>m||ny>m)continue;
            if(c[x][y]){
                int add=0;//把一样的操作归并在一起,把不同的一点点挑出来
                if(!c[nx][ny])add=2;
                else if(c[nx][ny]!=c[x][y])add=1;
                if(dis[nx][ny]>dis[x][y]+add){
                    dis[nx][ny]=dis[x][y]+add;
                    if(add==2)q.push({nx,ny,dis[nx][ny],c[x][y]});
                    else q.push({nx,ny,dis[nx][ny],c[nx][ny]});
                }
            }
            else {
                int add=0;
                if(!c[nx][ny])continue;
                if(cur.c!=c[nx][ny])add=1;
                if(dis[nx][ny]>dis[x][y]+add){
                    dis[nx][ny]=dis[x][y]+add;
                    q.push({nx,ny,dis[nx][ny],c[nx][ny]});
                }
            }
        }
    }
    cout<<-1<<endl;
}

洛谷P1379 八数码难题

可能这个题要用A*,哈希什么的,但是我就用一个dijkstra就过了,然后加了亿点点细节,其实主要就是用map实现游戏网格和数字的转换。有兴趣可以看一下,思路很简单,(但是要开O2优化才能过,我也不是很懂。)

#include
#include
#include
#include
#include
#include
using namespace std;
#define inf 0x3f3f3f3f
typedef pair<int,int> PII;
const int N=2E6+10;//只有362880种状态,每种状态最多可以向4个方向转移,4*362880=1,451,520,N为2E6则可。
//Part 1:------------------------------------------------------------------------链式前向星存图,降低时间&空间复杂度
int n,cnt,head[N],vis[N],dis[N];
struct E{
    int to,nxt,w;
}e[N];
void add(int u,int v,int w){
    e[++cnt]={v,head[u],w};
    head[u]=cnt;
}
//Part 2:------------------------------------------------------------------------处理序号和图之间的关系
map<int ,int>reflect;//reflect[i]=j:i保存整个图,有0位数,j相当于i的序号。i从小到大按顺序存储。
int dx[4]={-1,0,0,1},dy[4]={0,-1,1,0};//dx表示行的移动,dy表示列的移动
int power10[10];//保存10的幂次,避免重复计算
int match[N];//match[i]=j:第i个位置保存的是j图
void pre(){
    //获得0-8所有的排列
    int pos=0;
    int a[10]={0,1,2,3,4,5,6,7,8};
    do{
        int sum=0;
        for(int i=0;i<=8;i++){
            sum=sum*10+a[i];
        }match[++pos]=sum,reflect[sum]=pos;
    }while(next_permutation(a,a+9));

    //获得10的1-9的幂次结果
    power10[0]=1;
    for(int i=1;i<=9;i++)power10[i]=power10[i-1]*10;
}
//用序号寻找0的位置
PII ReflectToZeroPosition(int i){
    PII GridPosition;//GridPosition保存序号对应的图中0所在的位置(行,列)
    int tmp=match[i];
    int ArrayPosition=0;
    while(tmp%10!=0){
        tmp/=10;
        ArrayPosition++;
    }
    ArrayPosition=8-ArrayPosition;
    GridPosition={ArrayPosition/3,ArrayPosition%3};
    return GridPosition;
}
int CheckTransform(int CurState,int direction){//如果当前的状态可以向某个方向转移就直接返回那个方向的状态,否则返回-1
    PII CurPosition=ReflectToZeroPosition(CurState);
    int next_x=CurPosition.first+dx[direction],next_y=CurPosition.second+dy[direction];
    if(next_x>-1&&next_x<3&&next_y>-1&&next_y<3){
        int NextDigit=next_x*3+next_y,CurDigit=CurPosition.first*3+CurPosition.second;
        int tmp=match[CurState],num=tmp/power10[8-NextDigit]%10;
        tmp=tmp-num*power10[8-NextDigit]+num*power10[8-CurDigit];
        return reflect[tmp];
    }
    return -1;
}
void OutputGrid(int i){
    int grid=match[i];
    for(int i=8;i>-1;i--){
        printf("%d ",grid/power10[i]%10);
        if(i==6||i==3||i==0)
            printf("\n");
    }
}
//Part 3:---------------------------------------------------------------------------------- dijkstra计算单源最短路
struct Node{
    int dis,pos;
    bool operator <(const Node &x)const{
        return x.dis<dis;
    }
};
priority_queue<Node>q;
int s,ed,fa[N];//s为源点,ed为终点,fa用于记录路径,从终点沿着fa回溯到起始点。
void dijkstra(){
    q.push({0,s});
    dis[s] = 0;
    while(!q.empty()){
        Node cur=q.top();//每次都取出队列中到达源点距离最短的点(优先队列会自动对队列排序)
        if(cur.pos==ed)return;
        q.pop();
        if(vis[cur.pos])continue;
        vis[cur.pos]=1;
        for(int i=head[cur.pos];i;i=e[i].nxt){
            int v=e[i].to;
            if(dis[v]>dis[cur.pos]+e[i].w&&!vis[v]){
                fa[v]=cur.pos;
                dis[v]=dis[cur.pos]+e[i].w;
                q.push(Node{dis[v],v});
            }
        }

    }
}
stack<int>path;
int main(){
    pre();
    n=362880;
    ed=123804765;
    scanf("%d",&s);
    ed=reflect[123804765];
    s=reflect[s];
    for(int i=1;i<=n;i++)dis[i]=inf;
    for(int i=1;i<N;i++)e[i].w=1;
    for(int i=1;i<=n;i++){
        for(int j=0;j<4;j++){
            int v=CheckTransform(i,j);
            if(v!=-1){add(i,v,1);}
        }
    }
    dijkstra();
    int i=reflect[123804765];
    while(i!=0){
        path.push(i);
        i=fa[i];
    }
    int step=0;
    printf("%d\n",(int)path.size() - 1);
    return 0;
}

剪枝优化

其实剪枝我现在真的是摸不着头脑,把写过的奇技淫巧优秀剪枝方法都稍微记录一下。
dfs的框架其实很简单,每次dfs有一个当前状态,通过某些状态转移方法继续dfs,进入下一状态,若当前状态是目标状态就可以更新答案。
1.记忆化搜索:某个状态是否搜到就确定,是否一个状态会被一直重复。
2.约束满足:先搜分支少的,减少搜索层数。
3.若要求最优解,则可以通过比较当前状态的解和已经获得的最优解决定当前是否可以直接返回。

AcWing 165. 小猫爬山

一股dfs的浓浓的味道。将当前状态设为第 c u r cur cur只猫,即对第 c u r cur cur只猫进行分配,决定它待在哪个车里,目标状态设为 c u r = n + 1 cur=n+1 cur=n+1,即 n n n只猫都有车可搭了。状态转移方法则是将当前的猫 c u r cur cur放在不同编号的车里。
优化:

  1. 先将猫的重量从大到小排序,因为更重的猫接下去的分支会更少。
  2. 若当前车的数量已经不小于已经获得的答案,则不必再dfs下去,因为肯定会获得更多的车子数。
#include
using namespace std;
#define ll long long
const ll inf=1e18;
ll a[20];
ll w,n,ans;
bool vis[20];
bool cmp(ll x,ll y){return x>y;}
void dfs(ll cnt,ll last){
    if(cnt>=ans)return;//如果已经大于当前答案不必再搜
    int fl=1;
    for(int i=1;i<=n;i++){
        if(!vis[i]){
            fl=0;break;
        }
    }
    if(fl){
        if(last!=w)cnt++;
        ans=min(ans,cnt);
        return;
    }
    for(int i=1;i<=n;i++){
        if(!vis[i]){
            vis[i]=1;
            if(last<a[i]){
                dfs(cnt+1,w-a[i]);
            }
            else if(last==a[i]){
                dfs(cnt+1,w);
            }
            else dfs(cnt,last-a[i]);
            vis[i]=0;
        }
    }
}
int main(){
    cin>>n>>w;
    ans=inf;
    for(int i=1;i<=n;i++)cin>>a[i];
    sort(a+1,a+1+n,cmp);
    dfs(0,w);
    cout<<ans<<endl;
    return 0;
}

记忆化搜索

记忆化搜索就是一个非常棒的剪枝,在搜索到某个位置如果这个位置已经有答案了,换句话说到这里你已经知道再往下搜会是什么结果了(因为你之前已经搜过了),那么直接使用这个答案就好,不用再继续搜下去。使用场景大概是(根据我写的并不多的题),题目里的状态有明显的单调性,比如高度和次数,就是我们可以确定已经搜索到的答案具有唯一性,一旦搜到就不会再改变了,因为记忆化搜索就是dp和搜索的结合,要保证无后效性。

洛谷P1434 [SHOI2002]滑雪

先祭出一道最经典的题目,用简单的dfs会tle,原因是什么呢?有很多个地方会重复搜索。

1  2  3  4  5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

用样例来说明一下,我们从第一个位置搜索到最后一个位置。

    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            ans=max(ans,dfs(i,j));
        }
    }

当我们搜到 25 25 25那个位置,因为它是最高的点,会向右侧的 20 20 20继续dfs,换句话说,我们要知道 20 20 20的最长轨道才能判断是否用 20 20 20的轨道去更新 25 25 25的轨道。更新完 25 25 25就该更新 20 20 20了,这时又要累死累活地重新dfs去计算 20 20 20的最长轨道,但如果我们在更新 25 25 25的时候保存了 20 20 20的最长轨道,这时候就可以直接使用了, 20 20 20直接不用计算,轨道越长,我们省下来的时间越多。

#include
using namespace std;
const int N=3e2+10;
int g[N][N],dp[N][N];
int n,m,ans;
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
int dfs(int x,int y){
    if(dp[x][y]!=-1)return dp[x][y];
    int f1=0,f2=0;
    for(int i=0;i<4;i++){
        int nx=dx[i]+x,ny=dy[i]+y;
        if(nx<1||nx>n||ny<1||ny>m)continue;
        f1++;
        if(g[nx][ny]>=g[x][y]){
            f2++;
        }
        else dp[x][y]=max(dp[x][y],dfs(nx,ny)+1);
    }
    if(f1==f2)return dp[x][y]=1;
    return dp[x][y];
}
int main(){
    cin>>n>>m;
    memset(dp,-1,sizeof dp);
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>g[i][j];
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            ans=max(ans,dfs(i,j));
        }
    }
    cout<<ans<<endl;
    return 0;
}

P1535 [USACO08MAR]Cow Travelling S

首先想到暴力bfs,但某个状态会被重复放到队列中很多次,记忆化搜索的优化点是将这些重复的状态记录下来,状态dp[i][j][k]表示走到第i行第j列且当前花费k时的状态数。若dp[i][j][k]不为0,则说明这个状态已经在队列中出现过,且当前肯定还没有被弹出队列。为什么呢?bfs的过程是这样的:
简单stl和搜索题单_第1张图片
因此随着每次层数加深,k值不减,即记忆化的必然是同一层的,若第k时的状态数不为0,则它只可能在当前层出现过,因为还没有到达下一层,当前层的所有状态都还没有被弹出队列,所以只需要改变其状态数而没必要将该状态再次放入队列,即队列中此时必然存在该状态。
以后都不会代码补全了!s给我补全成x…

#include//参考题解
using namespace std;
const int N=1e2+10;
char g[N][N],dp[N][N][20];
int n,m,t,sx,sy,ex,ey;
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
struct Node{int x,y,s;};
void bfs(){
    queue<Node>q;
    Node node={sx,sy,0};
    q.push(node);
    dp[sx][sy][0]=1;
    while(!q.empty()){
        Node cur=q.front();q.pop();
        int x=cur.x,y=cur.y,s=cur.s;
        //cout<
        for(int i=0;i<4;i++){
            int nx=x+dx[i],ny=y+dy[i],ns=s+1;
            if(dp[nx][ny][ns]){
                dp[nx][ny][ns]+=dp[x][y][s];
                continue;
            }
            if(nx<1||ny<1||nx>n||ny>m||g[nx][ny]=='*'||ns>t)continue;
            dp[nx][ny][ns]=dp[x][y][s];
            q.push({nx,ny,ns});
        }
    }
    cout<<dp[ex][ey][t]<<endl;
}
void solve(){
    cin>>n>>m>>t;
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>g[i][j];
    cin>>sx>>sy>>ex>>ey;
    bfs();
}
int main(){
    int T=1;
    while(T--){
        solve();
    }
}

洛谷P1514引水入城

  1. 对每个点 d f s dfs dfs一次,就可以判断第 n n n行哪些点不可到达。
  2. 若第 n n n行所有点均可到达,那么第 1 1 1行流到第 n n n行会形成一个连续区间,利用反证法:假设第 1 1 1行第 a a a个点可以流到第 n n n行的最左边的点是l,最右边的点是 r r r,假设 l l l r r r之间还有其他点如图中的 s s s a a a可通过蓝色路径到达 l l l, r r r,因为第 n n n行每个点都有存在于第 1 1 1行的起始点流向它,假设 s s s点还有 d d d可以通过紫色路径流向它,因为蓝色路径将 s s s包围,因此紫色路径必然和蓝色路径存在交点 c c c,故 a a a必然可通过蓝色路径 a − c a-c ac和紫色路径 c − s c-s cs到达 s s s,故a可达 l l l, r r r之间的所有点,即 a a a可达点会形成连续区间。
  3. 最后再用最经典的贪心算法之区间覆盖,按照右端点从左到右排,只要下个点的左端点可以保证当前可覆盖,就一直右移直到覆盖全部区间即可。
    简单stl和搜索题单_第2张图片

采用记忆化搜索的方式记录第1行可达的左右区间,这样做需要对第 1 1 1行每个位置都进行1次dfs,但其实因为第 1 1 1行也有高度差,可以互达,有很多次的 d f s dfs dfs是重复的,妥妥超时。

void dfs(int fy,int x,int y){//对于第一行每个点fy都需要dfs一次才能获得其左右端点。
    vis[x][y]=fy;
    for(int i=0;i<4;i++){
        int nx=x+dx[i],ny=y+dy[i];
        if(nx<1||ny<1||nx>n||ny>m||g[nx][ny]>=g[x][y]||vis[nx][ny]==fy)continue;
        if(nx==n){
            dp[fy][0]=min(dp[fy][0],ny);
            dp[fy][1]=max(dp[fy][1],ny);
        }
        dfs(fy,nx,ny);
    }
}

改成记录每个点可达的左右端点,这样就不必每次都 d f s dfs dfs每个点了。

void dfs(int x,int y){
    vis[x][y]=1;
    for(int i=0;i<4;i++){
        int nx=x+dx[i],ny=y+dy[i];
        if(nx<1||ny<1||nx>n||ny>m||g[nx][ny]>=g[x][y])continue;
        if(!vis[nx][ny])dfs(nx,ny);
        l[x][y]=min(l[x][y],l[nx][ny]);
        r[x][y]=max(r[x][y],r[nx][ny]);
    }
}

最后是区间覆盖

struct Node{
    int l,r;
    bool operator<(const Node a)const{
        if(a.r==r)return l<a.l;
        return r<a.r;
    }
}node[N];

 sort(node+1,node+1+cnt);
 int num=0,cur=0;
 for(int i=1;i<=cnt;i++){
     if(i<cnt&&node[i+1].l<=cur+1)continue;//此处忘记+1...现在彻底会了
     num++;
     cur=node[i].r;
     if(cur==m)break;
 }

对记忆化搜索的理解深了一点,一开始那种应该是假的记忆化,因为其实每次都还是从上到下,并没有办法记录什么有用的信息。后面那种是从下往上,可以将已经计算好的结果一层层往上传。区间问题忘记+1…闭区间闭区间闭区间!

IDA*

粗浅的理解:对dfs的一种改进,假定最优解的步数已经知道,通过估值函数判断当前状态是否可以在限制的步数内向最优解转移。因为朴素的dfs具有很大的盲目性,IDA*通过估值函数赋予dfs一种"智能的性质“,让它在不可能达到解的时候停下来,进行剪枝优化。同时估值函数应该比真实值小,直观的理解是,太大的估值函数可以会”跨过“真实答案,而小的估值函数虽然需要更长的搜索时间,但可以获得答案。

洛谷P2534铁盘整理

  1. 一开始想的最简单的估价函数是总的错位数,设第 i i i大的盘子和第 j j j大的盘子的位置为 p o s [ i ] , p o s [ j ] pos[i],pos[j] pos[i],pos[j],则错位数为 ∑ i = 1 n ∑ j = i + 1 n a b s ( ( p o s [ i ] − p o s [ j ] ) − ( i − j ) ) \sum_{i=1}^n\sum_{j=i+1}^nabs((pos[i]-pos[j])-(i-j)) i=1nj=i+1nabs((pos[i]pos[j])(ij)),但这样的估值函数必定大于真实值。假设当前序列是 12354 12354 12354,我的估价函数结果为 4 4 4,但实际 2 2 2次翻转就可实现。
  2. 考虑到每次翻转至少会改变翻转位置前后的相对位置,比如翻转到 3 3 3位置,则 3 3 3 4 4 4的相对位置发生改变。将估值函数改变为(参考题解,我的小脑袋瓜没这么聪明):
int evaluate(){
    int cnt=0;
    for(int i=1;i<=n;i++){
        if(abs(a[i]-a[i+1])!=1)cnt++;
    }
    return cnt;
}

合理设计估价函数,既要保证它是有左右的,如以上函数保存相邻的错位值,我们知道每次翻转只能改变一对相邻数的差值,而我们的估价函数就是相邻数的最小差值,(无论差值是多大我们都只设置为1),从而保证我们的估价函数小于真实值。设计的时候要知道我们的步骤可以优化“什么”(比如此题的“什么”就是相邻数差值),通过”什么“来设计估值函数,其实是用预设的最优解的作用来设计估价函数。

洛谷 P2324 [SCOI2005]骑士精神

虽然知道会错但还是尝试思考的思路
这道题和上一道差不多,只是有最多的步数限制,我们先枚举枚举从0到15的步数,然后设计估价函数,最后利用估价函数去协助dfs过程剪枝。
估价函数设计:我们从答案出发,即步数可以对什么产生影响,每移动一个骑士,我们至多可以改变一个错位的骑士数,也可能无法改变错位骑士数量,比如当空格在黑色阵营内,同时我们移动的是黑色骑士。因此我们可以设计估价函数为当前的白色与黑色错位骑士数量,这样的估价函数可以确保小于真实步数。

char g[10][10];
const char const_g[6][6]={"11111","01111","00*11","00001","00000"};
int evaluate(){
    int cnt;
    for(int i=0;i<5;i++){
        for(int j=0;j<5;j++){
            if(g[i][j]!=const_g[i][j])cnt++;
        }
    }
    return cnt;
}

根据题意,只有空格附近八个位置才能移动。
简单stl和搜索题单_第3张图片
最小步数从小到大枚举,若当前步数可以达到指定的目标状态则结束。在dfs中传入4个参数,分别是当前空格的坐标,当前已经使用的步数,和当前估价函数的值。在dfs过程中枚举空格旁边8个位置,再更新一下估值。更新之后取下一个位置继续dfs,同时增加当前步数,dfs返回之后回溯。
以下是全T代码…

#include
using namespace std;
char g[10][10];
const char ans_g[6][6]={"11111","01111","00*11","00001","00000"};
int dx[8]={-2,-2,2,2,1,1,-1,-1},dy[8]={1,-1,1,-1,2,-2,2,-2};
int ans;
bool fl;
int evaluate(){
    int cnt=0;
    for(int i=0;i<5;i++){
        for(int j=0;j<5;j++){
            if(g[i][j]!='*'&&g[i][j]!=ans_g[i][j])cnt++;
        }
    }
    return cnt;
}
void dfs(int x,int y,int eval,int step){
    if(fl)return;
    if(step==ans){
        if(!eval){
           cout<<ans<<endl;
            fl=1;
        }
        return;
    }
    for(int i=0;i<8;i++){
        int tmp=eval;
        int nx=x+dx[i],ny=y+dy[i];
        if(nx<0||ny<0||nx>4||ny>4)continue;
        if(ans_g[x][y]==g[nx][ny])tmp--;
        if(ans_g[nx][ny]==g[nx][ny])tmp++;
        swap(g[nx][ny],g[x][y]);
        dfs(nx,ny,tmp,step+1);
        swap(g[nx][ny],g[x][y]);
    }
}
void solve(){
    fl=0;
    for(int i=0;i<5;i++)for(int j=0;j<5;j++)cin>>g[i][j];
    int x=-1,y=-1;
    for(int i=0;i<5;i++){
        for(int j=0;j<5;j++){
            if(g[i][j]=='*'){
                x=i;y=j;break;
            }
        }if(x!=-1)break;
    }
    for(ans=0;ans<=15;ans++){
        dfs(x,y,evaluate(),0);
        if(fl)return ;
    }
    if(!fl)cout<<-1<<endl;
    return ;
}
int main(){
    int T;cin>>T;
    while(T--)
        solve();
    return 0;
}

下一步就是最关键的一步!看题解…
(知道一定会错还是努力地去尝试,也许这就是骑士精神吧!)
简单stl和搜索题单_第4张图片
最大问题:我根本就没有用到我的估价函数…只要在每次进行下一次的dfs加上下面这句话就可以过!

if(evaluate()+step<=ans)//上述简直是失去灵魂的IDA*!!

你可能感兴趣的:(算法,算法,数据结构)