迭代加深
加成序列
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 1e9
using namespace std;
typedef pair<int, int>pii;
const int N = 110;
int n;
int path[N];
bool dfs(int u,int k){
if(u==k) return path[u-1]==n;
bool st[N]={0};
for(int i=u-1;i>=0;i--)
for(int j=i;j>=0;j--){
int s=path[i]+path[j];
if(s>n||s<=path[u-1]) continue;
st[s]=true;
path[u]=s;
if(dfs(u+1,k)) return true;
}
return false;
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
path[0]=1;//树的第0层
while(cin>>n,n){
int k=1;//一共搜索k层
while(!dfs(1,k)) k++;
for(int i=0;i<k;i++) cout<<path[i]<<" ";
cout<<endl;
}
return 0;
}
思路分析
- 因为往下搜索会搜索很深,并且解空间很大。但是根据题意可知,答案所在层数很浅,所以设置搜索的最大深度进行搜索。
- 理解 解元素 x[N]——放的是路径
总结
迭代加深的思路非常的重要。一步一步扩大搜索的深度进行搜索。
双向DFS
送礼物
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 1e9
using namespace std;
typedef pair<int, int>pii;
const int N = 1<<24;
int n,m,k;
int g[50],w[N];
int cnt=0;
int ans;
void dfs(int u,int s){
if(u==k){
w[cnt++]=s;
return;
}
if(s+g[u]<=m) dfs(u+1,s+g[u]);
dfs(u+1,s);
}
void dfs2(int u,int s){
if(u==n){
//二分这里注意
int l=0,r=cnt-1;
while(l<r){
int mid=l+r+1>>1;
if(s+w[mid]<=m) l=mid;
else r=mid-1;
}
if(w[l]+s<=m) ans=max(ans,w[l]+s);
return;
}
if(s+g[u]<=m) dfs2(u+1,s+g[u]);
dfs2(u+1,s);
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>m>>n;
for(int i=0;i<n;i++) cin>>g[i];
sort(g,g+n);
reverse(g,g+n);
k=n/2;
dfs(0,0);
sort(w,w+cnt);
int t=1;
for(int i=1;i<cnt;i++)
if(w[i]!=w[i-1])
w[t++]=w[i];
cnt=t;
dfs2(k,0);
cout<<ans<<endl;
return 0;
}
思路总结
- 本题是一个动态规划的01背包问题,但是n*m会超过时间复杂度。
- 如果用不优化的dfs,最大时间复杂度是2的46次方,也会超时
- 所以我们想到用双向dfs减少时间复杂度(2的23次方),先搜索前一半的物品,将每种选取方式得到的总量(价值)记录下来,方便后面匹配二分。然后搜索后一半,找到不超过m的最大总重量。
总结
双向dfs,先搜索前面一半,记录起来,然后搜索后一半,搜索的和前面一半记录的进行判断。
IDA*
排书
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 1e9
using namespace std;
typedef pair<int, int>pii;
const int N = 20 ;
int n;
int a[N],w[10][N];
int f(){
int cnt=0;
for(int i=0;i+1<n;i++)
if(a[i+1]!=a[i]+1)
cnt++;
return (cnt+2)/3;
}
bool dfs(int u,int depth){
if(u+f()>depth) return false;
if(f()==0) return true;
for(int len=1;len<=n;len++){
for(int l=0;l+len-1<n;l++){
int r=l+len-1;
for(int k=r+1;k<n;k++){
memcpy(w[u],a,sizeof a);
int x,y;
for(x=r+1,y=l;x<=k;x++,y++) a[y]=w[u][x];//上面定义了x,y,for循环里面不要再定义临时变量
for(x=l;x<=r;x++,y++) a[y]=w[u][x];
if(dfs(u+1,depth)) return true;
memcpy(a,w[u],sizeof a);
}
}
}
return false;
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin>>t;
while(t--){
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
int depth=0;
while(depth<5&&!dfs(0,depth)){
depth++;
}
if(depth>=5) cout<<"5 or more"<<endl;
else cout<<depth<<endl;
}
return 0;
}
思路分析
- 因为每一层搜索很大,所以只能搜索很浅才行,并且需要尽量搜索较少的点。因此需要引入估价函数剪枝来减少搜索的点。
- 估价函数的定义,估价函数要满足不大于实际步数。一般都定义:到最终答案所需要的最少步数。
- IDA一般用来求字典序最小,和减少时间复杂度。写法一般比A更简单。
总结
- 搜索顺序方向很重要。先枚举移动的长度大小,然后枚举将其放在哪个数的后面,改变状态(得到这一层的状态),进入下一层,若失败,恢复状态。
- 估价函数的定义,对于有序序列,可以用这种方式。
- 改变状态的技巧,注意不要再在for循环里面设置临时变量x,y了。y是从左到右开始改变
回转游戏
/*
0 1
2 3
4 5 6 7 8 9 10
11 12
13 14 15 16 17 18 19
20 21
22 23
*/
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 24;
int op[8][7] = {
{0, 2, 6, 11, 15, 20, 22},
{1, 3, 8, 12, 17, 21, 23},
{10, 9, 8, 7, 6, 5, 4},
{19, 18, 17, 16, 15, 14, 13},
{23, 21, 17, 12, 8, 3, 1},
{22, 20, 15, 11, 6, 2, 0},
{13, 14, 15, 16, 17, 18, 19},
{4, 5, 6, 7, 8, 9, 10}
};
int oppsite[8] = {5, 4, 7, 6, 1, 0, 3, 2};
int center[8] = {6, 7, 8, 11, 12, 15, 16, 17};
int q[N];
int path[100];
int f()
{
static int sum[4];
memset(sum, 0, sizeof sum);
for (int i = 0; i < 8; i ++ ) sum[q[center[i]]] ++ ;
int maxv = 0;
for (int i = 1; i <= 3; i ++ ) maxv = max(maxv, sum[i]);
return 8 - maxv;
}
void operate(int x)
{
int t = q[op[x][0]];
for (int i = 0; i < 6; i ++ ) q[op[x][i]] = q[op[x][i + 1]];
q[op[x][6]] = t;
}
bool dfs(int depth, int max_depth, int last)
{
if (depth + f() > max_depth) return false;
if (f() == 0) return true;
for (int i = 0; i < 8; i ++ )
if (last != oppsite[i])
{
operate(i);
path[depth] = i;
if (dfs(depth + 1, max_depth, i)) return true;
operate(oppsite[i]);
}
return false;
}
int main()
{
while (cin >> q[0], q[0])
{
for (int i = 1; i < 24; i ++ ) cin >> q[i];
int depth = 0;
while (!dfs(0, depth, -1)) depth ++ ;
if (!depth) printf("No moves needed");
else
{
for (int i = 0; i < depth; i ++ ) printf("%c", 'A' + path[i]);
}
printf("\n%d\n", q[6]);
}
return 0;
}
思路分析
- 这题也需要减少时间复杂度,并且需要字典序最小,所以选择IDA*算法。
- 然后设置估价函数和考虑如何扩展方向。
总结
- IDA*算法,迭代加深加上估价函数
- 扩展方向的技巧,先将要变化的下标存储起来,然后进行八种操作进行扩展。
- 解元素 x[N]存放的是搜索的路径,即达到当前层的操作种类,最后也是字典序。