这周主要学习了搜索算法的知识
整理了几道比较典型的题目
1.八皇后[USACO1.5]八皇后 Checker Challenge - 洛谷
新生赛的时候就遇到了这道题的变种,不过当时能力欠佳
因为棋盘是正方形,是对称的,方案也是对称的。所以没必要将所有方案搜索出来。
当n为偶数,第一行的棋子放在第[1..⌊n div 2⌋ ]列的方案,与 第一行的棋子放在第[ ⌊n div 2⌋+1 ..n]列的方案 ,数量相等且左右对称。
当 n 为奇数时,第一行的棋子放在第[1..⌊n div 2⌋ ]列的方案,与 第一行的棋子放在第[ ⌈n div 2⌉+1..n]列的方案 ,数量相等且左右对称。第一行的棋子还可以放在第⌈n div 2⌉列,那么,第二行的棋子又具有对称性。
上代码:
#include<bits/stdc++.h>
using namespace std;
const int N=20;
int lg[1<<17];
int n;
int flag;
int sum;
int upperlim;
int a[N];
int cnt;
void print_a(){
for(int i=1;i<=n;++i)
printf("%d ",a[i]);
printf("\n");
}
void dfs(int row,int ld,int rd){
if(row==upperlim){
++sum;
if(sum<=3)print_a();
return;
}
int pos=upperlim&(~(row|rd|ld));
if(cnt==0){//要放第一行的棋子
if(flag==1)pos=(1<<(n>>1))-1;//n为偶数或奇数,第一行的棋子放在第[1..⌊n div 2⌋ ]列。
else pos=1<<(n>>1);//n为奇数,且第一行的棋子放在第⌈n div 2⌉列
}
if(cnt==1&&flag==2)pos=pos&((1<<(n>>1))-1);//n为奇数,且第一行的棋子放在第⌈n div 2⌉列,第二行的棋子放在[1..⌊n div 2⌋-1].
while(pos){
register int low=pos&-pos;
pos^=low;
a[++cnt]=lg[low];
dfs(row|low,(ld|low)<<1,(rd|low)>>1);
--cnt;
}
}
int main(){
scanf("%d",&n);
lg[1]=1;
for(register int i=0;i<17;++i)
lg[1<<i]=i+1;
upperlim=(1<<n)-1;
flag=1;
dfs(0,0,0);
if(n&1){
flag=2;
dfs(0,0,0);
}
if(n==6)printf("4 1 5 2 6 3\n");//n==6的时候只有四种方案,只好打表了
printf("%d",sum<<1);
return 0;
}
不断更新后面的数,越靠前的越迟更新
1后面的数可以是2或3;2后面的数可以是3或4;如此往复
#include<bits/stdc++.h>
using namespace std;
int n,k,he,ans,a[21];
int sushu(int m)//素数判断
{
for(int i=2;i<m;i++)
{
if(m%i==0)
return 0;
}
return 1;
}
void xuanshu(int l,int f)
{
if(l==0){
ans+=sushu(he);
}
else{
f++;
for(int i=f;i<=n;i++)
{
he+=a[i];
l--;
xuanshu(l,i);
he-=a[i];
l++;
}
}
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
xuanshu(k,0);
printf("%d",ans);
return 0;
}
3.马的遍历马的遍历 - 洛谷
把棋盘上的每一个点按照规则入队,第一次到达该点时的步数一定是最优步数
#include<bits/stdc++.h>
using namespace std;
struct queue_
{
int x,y;//一个结构体,x,y是队列该位置放的点的x,y值
} que[160010];
int head=0,tail=1,get[401][401],n,m,sx,sy;
int fx[16]={2,-2,2,-2,-1,1,-1,1},fy[16]={1,1,-1,-1,2,2,-2,-2};//方向
int main()
{
cin>>n>>m>>sx>>sy;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
get[i][j]=-1;
get[sx][sy]=0;
que[1].x=sx;
que[1].y=sy;
while(head<tail)
{
head++;//头指针加1
int s=get[que[head].x][que[head].y]+1;
for(int i=0;i<8;++i)
{
int nx=que[head].x+fx[i],ny=que[head].y+fy[i];
if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&get[nx][ny]==-1)/
{
tail++;
que[tail].x=nx;
que[tail].y=ny;//新点入队
get[nx][ny]=s;//标记到达该点的最小步数
}
}
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
cout<<get[i][j];
cout<<endl;
}
return 0;
}
4.自然数的拆分问题 自然数的拆分问题 - 洛谷
题目中给了提示,搜取每个节点,如节点没搜到,回溯;
#include<bits/stdc++.h>
using namespace std;
int n;
int a[1000];
void dfs(int x,int h)
{
int i;
if(x==0&&h>2)
{
cout<<a[n];
for(i=2;i<h;i++)
cout<<"+"<<a[i];
cout<<endl;
return ;
}
for(i=a[h-1];i<=x;i++)
{
if(i>=1)
{
a[h]=i;
dfs(x-i,h+1);
}
}
}
int main()
{
cin>>n;
dfs(n,1);
return 0;
}
严格来说,搜索算法也算是一种暴力举策略,但是其算法特性决定了效率比直接的枚举所有答案要高,因为搜索可以跳过一些无效状态,降低问题规模,是一个十分实用的策略。