搜索专题---记忆化搜索

不记得在哪里看到过,记忆化搜索的本质其实就是DP,但看在它叫做搜索,就暂且把它搁在这儿吧~~

记忆化搜索,顾名思义,就是在搜索的同时,记忆每一次搜索到的状态的结果,下次再碰到这个状态,就可以直接得到结果了.

可以看出记忆化搜索保证了每一个状态只搜索一次,从而大大地提高运行效率.

我们常把搜索到的结果(即要记忆的东西)放入一个数组中.

[SHOI2002] 滑雪

int n,m,ans;
int high[105][105],f[105][105];
int dx[4]={0,0,1,-1},
    dy[4]={1,-1,0,0};
int dfs(int x,int y){
    if(f[x][y])return f[x][y];
//之前搜过这个状态,就可以直接调用已经记忆好的结果
//这里体现了记忆化的好处
    int cnt=1;
//不论从哪个点开始出发,长度初始都为1(即出发点本身)
    for(int i=0;i<=3;i++){
        int xx=x+dx[i],yy=y+dy[i];
        if(xx>=1&&xx<=n&&yy>=1&&yy<=n&&high[xx][yy]<high[x][y])
//high[xx][yy]与high[x][y]的关系大于或小于都可以
//大于,相当于以[x,y]为终点.逆序向起点搜索
//小于,相当于以[x,y]为起点,顺序向终点搜索
            cnt=max(dfs(xx,yy)+1,cnt);
//这里+1一定不能漏
//上面cnt=1算的是起点,这里+1加的是终点
    }
    return f[x][y]=cnt;
//返回此次搜索结果的同时,把结果记忆化到数组中
//这里体现的就是记忆化
}
int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++){
        high[i][j]=read();
    }
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++){
        ans=max(ans,dfs(i,j));
    }
    printf("%d\n",ans);
    return 0;
}

[HNOI2013] 比赛

此次联赛共N支球队参加,比赛规则如下:

(1)每两支球队之间踢一场比赛。

(2)若平局,两支球队各得1分。

(3)否则胜利的球队得3分,败者不得分。

已知每只球队的最后总得分,求有多少种可能的比赛过程?由于答案可能很大,你只需要输出答案对\(10^9+7\)取模的结果

剪枝1:

3\(*\)输赢局+2\(*\)平局=所有队伍的总分

输赢局+平局=\(n*(n-1)/2\)

通过这两个方程可表示出输赢局和平局

剪枝2:如果当前该队伍的分数已经大于它应有的分数,直接返回(因为比赛中没有扣分局)

剪枝3:如果当前该队伍在接下来的所有场比赛中都获胜,但达不到它应有的分数,直接返回

剪枝4:对于一个得分序列,可以发现不论它如何排列,最终答案都不会变,因此可以将得分序列从大到小排列,来缩小状态数

剪枝5:搜索第一个队伍和其它队伍的比赛结果,就可以得到剩下n-1个队伍的得分序列,以此递归搜索,同时用每个队伍的得分序列和队伍个数作为状态hash起来进行记忆化

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n,tot,wj,pj;
int a[15],now[15],b[15];
//a[i]球队i最后得分,now[i]球队i当前得分
map<int,int>hash;
bool cmp(int x,int y){
    return x>y;
}
int dfs(int x,int y){
    int ans=0;
    if(x==n)return 1;//所有球队都搜索完了
    if(now[x]>a[x])return 0;//剪枝2
    if(now[x]+3*(n-y+1)<a[x])return 0;//剪枝3
    if(y>n){//球队x与其它所有球队的比赛情况搜完了
        for(int i=x+1;i<=n;i++)
            b[i]=a[i]-now[i];
//这时我们得到了剩下队伍(x+1...n)的新的得分序列
        sort(b+x+1,b+n+1,cmp);
//对这个新的序列按照得分从大到小排序,跟之前一样了
        int sum=0;
        for(int i=x+1;i<=n;i++)
            sum=sum*28+b[i];
//把x+1...n队伍的得分情况哈希起来
//n<=10,所以每支队伍最多有27分,所以哈希乘28就可以了
        if(hash.find(sum)!=hash.end())
            return hash[sum];
        else return hash[sum]=dfs(x+1,x+2);
//哈希之后的记忆化搜索
    }
//三个if语句讨论三种情况,记得回溯
    if(now[x]+3<=a[x]&&wj>=1){
        wj--;now[x]+=3;
        ans+=dfs(x,y+1);
        wj++;now[x]-=3;
    }
    if(now[x]+1<=a[x]&&now[y]+1<=a[y]&&pj>=1){
        pj--;now[x]++;now[y]++;
        ans+=dfs(x,y+1);
        pj++;now[x]--;now[y]--;
    }
    if(now[y]+3<=a[y]&&wj>=1){
        wj--;now[y]+=3;
        ans+=dfs(x,y+1);
        wj++;now[y]-=3;
    }
    return ans%mod;
}
int main(){
    n=read();//n只球队
    for(int i=1;i<=n;i++){
        a[i]=read();
        tot+=a[i];
    }
//读入每支球队最后的总得分,并计算所有球队的总得分之和
    sort(a+1,a+n+1,cmp);
//按照得分从大到小排序,对应剪枝4
    wj=tot-n*(n-1);
    pj=n*(n-1)/2-wj;
//wj输赢局数,pj平局数
//对应剪枝1
    printf("%d\n",dfs(1,2)%mod);
//根据剪枝5
//我们先要搜索球队1和其它所有球队的比赛情况
//dfs(1,2)中1表示球队1,2表示球队2
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/PPXppx/p/10319934.html