Y1课程笔记:第五课时(搜索习题)

知识点一:深搜剪枝

先上题目

挑选钻石

时间限制:1秒        内存限制:128M

题目描述

小可带了一个购物袋去钻石店采购钻石。购物袋限重 m 克。钻石店一共有 n 种钻石,第 i 种钻石数量为 i 重量为 w​i​​ ,价值为 c​i​​ 。假设小可有无限多的钱,请问他可以带走的钻石的最大价值是多少?

输入描述

第一行数据组数 T

每一组数据第一行两个整数 n , m ,表示钻石种类数。

每一组数据第二行 n 个整数 w​i​​ 。

每一组数据第三行 n 个整数 c​i​​ 。

输出描述

对于每一组数据,输出小可可以带走的钻石的最大价值。

输入样例

  1. 2
  2. 2 4
  3. 3 2
  4. 5 3
  5. 3 100
  6. 4 7 1
  7. 5 9 2

输出样例

  1. 6
  2. 29

数据范围

1<=T<=74,1<=n<=15,1<=m,w​i​​,c​i​​<=10​9​​

解题思路

题目中的价值和重量都达到了1e9,无法使用动态规划,只能考虑深搜剪枝。

首先,将每种钻石按照性价比从高到低排序,以便先遍历到高性价比钻石。接下来进行剪枝

1.可行性剪枝:搜索时,按拿取数量由高到低遍历,当剩余空间不足时,直接continue

2.最优性剪枝:

        ①即使剩余空间都装当前性价比的东西也不是最优(sum+left*s[pos].a<=ans)

        ②即使把剩下的全装上也不最优(sum+as[pos]<=ans)PS:此处使用后缀和实现,不然更浪费时间

AC代码

#include<bits/stdc++.h>
using namespace std;
long long ans,n,m,t,as[25];
struct node{
    double a;
    long long w;
    long long c;
    long long cnt;
}s[25];
bool cmp(node x,node y){
    return x.a>y.a;
}
void dfs(long long sum,int pos,long long left){
    //cout<<sum<<' '<<pos<<' '<<left<<endl;
    ans=max(ans,sum);
    if(left==0||pos>n){
        return;
    }
    if(sum+left*s[pos].a<=ans){
        return;
    }
    if(sum+as[pos]<=ans){
        return;
    }                                             
    for(int i=s[pos].cnt;i>=0;i--){
        //cout<<sum+s[pos].c*i<<endl;
        if(left<s[pos].w*i){
            continue;
        }
        dfs(sum+s[pos].c*i,pos+1,left-s[pos].w*i);
    }
}
int main(){                                              
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
    cin>>t;
    while(t--){
        //memset(as,0,sizeof as);
        ans=0;
        cin>>n>>m;
        for(int i=1;i<=n;i++){
            cin>>s[i].w;
            s[i].cnt=i;
            //cout<<s[i].cnt<<' ';
        }
        //cout<<endl;
        for(int i=1;i<=n;i++){
            cin>>s[i].c;
            s[i].a=1.0*s[i].c/s[i].w;
            //cout<<s[i].a<<' ';
        }
        //cout<<endl;
        sort(s+1,s+1+n,cmp);
        as[n+1]=0;
		for(int i=n;i>=1;i--){
	            as[i]=as[i+1]+s[i].c*s[i].cnt;
	            //cout<<s[i].cnt<<' ';
	        }                                           
        //cout<<endl;
        dfs(0,1,m);
        cout<<ans<<endl;
    }
    return 0;
}

知识点二:迭代加深搜索

先上题目

最短的序列

时间限制:2秒        内存限制:128M

题目描述

有这样一个序列a​0​​,a​1​​,..,a​m​​ ,此序列满足以下四个条件:

1.a​0​​=1
2.a​m​​=n
3.a​0​​<a​1​​<a​2​​<..<a​m​​
4.对于每一个k(1<=k<=m) 都存在有两个整数i,j(0<=i,j<=k−1 , i 可以和 j 相等 ) ,使得a​k​​=a​i​​+a​j​​

给定一个 n ,找出符合上述四个条件的长度最小的序列,假如有多个满足要求的,输出字典序最大的解。

输入描述

多组数据,以 0 结尾,每组数据一个数 n

输出描述

对于每组数据,输出字典序最大的符合要求的最短的序列

输入样例

  1. 5
  2. 7
  3. 12
  4. 15
  5. 77
  6. 0

输出样例

  1. 1 2 4 5
  2. 1 2 4 6 7
  3. 1 2 4 8 12
  4. 1 2 4 5 10 15
  5. 1 2 4 8 9 17 34 68 77

数据范围

1<=n<=10000

解题思路

如果采用宽搜,在n=10000时,会遍历约400000000个数,一定会爆内存,但我们又知道答案是求最小值,一定在状态树的浅层,因此我们可以考虑迭代加深搜索↓

知识补充

迭代加深搜索是一个状态空间(状态图)搜索策略。在这个搜索策略中,一个具有深度限制的深度优先搜索算法会不断重复地运行,并且同时放宽对于搜索深度的限制,直到找到目标状态,与广度优先算法是等价的,但对内存的使用会少很多;在每一步迭代中,它会按深度优先算法中的顺序,遍历搜索树中的节点,但第一次访问节点的累积顺序实际上是广度优先的。

同时,我们还需要一点点剪枝:当当前遍历到的数一直倍增到限制层仍不能达到n时,直接return。

AC代码

#include<bits/stdc++.h>
using namespace std;
int n,ans=0x3f3f3f3f,flag;
int po[105]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288};
vector<int>v;
void dfs(int lim,int cur,int last){
    last=v.back();
    if(v.back()==n){
        flag=1;
        return;
    }
    if(cur>lim){
        return;
    }
    if(v.back()*po[lim-cur]<n){
        return;
    }
    int len=v.size();
    for(int i=len-1;i>=0;i--){
        v.push_back(v[i]+last);
        dfs(lim,cur+1,last);
        if(flag==1){
            return;
        }
        v.pop_back();
    }
    
}
int main(){                                              
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
    while(cin>>n){
        ans=0x3f3f3f3f;
        v.clear();
        for(int i=1;i<=n;i++){
            flag=0;
            v.push_back(1);
            dfs(i,1,1);
            if(flag==1){
                for(int i=0;i<v.size();i++){
                    cout<<v[i]<<' ';
                }
                cout<<'\n';
                break;
            }
            v.pop_back();
        }
    }
    return 0;
}

知识点三:双端队列优化宽搜

题目描述

小可家的位置比较偏僻,因此小可想修建一条回家之路。

回家的路为了方便必须接入到主要干道。为了方便起见,可以将所有地理坐标看作是一个平面直角坐标系,将主要干道的坐标设置为(0,0)点。为了修建这条道路,有些存在原有建筑的地方需要进行拆除。这条路小可想修的横平竖直,即不存在弯曲的地方或者斜着的地方。

现在请问最少拆除多少建筑可以将这条路修建起来。

输入描述

第一行包含三个整数N,x,y,表示建筑的数量和小可家的坐标。

然后输入N行,每行一个坐标,表示建筑所在位置的坐标。

输出描述

请你输出为了修建这条道路,最少需要拆除多少建筑?

输入样例

  1. 7 6 3
  2. 6 2
  3. 5 2
  4. 4 3
  5. 2 1
  6. 7 3
  7. 5 4
  8. 6 4

输出样例

  1. 1

数据范围

1≤N≤5∗10^​4​​,1≤x,y≤10^​3​​

解题思路

经典宽搜求最短路径,入队时需要拆除的从尾部入队,不需拆除的头部优先入队,出队时头部出队,这样就能优先走无需拆除的路线。

AC代码↓

#include<bitsdc++.h>
using namespace std;
int n,ex,ey,a[2005][2005],sdis[2005][2005],dis[2005][2005],vis[2005][2005],dx[]={1,0,-1,0},dy[]={0,1,0,-1},ans=0x3f3f3f3f;
deque<int>qx,qy;
int main(){
    cin>>n>>ex>>ey;
    int xxx,yyy;
    for(int i=1;i<=n;i++){
        scanf("%d%d",&xxx,&yyy);
        a[xxx][yyy]=1;
    }
    qx.push_front(0);
    qy.push_front(0);
    vis[0][0]=1;
    while(!qx.empty()){
        int x=qx.front();
        int y=qy.front();
        qx.pop_front();
        qy.pop_front();
        if(x==ex&&y==ey){
            ans=min(sdis[x][y],ans);
            /*for(int i=1;i<=10;i++){
				for(int j=1;i<=10;j++){
					cout<<sdis[i][j]<<' ';
				}
				cout<<endl;
			}*/
            //return 0;
        }
        for(int i=0;i<4;i++){
            int xx=x+dx[i];
            int yy=y+dy[i];
            if(xx>=0&&yy>=0&&xx<1015&&yy<1015&&vis[xx][yy]==0){
                vis[xx][yy]=1;
                if(a[xx][yy]==1){
                    qx.push_back(xx);
                    qy.push_back(yy);
                    dis[xx][yy]=dis[x][y];
                    sdis[xx][yy]=sdis[x][y]+1;
                }
                else{
                    qx.push_front(xx);
                    qy.push_front(yy);
                    dis[xx][yy]=dis[x][y]+1;
                    sdis[xx][yy]=sdis[x][y];
                }
            }
        }
    }
    cout<<ans;
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值