基础搜索选做
01迷宫
需要一点小小的处理,提前想好再写代码
再就是stl看起来不是特别好用,不过可以避免边界问题啥的。。。
#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
int n,m;
int vis[2001][2001];
char mp[2001][2001];
int ans[2001][2001];
struct dot{
int x,y;
};
queue<dot>q;
int xx[4]={0,0,1,-1};
int yy[4]={1,-1,0,0};
int v(int x,int y,char val)
{
if(x>0&&x<=n&&y>0&&y<=n&&val!=mp[x][y]) return 1;
return 0;
}
void bfs(int x,int y)
{
while(!q.empty()) q.pop();
dot tmp1;tmp1.x=x;tmp1.y=y;q.push(tmp1);
vis[x][y]=1;
int num=1;
while(!q.empty())
{
dot tmp=q.front();q.pop();
for(int i=0;i<4;++i)
{
int x2=tmp.x+xx[i],y2=tmp.y+yy[i];
if(v(x2,y2,mp[tmp.x][tmp.y])&&vis[x2][y2]==0)
{
dot tmp2;tmp2.x=x2;tmp2.y=y2;
q.push(tmp2);
vis[x2][y2]=1;
++num;
}
}
}
q.push(tmp1);ans[x][y]=num;
while(!q.empty())
{
dot tmp=q.front();q.pop();
for(int i=0;i<4;++i)
{
int x2=tmp.x+xx[i],y2=tmp.y+yy[i];
if(v(x2,y2,mp[tmp.x][tmp.y])&&ans[x2][y2]==0)
{
dot tmp2;tmp2.x=x2;tmp2.y=y2;
q.push(tmp2);
ans[x2][y2]=num;
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
cin>>mp[i][j];
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(vis[i][j]) continue;
else bfs(i,j);
for(int i=1;i<=m;++i)
{
int x,y;scanf("%d%d",&x,&y);
printf("%d\n",ans[x][y]);
}
return 0;
}
数独
犯了一个很小的错误:在dfs函数判断a[m][n]!=0里面没有加return!导致调了很久不知道错在哪。
还是细节上的问题,下次注意。。。
#include<iostream>
#include<cstdio>
using namespace std;
int a[10][10],x[10][10],y[10][10],z[10][10];
int b[10][10];
int zz[10][10]={{},
{0,1,1,1,2,2,2,3,3,3},
{0,1,1,1,2,2,2,3,3,3},
{0,1,1,1,2,2,2,3,3,3},
{0,4,4,4,5,5,5,6,6,6},
{0,4,4,4,5,5,5,6,6,6},
{0,4,4,4,5,5,5,6,6,6},
{0,7,7,7,8,8,8,9,9,9},
{0,7,7,7,8,8,8,9,9,9},
{0,7,7,7,8,8,8,9,9,9}};
void f(int m,int n,int val)
{
a[m][n]=val;
x[m][val]=1;
y[n][val]=1;
z[zz[m][n]][val]=1;
}
void g(int m,int n,int val)
{
a[m][n]=0;
x[m][val]=0;
y[n][val]=0;
z[zz[m][n]][val]=0;
}
void print()
{
for(int i=1;i<=9;++i,printf("\n"))
for(int j=1;j<=9;++j)
printf("%d ",a[i][j]);
exit(0);
}
void dfsnxt(int,int);
void dfs(int,int);
int main()
{
// for(int i=1;i<=9;++i)
// for(int j=1;j<=9;++j)
// scanf("%d",&b[i][j]);
for(int i=1;i<=9;++i)
for(int j=1;j<=9;++j)
{
scanf("%d",&a[i][j]);
if(a[i][j]==0) continue;
x[i][a[i][j]]=1;
y[j][a[i][j]]=1;
z[zz[i][j]][a[i][j]]=1;
}
dfs(1,1);
return 0;
}
void dfsnxt(int m,int n)
{
if(n==9&&m==9) print();
else if(n==9) dfs(m+1,1);
else dfs(m,n+1);
}
void dfs(int m,int n)
{
if(a[m][n]) {dfsnxt(m,n);return;}
for(int i=1;i<=9;++i)
{
if(x[m][i]||y[n][i]||z[zz[m][n]][i]) continue;
f(m,n,i);
dfsnxt(m,n);
g(m,n,i);
}
}
靶型数独
在上一个题数独的基础上加了一点小小的剪枝,就是先遍历0比较少的行,这样可以使dfs的搜索层数最小化。还是要注意细节吧。。。
#include<iostream>
#include<cstdio>
using namespace std;
int ans=-1;
int a[10][10],x[10][10],y[10][10],z[10][10];
int k[10],kk[10],k1[10];
int zz[10][10]={{},
{0,1,1,1,2,2,2,3,3,3},
{0,1,1,1,2,2,2,3,3,3},
{0,1,1,1,2,2,2,3,3,3},
{0,4,4,4,5,5,5,6,6,6},
{0,4,4,4,5,5,5,6,6,6},
{0,4,4,4,5,5,5,6,6,6},
{0,7,7,7,8,8,8,9,9,9},
{0,7,7,7,8,8,8,9,9,9},
{0,7,7,7,8,8,8,9,9,9}};
void f(int m,int n,int val)
{
a[m][n]=val;
x[m][val]=1;
y[n][val]=1;
z[zz[m][n]][val]=1;
}
void g(int m,int n,int val)
{
a[m][n]=0;
x[m][val]=0;
y[n][val]=0;
z[zz[m][n]][val]=0;
}
void print()
{
int sum=0;
for(int i=1;i<=9;++i)
for(int j=1;j<=9;++j)
{
int tmp=min(min(i-1,9-i),min(j-1,9-j))+6;
sum+=tmp*a[i][j];
}
ans=max(ans,sum);
}
void dfsnxt(int,int);
void dfs(int,int);
int main()
{
for(int i=1;i<=9;++i)
{
int kkk=0;
for(int j=1;j<=9;++j)
{
scanf("%d",&a[i][j]);
if(a[i][j]==0) {++kkk;continue;}
x[i][a[i][j]]=1;
y[j][a[i][j]]=1;
z[zz[i][j]][a[i][j]]=1;
}
kk[i]=kkk;
}
int lst=0;
for(int i=1;i<=9;++i) {
int mink=1000,minj;
for(int j=1;j<=9;++j) {
if(k[j]==0&&kk[j]<mink) {
mink=kk[j];
minj=j;
}
}
k[minj]=i;k1[lst]=minj;lst=minj;
}
dfs(k1[0],1);
printf("%d\n",ans);
return 0;
}
void dfsnxt(int m,int n)
{
if(k[m]==9&&n==9) print();
else if(n==9) dfs(k1[m],1);
else dfs(m,n+1);
}
void dfs(int m,int n)
{
if(a[m][n]) {dfsnxt(m,n);return;}
for(int i=1;i<=9;++i)
{
if(x[m][i]||y[n][i]||z[zz[m][n]][i]) continue;
f(m,n,i);
dfsnxt(m,n);
g(m,n,i);
}
}
小木棍
非常好剪枝,使我感动的旋转
一开始猜错了结论,贪心是错误的,排序并尽量先选最大的并不能使结果一定正确
搜索,有一些经典的和巧妙的剪枝,具体在这里有详细的介绍,我大致列一个提纲
1.记忆化,使用vis数组,不要忘了递归时的还原和整体的清空
2.排序,这个感觉上会降低一点时间
3.设置搜索起点,每次从第i+1个往后搜
4 第一根木棍必须成功,否则直接舍去。这个剪枝适合那种比较离谱的数据
4.1 搜索范围从a[1]开始(亲测从2开始也行,因为剪枝4会直接舍去那些比较小的)
以上这四个都是比较基本的,,,
5.预处理长度相同的木棍,搜索时跳过,适合这种n很小导致很多数据重复的题目。
6.这个剪枝跟上面那个错误的猜想有关,当凑出一部分木棍但继续dfs发现无法完成任务时,立即退出
看起来每个剪枝都不是很起眼,但是少了哪一个都过不了这道题。。。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,a[200],nxt[200],vis[200],sum,now,xx;
bool dfs(int x,int j)
{
for(int i=j;i<=n;++i)
{
if(vis[i]) continue;
if(x==a[i])
{
vis[i]=1;
--now;
if(now==0) return 1;
if(dfs(xx,1)) return 1;
vis[i]=0;
++now;
return 0;
}
else if(x>a[i])
{
vis[i]=1;
if(dfs(x-a[i],i+1))
return 1;
vis[i]=0;
i+=nxt[i];
}
if(x==xx) return 0;
}
return 0;
}
bool cmp(int x,int y) {return x>y;}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) {scanf("%d",&a[i]);sum+=a[i];}
sort(a+1,a+n+1,cmp);
for(int i=n-1;i>=1;--i)
if(a[i]==a[i+1])
nxt[i]=nxt[i+1]+1;
if(sum==1) printf("1\n");
for(int i=2;i<=sum;++i)
{
if(sum%i) continue;
now=sum/i;xx=i;
if(dfs(i,1))
{
for(int j=1;j<=n;++j) vis[j]=0;
printf("%d\n",i);
break;
}
}
return 0;
}
走迷宫
题目说的方向:优先左上右下,根据这个对应方向数组
再就是边界、vis数组的起点标记等细节
#include<iostream>
#include<cstdio>
using namespace std;
int n,m,x00,y00,x11,y11;
int a[100][100],vis[100][100];
int xx[4]={0,-1,0,1};
int yy[4]={-1,0,1,0};
struct dot{
int x,y;
}q[1000];
int cnt,b;
void print(){
b=1;
for(int i=1;i<=cnt;++i){
printf("(%d,%d)",q[i].x,q[i].y);
if(i==cnt) printf("\n");
else printf("->");
}
}
bool f(int x,int y){
if(x>0&&y>0&&x<=n&&y<=m&&vis[x][y]==0&&a[x][y]==1) return 0;
return 1;
}
void dfs(){
if(q[cnt].x==x11&&q[cnt].y==y11) {print();return;}
for(int i=0;i<4;++i){
int x22=q[cnt].x+xx[i],y22=q[cnt].y+yy[i];
if(f(x22,y22)) continue;
q[++cnt].x=x22;q[cnt].y=y22;vis[x22][y22]=1;
dfs();
--cnt;vis[x22][y22]=0;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&a[i][j]);
scanf("%d%d%d%d",&x00,&y00,&x11,&y11);
q[++cnt].x=x00;q[cnt].y=y00;
vis[x00][y00]=1;dfs();
if(!b) printf("-1\n");
return 0;
}
数的划分
上古题就是很可爱
好像突然发现了去年计概C期中考试的附加题(?)
dfs,对范围进行剪枝和去重,最小可以到n/k上取整,最大是min(n-k+1,z)
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int n,k;
ll ans;
int vis[1000];
int f(int x,int y){
if(x%y) return x/y+1;
return x/y;
}
void dfs(int x,int y,int z){
if(x==0&&y==0) {++ans;return;}
int l=f(x,y),r=min(x-y+1,z);
for(int i=l;i<=r;++i){
dfs(x-i,y-1,i);
}
}
int main(){
scanf("%d%d",&n,&k);
dfs(n,k,n);
printf("%lld\n",ans);
return 0;
}
dp需要一点点技巧(?)就是分含1的和不含1的讨论。跟这道题方程很类似,可惜我考场上没有想到(悲)
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
ll n,k,f[1000][1000][2];
int main(){
scanf("%lld%lld",&n,&k);
f[1][1][1]=f[2][1][1]=f[2][2][1]=1;
for(int i=3;i<=n;++i)
for(int j=1;j<=i;++j){
f[i][j][0]=f[i-1][j-1][0]+f[i-1][j-1][1];
f[i][j][1]=f[i-j][j][0]+f[i-j][j][1];
}
printf("%lld\n",f[n][k][0]+f[n][k][1]);
return 0;
}
八数码难题
记忆化bfs,使用map,可以直接赋值,调用的时候未赋值默认返回0,很方便
#include<iostream>
#include<cstdio>
#include<queue>
#include<map>
using namespace std;
struct con{
int num,a;
};
queue<con>q;
map<int,int>ma;
int ans=123804765;
int xx[4]={-3,-1,1,3};
int x10[10]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
int f(int x,int d){
return (x/x10[d])%10;
}
int fnd0(int x){
int ret=0;
while(x%10) {x/=10;++ret;}
return ret;
}
int yes(int d,int x){
if(d==0) return x==3||x==1;
if(d==1) return x!=-3;
if(d==2) return x==3||x==-1;
if(d==3) return x!=-1;
if(d==5) return x!=1;
if(d==6) return x==-3||x==1;
if(d==7) return x!=3;
if(d==8) return x==-1||x==-3;
return 1;
}
int sol(int sta,int d,int x){
int ret=0,m=1;
for(int i=0;i<=8;++i){
if(i==d) ret+=m*f(sta,d+x);
else if(i==d+x) ret+=m*f(sta,d);
else ret+=m*f(sta,i);
m*=10;
}
return ret;
}
void bfs(){
while(!q.empty()){
con now=q.front();q.pop();
if(now.a==ans) {
printf("%d\n",now.num);
exit(0);
}
int d=fnd0(now.a);
for(int i=0;i<4;++i)
if(yes(d,xx[i])){
con tmp;tmp.a=sol(now.a,d,xx[i]);tmp.num=now.num+1;
if(ma[tmp.a]) continue;
q.push(tmp);ma[tmp.a]=1;
}
}
}
int main(){
con tmp;scanf("%d",&tmp.a);tmp.num=0;
ma[tmp.a]=1;q.push(tmp);
bfs();
return 0;
}
Power Hungry Cows
如果使用bfs搜索,入队的状态会非常多,导致MLE。如果使用dfs搜索,搜索树可能会很深。但是注意到答案所在节点一定很浅,因此可以使用A做一个合理的估价函数,避免搜索过深。更好的方法是直接IDA,通过迭代加深(main函数中限制深度,逐层加深)的dfs来剪掉很深但没用的答案节点。复杂度 \(O(2*k)\) ,k与logn同级(所以可以说是 \(O(n)\) ?)
IDA*是核心算法,还有两个有用的剪枝:
- 如果当前里答案节点过远,以至于一直倍增都无法到达答案节点则剪枝
- 如果gcd(x, y)不能整除n,则显然永远无法到达n(\(Ax+By=C\) 有整数解的充要条件),可以减掉
还有一个没用的剪枝:
- 记录当前状态的是否已经扫描过,如果是,维护最浅的深度。
没用的原因是,这个剪枝剪掉的部分已经是离树枝很远离叶子很近的节点了,不如上面两种可以直接剪掉整条树枝。
#include<iostream>
#include<cstdio>
#include<map>
using namespace std;
#define int long long
int n, lim;
//map<tuple<int, int>, int> vis;
//void swap(int &x, int &y){
// int temp = x;x = y; y = temp;
//}
int gcd(int x, int y){
if(x == 0) return y;
return gcd(y%x, x);
}
void dfs(int x, int y, int t){
if(x==n||y==n) {cout<<t<<endl;exit(0);}
if(t>=lim) return; //>= not > !
if(x>y) swap(x, y);
if(x<0) return;
if(y*(1<<(lim-t))<n) return;
if(gcd(x, y)&&n%gcd(x, y)) {/*cout<<x<<' '<<y<<' '<<t<<endl;*/return;} //gcd may be 0 !!!
// if(vis[make_tuple(x, y)]&&vis[make_tuple(x, y)] <= t) return;
// vis[make_tuple(x, y)] = t;
dfs(x, y*2, t+1);dfs(y, y*2, t+1);
dfs(x, x*2, t+1);dfs(y, x*2, t+1);
dfs(x, x+y, t+1);dfs(y, x+y, t+1);
dfs(x, y-x, t+1);dfs(y, y-x, t+1);
// cout<<x<<' '<<y<<' '<<t<<endl;
}
main(){
cin>>n;
for(lim = 0;; ++lim){
// vis.clear();
dfs(0, 1, 0);
}
return 0;
}