IDA*
一、概念
简单的说,IDA算法就是迭代加深版的A算法。
二、分析
设计一个估价函数
f
(
s
t
a
t
e
)
<
=
真
实
步
数
f(state)<=真实步数
f(state)<=真实步数
若
当
前
步
数
+
f
(
s
t
a
t
e
)
>
上
界
当前步数 + f(state) > 上界
当前步数+f(state)>上界 真实步数必然大于上界,可以提前退出。
三、模板代码
bool dfs()
{
if(now_depth + f() > max_depth) return false;
}
int depth=0;
while(!dfs(0,depth)) depth++;
四、实例代码
Question:AcWing 180.排书
Question Link:acwing.com/problem/content/182
Question Analysis:
连续选择k个书,一共有
n
−
k
+
1
n - k + 1
n−k+1 种选法,总共有
n
−
k
n - k
n−k种放法,那么一共有
(
15
∗
14
+
14
∗
13
+
.
.
.
+
2
∗
1
)
/
2
=
560
(15*14 + 14*13 + ... + 2*1)/2 = 560
(15∗14+14∗13+...+2∗1)/2=560 种操作
暴力最坏时间复杂度
O
(
56
0
4
)
O(560^4)
O(5604)
每次操作会断开三个连接,然后引入三个连接。即每次操作最多会将三个连接修正
判断
n
−
1
n - 1
n−1个连接中有多少个错误的连接,记为
t
o
t
tot
tot,若全部修正至少需要
[
t
o
t
/
3
]
[tot/3]
[tot/3]次操作。(’[ ]'为上取正符号)
那么我们可以设计出估价函数,在当前状态之下,最少进行多少次操作,才可以变成有序序列,即
f
(
s
t
a
t
e
)
=
[
t
o
t
/
3
]
f(state)=[tot/3]
f(state)=[tot/3]
Diagram:
0.初始
1.将 [ L , R ] 抽出来
2.把[ R + 1 , K ]补到前面去
3.把[ L , R ]插入位置
Code:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=15;
int n,q[N];
int w[5][N];//辅助数组(交换用)
int f()
{
int tot=0;
for (int i = 0; i+1 < n; i++)
{
if(q[i+1]!=q[i]+1) tot++;
}
return (tot+2)/3; //即(tot/3)的上取整
}
bool check()
{
for (int i = 0; i < n; i++)
{
if(q[i]!=i+1) return false;//q[0]=1
}
return true;
}
bool dfs(int depth,int max_depth)
{
if(depth + f() > max_depth) return false;
if(check()) 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[depth],q,sizeof q);
int x,y;
for(x=r+1,y=l;x<=k;x++,y++) q[y]=w[depth][x];//Diagram 2
for(x=l;x<=r;x++,y++) q[y]=w[depth][x];//Diagram 3
if(dfs(depth+1,max_depth)) return true;
memcpy(q,w[depth],sizeof q);
}
}
}
return false;
}
int main()
{
int T;
cin >> T;
while(T--)
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> q[i] ;
}
int depth=0;
while(depth<5&&!dfs(0,depth)) depth++;//迭代加深
if(depth>=5) puts("5 or more");
else cout << depth << endl;
}
}
代码思路与分析来自于y总视频
Question:AcWing 181.回转游戏
Question Link:acwing.com/problem/content/183
Question Analysis:
估价函数:把中间
8
8
8个数中出现最多次数的出现次数记为
k
k
k ,每次操作最多会加入一个相同的数,即最多
8
−
k
8 - k
8−k 次。
f
(
s
t
a
t
e
)
=
8
−
k
f(state) = 8 - k
f(state)=8−k;
剪枝:避免枚举和上次相反的操作
最小字典序:按照字典序进行搜索
打表设计:编号,把八种操作对应的这一列数存下
题目例图
Code:
#include<iostream>
#include<cstring>
#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 opposite[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 s=0;
for (int i = 1; i < 4; i++) s=max(s,sum[i]);
return 8-s;
}
bool check()
{
for (int i = 1; i < 8; i++)
{
if(q[center[i]]!=q[center[0]]) return false;
}
return true;
}
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)//last:last op
{
if(depth+f()>max_depth) return false;
if(check()) return true;
//拓展其他分支
for (int i = 0; i < 8; i++)
{
if(opposite[i]==last) continue;//剪枝
operate(i);
path[depth]=i;
if(dfs(depth+1,max_depth,i)) return true;
operate(opposite[i]);//返回状态
}
return false;
}
int main()
{
while(cin >> q[0],q[0])
{
for (int i = 1; i < N; 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",path[i]+'A');
}
cout << endl << q[6] << endl;
}
return 0;
}
代码思路与分析来自于y总视频
Question:AcWing 182.破坏正方形
Question Link:acwing.com/problem/content/184
Question Analysis:
对于初始状态:
边长为1的正方形:n^2
边长为2的正方形:(n-1)^2
…
边长为n的正方形:1^2
一共有 (n*(n+1)*(2n+1))/6 个正方形
一共 2n(n+1) 根火柴
Target:从现有的火柴中,最少选择多少根火柴,可以使得每个正方形中都被至少选择了一根火柴
重复覆盖问题 一般用 Dancing Links 数据结构解决。
图形化
问题转换:
至少选择多少行,可以使得每一列至少有一个1(选择一根火柴)
搜索顺序:
每次选择一个还没有被覆盖的正方形(选择最小的一个)(优化),枚举选择它上面的哪一根火柴。
估价函数:
枚举每个正方形,如果当前正方形还是完整的,那么删掉它的所有边,但是只记删除一次。(估价函数>=真实值)
正方形边的序号:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=61;
vector<int> square[N];//每个正方形所有的边
bool st[N];//每条边是否被用过
int n,m;
bool check(vector<int> &sq)//检测正方形是否是完整的
{
for (int i = 0; i < sq.size(); i++)
{
if(st[sq[i]]) return false;
}
return true;
}
int f()
{
static bool state[N];//全局变量,只会被开辟一次
memcpy(state,st,sizeof st);//由于要修改st的值,先保存到另一个
int res=0;
for (int i = 0; i < m; i++)
{
vector<int> &sq=square[i];
if(check(sq))
{
res++;
for (int j = 0; j < sq.size(); j++) st[sq[j]]=true;
}
}
memcpy(st,state,sizeof st);
return res;
}
bool dfs(int depth,int max_depth)
{
if(depth+f()>max_depth) return false;
for (int i = 0; i < m; i++)
{
vector<int> &sq=square[i];
if(check(sq))
{
for (int j = 0; j < sq.size(); j++)
{
int x=sq[j];//枚举选择第x个
st[x]=true;
if(dfs(depth+1,max_depth)) return true;
st[x]=false;//状态回溯
}
return false;
}
}
return true;
}
int main()
{
int T;
cin >> T;
while(T--)
{
cin >> n ;
memset(st,0,sizeof st);
//得所有正方形的所有边的序号
m=0;
for (int len = 1; len <= n; len++)//长度
{
for (int a = 1; a+len-1 <= n; a++)//枚举起点
{
for (int b = 1; b+len-1 <= n; b++)
{
//得到该正方形的所有边的序号
square[m].clear();
int d=2*n+1;//公差
for (int i = 0; i < len; i++)//枚举每条边,得到正方形的所有边的序号
{
square[m].push_back(1+(a-1)*d+b-1+i);
square[m].push_back(1+(a+len-1)*d+b-1+i);
square[m].push_back(n+1+(a-1)*d+b-1+i*d);
square[m].push_back(n+1+(a-1)*d+b-1+i*d+len);
}
m++;
}
}
}
//PreDestroy
int k=0;
cin >> k;
while (k--)
{
int x;
cin >> x;
st[x]=true;
}
int depth=0;
while(!dfs(0,depth)) depth++;
cout << depth << endl;
}
}
代码思路与分析来自于y总视频
Question:AcWing 195.骑士精神
Question Link:acwing.com/problem/content/197
Question Analysis:
估价函数:位置不对的数量
Code:
#include<iostream>
#include<cstring>
using namespace std;
char str[5][5];
char tar[5][5]={'1','1','1','1','1',
'0','1','1','1','1',
'0','0','*','1','1',
'0','0','0','0','1',
'0','0','0','0','0'};//目标状态
int stari,starj;//初始空白位置坐标
pair<int,int> dire[8];//八种跳法
int f()//估价函数:位置不对的数量(除空白处)
{
int res=0;
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
if(str[i][j]!=tar[i][j]&&str[i][j]!='*') res++;
}
}
return res;
}
bool check()
{
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
if(str[i][j]!=tar[i][j]) return false;
}
}
return true;
}
bool dfs(int depth,int depth_max,int x,int y)//x,y为当前空白处坐标
{
if(depth+f()>depth_max) return false;
if(check()) return true;
for (int i = 0; i < 8; i++)
{
int tx=x+dire[i].first,ty=y+dire[i].second;
if(tx>=5||tx<0||ty>=5||ty<0) continue;//剪枝
swap(str[x][y],str[tx][ty]);
if(dfs(depth+1,depth_max,tx,ty)) return true;
swap(str[x][y],str[tx][ty]);//状态回溯
}
return false;
}
int main()
{
//跳法赋值
dire[0].first=1;dire[0].second=2;
dire[1].first=1;dire[1].second=-2;
dire[2].first=-1;dire[2].second=2;
dire[3].first=-1;dire[3].second=-2;
dire[4].first=2;dire[4].second=1;
dire[5].first=2;dire[5].second=-1;
dire[6].first=-2;dire[6].second=1;
dire[7].first=-2;dire[7].second=-1;
int T;
cin >> T;
while (T--)
{
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
cin >> str[i][j];
if(str[i][j]=='*')
{
stari=i;
starj=j;
}
}
}
int depth=0;
while(depth<=15&&!dfs(0,depth,stari,starj)) depth++;
if(depth>15) cout << -1 << endl;
else cout << depth << endl;
}
}
如有疑问欢迎在评论区留言或者通过Email联系我
My Email:Wizzy-Ang@qq.com
欢迎大家关注我的个人公众号WizzyAngShare,(还有个人博客)我会在这里分享编程语言语法,算法,及区块链的相关知识,还有各种奇奇怪怪的小知识等着你~
虽然现在这个公众号有亿点草率 ,我会努力更新的~~~