知识点一:深搜剪枝
先上题目
挑选钻石
时间限制:1秒 内存限制:128M
题目描述
小可带了一个购物袋去钻石店采购钻石。购物袋限重 m 克。钻石店一共有 n 种钻石,第 i 种钻石数量为 i 重量为 wi ,价值为 ci 。假设小可有无限多的钱,请问他可以带走的钻石的最大价值是多少?
输入描述
第一行数据组数 T
每一组数据第一行两个整数 n , m ,表示钻石种类数。
每一组数据第二行 n 个整数 wi 。
每一组数据第三行 n 个整数 ci 。
输出描述
对于每一组数据,输出小可可以带走的钻石的最大价值。
输入样例
2
2 4
3 2
5 3
3 100
4 7 1
5 9 2
输出样例
6
29
数据范围
1<=T<=74,1<=n<=15,1<=m,wi,ci<=109
解题思路
题目中的价值和重量都达到了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
题目描述
有这样一个序列a0,a1,..,am ,此序列满足以下四个条件:
1.a0=1
2.am=n
3.a0<a1<a2<..<am
4.对于每一个k(1<=k<=m) 都存在有两个整数i,j(0<=i,j<=k−1 , i 可以和 j 相等 ) ,使得ak=ai+aj
给定一个 n ,找出符合上述四个条件的长度最小的序列,假如有多个满足要求的,输出字典序最大的解。
输入描述
多组数据,以 0 结尾,每组数据一个数 n
输出描述
对于每组数据,输出字典序最大的符合要求的最短的序列
输入样例
5
7
12
15
77
0
输出样例
1 2 4 5
1 2 4 6 7
1 2 4 8 12
1 2 4 5 10 15
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行,每行一个坐标,表示建筑所在位置的坐标。
请你输出为了修建这条道路,最少需要拆除多少建筑?
7 6 3
6 2
5 2
4 3
2 1
7 3
5 4
6 4
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;
}