第11期:搜索题集(持续更新中......)

目录

1 P1219 [USACO1.5]八皇后 Checker Challenge

2 P2392 kkksc03考前临时抱佛脚

3 P1443 马的遍历

4 P1135 奇怪的电梯

5 P2895 [USACO08FEB]Meteor Shower S

6 P1036 [NOIP2002 普及组] 选数

7 P2036 [COCI2008-2009#2] PERKET

8 P1433 吃奶酪

9 P1605 迷宫

10 P1019 [NOIP2000 提高组] 单词接龙

11 P1101 单词方阵

12 P2404 自然数的拆分问题

13 P1596 [USACO10OCT]Lake Counting S

14 P1162 填涂颜色

15 P1032 [NOIP2002 提高组] 字串变换

16 P1825 [USACO11OPEN]Corn Maze S


​​​​​​​

1 P1219 [USACO1.5]八皇后 Checker Challenge

#c++搜索与回溯

##基本思路:搜索 标记 AC

###注释:对角线d[i-j]后面必须加上一个n,因为i-j可能为负数,那么数组就会出错,所以将整体向右偏移n个单位(坐标偏移不会影响我们需要达到的目的),将所有可能变成正数;(因为i-j的最小值是-n+1,所以加上一个n就一定会变成一个正数)

本道题最重要的就是记录下皇后占领的格子(打标记的思想),通过此判断下一个皇后是否可以在某个位置,如果可以,则继续搜索下一个皇后可以在的位置,如果不行,则清除标记回到上一步,继续搜索;

可以先考虑六个皇后(即6*6网格),再将6改为n,并且输入n,就可以得出6到13个皇后的解了。

#include<bits/stdc++.h>
using namespace std;
int a[100],b[100],c[100],d[100];
/*
a数组表示的是行;
b数组表示的是列;
c数组表示的是左下到右上的对角线;
d数组表示的是左上到右下的对角线; 
*/ 
int total;//总数:记录解的总数
int n;//输入的数,即N*N的格子,全局变量,搜索中要用
void print(){
	if(total<=2){//保证只输出前三个解,如果解超出三个就不再输出,但后面的total还需要继续叠加 
		for(int k=1;k<=n;k++)
			cout<<a[k]<<" ";//for语句输出
		cout<<endl; 
	}
	total++;//total既是总数,也是前三个排列的判断 
}
void queen(int i){//搜索与回溯主体 
	if(i>n){
		print();
		return;
	}else{
		for(int j=1;j<=n;j++){//尝试可能的位置
			if(!b[j] && !c[i+j] && !d[i-j+n]){//如果没有皇后占领,执行以下程序 
				a[i]=j;//标记i排是第j个
				b[j]=1;//宣布占领纵行
				c[i+j]=1;
				d[i-j+n]=1;
				//宣布占领两条对角线
				queen(i+1);//进一步搜索,下一个皇后
				b[j]=0;
				c[i+j]=0;
				d[i-j+n]=0;
				//(回到上一步)清除标记 
			}
		}
	} 
}
int main(){
	cin>>n;//输入N*N网格,n已在全局中定义
	queen(1);//第一个皇后
	cout<<total<<endl;//输出可能的总数
	return 0; 
}

2 P2392 kkksc03考前临时抱佛脚

枚举每道题交给哪边的脑子解决,找到两边时间较大值的最小值。

对于一道题只有两个状态,一是加到左脑,二是加到右脑,所以是01背包。这里还可以用另一个思想,将一边的脑子加到最接近一半则另一边脑子时间就是正解。

//搜索解法
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x7fffffff;
int Left,Right,minn,ans;
int s[5],a[21][5];
void search(int x,int y){
	if(x>s[y]){
		minn=min(minn,max(Left,Right));
		return;
	}
	Left+=a[x][y];
	search(x+1,y);
	Left-=a[x][y];
	Right+=a[x][y];
	search(x+1,y);
	Right-=a[x][y];//搜索回溯 
}
int main(){
	ios::sync_with_stdio(false);
	for(int i=1;i<=4;i++) cin>>s[i];
	for(int i=1;i<=4;i++){
		Left=Right=0;
		minn=INF;
		for(int j=1;j<=s[i];j++) cin>>a[j][i];
		search(1,i);
		ans+=minn;
	}
	cout<<ans<<"\n";
	return 0;
}
//DP解法
#include<bits/stdc++.h>
using namespace std;
int a[5],sum,t,homework[21],dp[2501];
int main(){
	for(int i=1;i<=4;i++) cin>>a[i];
	for(int i=1;i<=4;i++){
		sum=0;
		memset(dp,0,sizeof(dp)); 
		for(int j=1;j<=a[i];j++){
			cin>>homework[j];
			sum+=homework[j];
		}
		for(int j=1;j<=a[i];j++){
			for(int k=sum/2;k>=homework[j];k--)//只要是总和的一半
				dp[k]=max(dp[k],dp[k-homework[j]]+homework[j]);//01背包 
		}
		t+=sum-dp[sum/2];//累加为另一个脑子 
	}
	cout<<t<<endl;
	return 0;
} 

3 P1443 马的遍历

用queue写bfs

#include<bits/stdc++.h>
using namespace std;
const int dx[8]={-1,-2,-2,-1,1,2,2,1};
const int dy[8]={2,1,-1,-2,2,1,-1,-2};//8个方向
queue<pair<int,int> > q;
int f[500][500];//存步数
bool vis[500][500];//判断走没走过 
int n,m,x,y; 
int main(){
	memset(f,-1,sizeof(f)); memset(vis,false,sizeof(vis));
	cin>>n>>m>>x>>y;
	f[x][y]=0; vis[x][y]=true;
	q.push(make_pair(x,y));
	while(!q.empty()){
		int xx=q.front().first,yy=q.front().second;
		q.pop();//取队首并出队
		for(int i=0;i<8;i++){
			int u=xx+dx[i],v=yy+dy[i];
			if(u<1||u>n||v<1||v>m||vis[u][v]) continue;//出界或走过了就不走
			vis[u][v]=true;
			q.push(make_pair(u,v));
			f[u][v]=f[xx][yy]+1; 
		} 
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			printf("%-5d",f[i][j]);
		}
		printf("\n");
	}
	return 0;
}

4 P1135 奇怪的电梯

这题和上题3 P1443 马的遍历思想一致。

#include<bits/stdc++.h>
using namespace std;
const int N=200+10;
const int dx[2]={1,-1};
queue<int> q; 
int n,a,b,K[N];
int f[N]; //存步数 
bool vis[N]; //判断走过没 
int main(){
	memset(f,-1,sizeof(f)); memset(vis,false,sizeof(vis));
	cin>>n>>a>>b;
	for(int i=1;i<=n;i++) cin>>K[i];
	f[a]=0; vis[a]=true;
	q.push(a);
	while(!q.empty()){
		int xx=q.front();
		q.pop();
		for(int i=0;i<2;i++){
			int u=xx+K[xx]*dx[i];
			if(u<1||u>n||vis[u]) continue;
			vis[u]=true;
			q.push(u);
			f[u]=f[xx]+1;
		}
	}
	cout<<f[b]<<endl;
	return 0;
}

5 P2895 [USACO08FEB]Meteor Shower S

#include<bits/stdc++.h>
using namespace std;
const int dx[5]={0,0,0,1,-1};
const int dy[5]={0,1,-1,0,0};//方便移动和处理陨石砸落 
queue<pair<int,int> > q;//构造队列,存储将处理点x,y坐标 
int n,ma[305][305],v[305][305],sx,sy,st,ans[305][305];
/*n:陨石数量 ma:陨石砸落地图 v:记录是否走过地图 
sx,sy,st:陨石x,y坐标及砸落时间 ans:每个点的最少时间图*/
int read(){
	int x=0,y=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar()) if(c=='-') y=-y;
	for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
	return x*y;
}
void write(int x){
	if(x<0) x=-x,putchar('-');
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
int ch(int a){
	if(a==-1) return 99999;
	else return a;
}//判断路过该点时是否陨石已经砸落,如果是没有陨石,相当于n年后砸落 
int main(){
	n=read();
	memset(ma,-1,sizeof(ma));//陨石初始化为-1
	for(int i=1;i<=n;i++){
		sx=read();sy=read();st=read();//输入陨石
		for(int j=0;j<5;j++){//上下左右中标记陨石
			if(sx+dx[j]>=0&&sy+dy[j]>=0&&(ma[sx+dx[j]][sy+dy[j]]==-1||ma[sx+dx[j]][sy+dy[j]]>st)){
			//如果该标记x,y坐标大于0且该点没有被陨石砸落或已标记时间没有该时间早,标记陨石
				ma[sx+dx[j]][sy+dy[j]]=st;	
			} 
		} 
	} 
	v[0][0]=1;//初始点设为已走过
	q.push(make_pair(0,0));//初始点放入队列
	while(!q.empty()){//只要队列不为空
		int x=q.front().first,y=q.front().second;//提取将处理点x,y坐标
		q.pop();
		int s=ans[x][y]+1;//即将标记的点时间是现在点的下一个单位
		if(ma[x][y]==-1){//如果该点安全,输出即将标记的点的时间-1
			write(s-1);
			puts("");
			return 0; 
		} 
		for(int i=1;i<=4;i++){
			int xx=x+dx[i],yy=y+dy[i];//提取将处理点的坐标
			if(xx>=0&&yy>=0&&s<ch(ma[xx][yy])&&v[xx][yy]==0){
			//将处理点需要x,y坐标大于等于0且该点没有走过并且陨石降落时间小于现时间
				q.push(make_pair(xx,yy));//放入将处理队列
				v[xx][yy]=1;//标记已经走过
				ans[xx][yy]=s;//将该点时间放入数组	
			} 
		}
	} 
	puts("-1\n");//如果出不了陨石区,输出-1
	return 0; 
}

6 P1036 [NOIP2002 普及组] 选数

#include<bits/stdc++.h> 
using namespace std;
const int maxn=1e8;
int n,k;
int x[21];
bool isprime(int n){
	int s=(int)sqrt(double(n));
	for(int i=2;i<=s;i++){
		if(n%i==0) return false;
	}
	return true;
}
int read(){
	int x=0,y=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar()) if(c=='-') y=-y;
	for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
	return x*y;
}
void write(int x){
	if(x<0) x=-x,putchar('-');
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
int search(int left,int presum,int start,int end){
//left为剩余的k,presum为前面累加的和,start和end为全组合剩下数字的选取范围;调用递归生成全组合,在过程中逐渐把K个数相加,当选取的数个数为0时,直接返回前面的累加和是否为质数即可
	if(left==0) return isprime(presum);
	int sum=0;
	for(int i=start;i<=end;++i){
		sum+=search(left-1,presum+x[i],i+1,end);
	} 
	return sum;
}
int main(){
	n=read(); k=read();
	for(int i=0;i<n;i++) x[i]=read();
	write(search(k,0,0,n-1));
	puts("");
	return 0; 
}

7 P2036 [COCI2008-2009#2] PERKET

//dfs 
#include<bits/stdc++.h> 
#define int long long
using namespace std;
const int maxn=20;
int n,ans=0x7fffffff;
int s[maxn],b[maxn];
int read(){
	int x=0,y=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar()) if(c=='-') y=-y;
	for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
	return x*y;
}
void write(int x){
	if(x<0) x=-x,putchar('-');
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
void dfs(int i,int x,int y){//i是表示目前的配料编号,x为酸度,y为甜度
	if(i>n){
		//注意,必须大于n才表示全部搜完
		if(x==1&&y==0) return;
		//判断清水的情况
		ans=min(abs(x-y),ans);
		//更新ans
		return; 
	} 
	//分两种情况搜索:1添加 2不添加
	dfs(i+1,x*s[i],y+b[i]);
	dfs(i+1,x,y);
	//无需回溯 
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++){
		s[i]=read();
		b[i]=read();
	}
	dfs(1,1,0);
	write(ans);
	puts(""); 
	return 0; 
}
//位运算 状态压缩 
#include<bits/stdc++.h> 
#define int long long
using namespace std;
const int maxn=20;
int n,ans=0x7fffffff;
int s[maxn],b[maxn];
int read(){
	int x=0,y=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar()) if(c=='-') y=-y;
	for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
	return x*y;
}
void write(int x){
	if(x<0) x=-x,putchar('-');
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
void solve(){
	for(int i=1;i<(1<<n);++i){//因为至少有一种配料,所以从1开始
		int S=1,B=0;//注意总酸度初始值为1!
		for(int j=0;j<n;j++){
			if((i>>j)&1){//判断是否为1
				S*=s[j];
				B+=b[j]; 
			}
		}
		ans=min(ans,abs(S-B));//注意加绝对值 
	}
}
signed main(){
	n=read();
	for(int i=0;i<n;i++){
		s[i]=read();
		b[i]=read();
	}
	solve();
	write(ans);
	puts(""); 
	return 0; 
}

8 P1433 吃奶酪

①状态dp。设f[i][s]表示从i点出发遍历集合为s的点的路程最小值(i也包括在s里),枚举s里的其他点进行转移。

边界为f[i][s]=0(s中只有i)。

注意最后答案要加上到(0,0)的距离。

时间复杂度O(n*2^n)

#include<bits/stdc++.h>
using namespace std;
typedef double db;
db x[20],y[20],f[20][35000];
template<class T> T min(T a,T b) {return a<b?a:b;}
db dis(int a,int b) {return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));}
int main(){
    int n;scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%lf%lf",&x[i],&y[i]);
    memset(f,127,sizeof(f));
    for(int s=1;s<=(1<<n)-1;s++)
    for(int i=1;i<=n;i++){
        if((s&(1<<(i-1)))==0) continue;
        if(s==(1<<(i-1))) {f[i][s]=0;continue;}
        for(int j=1;j<=n;j++){
            if((s&(1<<(j-1)))==0||i==j) continue;
            f[i][s]=min(f[i][s],f[j][s-(1<<(i-1))]+dis(i,j));
        }
    }
    db ans=-1;
    for(int i=1;i<=n;i++){
        db s=f[i][(1<<n)-1]+dis(i,0);
        if(ans==-1||ans>s) ans=s;
    }
    printf("%.2lf\n",ans);
    return 0;
}

②忽然发现其实可以用状压dp的思想来优化dfs...

观察数据,15个奶酪!这数据并不大,根据题意,老鼠走的路径是无后效性的,只要经过的点一致,所在的点也一致,接下来所要走的路径就是等价的

所以可以用一个二进制数来记录走过的点,另一个数记录老鼠所在的点,如果之后搜到的答案比这个点要大,就不继续搜即可,大致思想就是这样

#include <bits/stdc++.h>

using namespace std;

int n;
long double a[30][2],lt[30][30],zt[(1<<15)+15][18];//记录状态,第一维记录走过的点,第二维记录所在的点
long double cc1,cc2,answ;
int bj[30],i,j;

void dfs(int y,int ww,int x,long double ans){//深搜
	if(x==n+1) if(answ==0 || answ>ans) {answ=ans;return;}
	for(int g=1;g<=n;g++)
	{
		if(!bj[g])
		{
			int xb=ww+(1<<(g-1));//xb表示当前已走过的点,有标记数组挡着不用怕二进制数会进位
			if(zt[xb][g]!=0)
			if(zt[xb][g]<=ans+lt[y][g]) continue;//如果生成的路径长度比之前的还要长就不对该点继续dfs
			bj[g]=1;//标记
			zt[xb][g]=ans+lt[y][g];//记录状态
			dfs(g,xb,x+1,zt[xb][g]);//继续往下搜
			bj[g]=0;//回溯
		}
	}
	return;
}

int main(){
	cin>>n;
	a[0][0]=0;a[0][1]=0;
	for(i=1;i<=n;i++)
	{
		scanf("%Lf %Lf",&a[i][0],&a[i][1]);
		for(j=0;j<i;j++)//对两点距离初始化
		{
			cc1=a[i][0]-a[j][0];
			cc2=a[i][1]-a[j][1];
			lt[j][i]=sqrt(cc1*cc1+cc2*cc2);//记录两点距离
			lt[i][j]=lt[j][i];
		}
	}
	dfs(0,0,1,0);
	printf("%.2Lf",answ);
	return 0;
}

③简单介绍一下搜索的技巧之一:卡时。

什么时候可以用到卡时呢?

当求最优解(max,min)(max,min)时,数据范围较大(但是如果过大那就别想了),我们可以贪心的卡时一下。

总的来说,卡时就是不断的试我们认为的最优解,次优解,最终找出真正的最优解。

就这道题来说,我们可以贪心的每次找离目前点最近的点,次近的点,以达到找到的解中在规定时限内出现最优解的情况。

看到这里可能会很笼统,好吧,其实就是在朴素的搜索里加一个特判,如果我们发现即将要超时,那么直接输出我们找到过的最优解,(其实也就是懵)

#include<bits/stdc++.h> 
#define ri register int
#define N 17
#define P 100000000

struct node {
	double d;
	int num;
};

double x[N],y[N];
bool vis[N];
node dis[N][N];
double minn=100000000.0;
int n;
int l=0;

bool cmp(node a,node b) {
	return a.d<b.d;
}

void dfs(int cnt,int now,double sum) {
	l++;
	if(l>=30000000) { //我们发现时间不够了,但是我们还没有找完,怎么办?
	//我们臭不要脸的告诉出题人我们已经找过的答案中的最优答案,
	//由于我们找的答案也非常接近最有答案,所以大概率这个答案是正确的。
		int t=clock();
		if(t>=940) {
			printf("%.2lf",minn);
			exit(0);
		}
		

	}
	if(sum>=minn)return;
	
	if(cnt==n) {
		minn=sum<minn?sum:minn;//找最优解
		return ;
	}
	for(ri i=1; i<=n; i++) { //枚举
		if(vis[dis[now][i].num]==true||now==dis[now][i].num)continue;
		vis[dis[now][i].num]=true;
		dfs(cnt+1,dis[now][i].num,sum+dis[now][i].d);
		vis[dis[now][i].num]=false;
	}
}
int main() {
	std::cin>>n;
	for(ri i=2; i<=n+1; i++)std::cin>>x[i]>>y[i];
	x[1]=0.0,y[1]=0.0,n+=1;
	for(ri i=1; i<=n; i++) {
		for(ri j=1; j<=n; j++)dis[i][j].d=sqrt((x[j]-x[i])*(x[j]-x[i])+(y[j]-y[i])*(y[j]-y[i])),dis[i][j].num=j;
		std::sort(dis[i]+1,dis[i]+n+1,cmp);//算出每两个点的距离并且进行排序。
	}
	vis[1]=true;
	dfs(1,1,0.0);//开始搜索
	printf("%.2lf",minn);
	return 0;
}

9 P1605 迷宫

题整体来说比较简单,使用深搜一个个查,使用一个数组map记录障碍的地方,再使用一个temp来标记自己所走过的路;

int dx[4]={0,0,1,-1};

int dy[4]={-1,1,0,0};

使用自动选择方向来代替4个if判断(使代码更加简洁长度变短);

如果没有障碍并且不是自己走过的,就进一步搜索,把自己走过的路打上标记,返回时,再将标记还原;

###注意:有些同学可能觉得就在地图map数组上打标记(自己走过的路)比较简单,走过的路和障碍可能引起混淆,如果只用map数组的话,可能只的得到80分

这里再给大家一个基本的深搜模板:

int search(int t)
{
    if(满足输出条件)
    {
        输出解;
    }
    else
    {
        for(int i=1;i<=尝试方法数;i++)
            if(满足进一步搜索条件)
            {
                为进一步搜索所需要的状态打上标记;
                search(t+1);
                恢复到打标记前的状态;//也就是说的{回溯一步}
            }
    }
}

###整个模板有几个地方需要注意:

1.第一个if是符合输出解的条件,第二个if是符合进一步搜索的条件;

2.下一步搜索时,不是使用return search(t+1),直接search(t+1);(新手可能会注意不到这个关键的地方,以至于每次写完不知道为什么只得到一个答案就返回主程序了

3.for循环之后的if可以是多个;

4.for循环边界,例如:

1>方向是四个,那么边界肯定就是4;(帖主用3,是因为从0开始的)

2>素数环需要尝试1至20,那么边界就是20。

#include<bits/stdc++.h>
using namespace std;
int mp[6][6];//地图
bool vis[6][6];//走过的标记 
int dx[4]={0,0,1,-1};
int dy[4]={-1,1,0,0};
int total,fx,fy,sx,sy,T,n,m,l,r;//total计数器,fx,fy是终点坐标,sx,sy是起点坐标,T是障碍总数,n,m是地图的长和宽,l,r是障碍的横坐标和纵坐标
void dfs(int x,int y){
	if(x==fx&&y==fy){//fx表示结束x坐标,fy表示结束y坐标
		total++;//总数增加
		return;//返回,继续搜索 
	}else{
		for(int i=0;i<4;i++){//0——3是左,右,下,上四个方向
			int u=x+dx[i],v=y+dy[i];
			if(vis[u][v]==0&&mp[u][v]==1){//判断没有走过和没有障碍
				vis[x][y]=1;//走过的地方打上标记
				dfs(u,v);
				vis[x][y]=0;//还原状态 
			}
		}
	} 
} 
int main(){
	ios::sync_with_stdio(false);
	cin>>n>>m>>T;//n,m长度宽度,T障碍个数
	for(int ix=1;ix<=n;ix++){
		for(int iy=1;iy<=m;iy++){
			mp[ix][iy]=1;//把地图刷成1 
		}
	}
	cin>>sx>>sy;//起始x,y
	cin>>fx>>fy;//结束x,y 
	for(int i=1;i<=T;i++){
		cin>>l>>r;//l,r是障碍坐标
		mp[l][r]=0;
	} 
	dfs(sx,sy);
	cout<<total<<endl;//输出总数
	return 0;
} 

10 P1019 [NOIP2000 提高组] 单词接龙

  • 两个单词合并时,合并部分取的是最小重叠部分

  • 相邻的两部分不能存在包含关系就是说如果存在包含关系,就不能标记为使用过。

  • 每个单词最多出现两次.

(其实也就是读题问题。这些都是我所犯的错误,希望大家能注意一下)

好了。然后是解题思路。

首先是预处理,用yc[i][j]来存储 第i个单词 后连接 第j个单词 的 最小重叠部分(mt函数)

后来预处理完了之后就是深搜:

先从第一个到最后一个单词看一看哪个单词是指定字母为开头的,作为深搜的第一个单词,同时标记使用过一次(vis[i]++)。然后继续搜吧。

以下是代码。 mt函数可能有点难理解。拿草纸模拟一下就能很直观知道在干什么了。

#include<bits/stdc++.h>
using namespace std;
int n;//单词数
string tr[30];//存储字符串
int yc[30][30];//两个字母的最小重叠部分
int vis[30];//判断单词使用的频率
int mt(int x,int y){//mt函数,返回x单词后连接一个y单词的最小重叠部分
	bool pp=true;
	int ky=0;
	for(int k=tr[x].size()-1;k>=0;k--){//从x单词尾部向前看看最小重叠部分是从哪里开始的,因为是倒着来,所以保证是最小的
		for(int kx=k;kx<tr[x].size();kx++){
			if(tr[x][kx]!=tr[y][ky++]){
				pp=false;
				break;
			}
		}
		if(pp==true){//如果说当前以k为开头的前一个单词后缀 ,是后面单词的前缀,就马上返回重叠部分。(tr[x].size()-k是找出来的规律)
            return tr[x].size()-k;        } 
        ky=0;
        pp=true;//不行就继续
	}
	return 0;
}//可能这里有点难理解。可以手动模拟一下
char ch;//开头字母 
int ans=-1;//答案 
int an=0;//每次搜到的当前最长串 
void dfs(int p){//p为尾部单词编号(p的后缀就是“龙”的后缀,因为p已经连接到”龙“后面了)
    bool jx=false; 
    for(int j=1;j<=n;j++){
        if(vis[j]>=2) continue;//使用了两次就跳过 
        if(yc[p][j]==0) continue;//两单词之间没有重合部分就跳过 
        if(yc[p][j]==tr[p].size() || yc[p][j]==tr[j].size()) continue;//两者存在包含关系就跳过 
        an+=tr[j].size()-yc[p][j];//两单词合并再减去最小重合部分 
        vis[j]++;//使用了一次
        jx=true;//标记一下当前已经成功匹配到一个可以连接的部分 
        dfs(j); //接上去
        an-=tr[j].size()-yc[p][j];//回溯,就要再减回去那一部分长度 
        vis[j]--;//回溯,使用-- 
    }
    if(jx==false){//jx==false说明不能再找到任何一个单词可以相连了 
        ans=max(ans,an);//更新ans 
    }
    return;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        cin>>tr[i];
    cin>>ch; 
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            yc[i][j]=mt(i,j); 
        }
    }//预处理yc数组。yc[i][j]就表示,i单词后连接一个j单词的最小重叠部分 
    //比如 i表示at,j表示att. yc[i][j]就为2 但是yc[j][i]就为0.
    //预处理是一个关键
     
    for(int i=1;i<=n;i++){//从头到尾看一下有没有以指定开头字母为开头的单词 
        if(tr[i][0]==ch){//如果有,就以当前单词为基准进行搜索。 
            vis[i]++;//使用过一次 
            an=tr[i].size();//更新当前串长度 
            dfs(i);//接上
            vis[i]=0;//消除影响 
        } 
    } 
    printf("%d\n",ans);
    return 0;
}

11 P1101 单词方阵

①搜索染色

#include<bits/stdc++.h>
using namespace std;
int c[10000][2],d=0;//c[10000][2]用来存“y”出现的地址,d是“y”的个数 
int x[9]={0,1,0,1,-1,0,-1,1,-1};//八个方位
int y[9]={0,0,1,1,0,-1,-1,-1,1};//方便比对
int n; 
char a[103][103],b,k[9]=" yizhong";
bool s[102][102];//定义染色体,“0”输出“*”,“1”正常输出
bool f(int i,int j,int m,int n,int next){//i,j为数组位置,m,n是方位
	if(next>=8){//next是“yizhong”的第几个字符
		s[i][j]=1;//比对完毕,进行染色
		return 1;//返回
	}
	if(a[i+m][j+n]==k[next]){//如果该位置上的字符与对应字符一致,则继续
		if(f(i+m,j+n,m,n,next+1)){
			s[i][j]=1;//染色
			return 1;//返回上一层 
		}
	}
	return 0;//不一致,则结束 
} 
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>b;
			a[i][j]=b;//输入字符
			if(b=='y'){
				c[++d][0]=i;//记录y的位置
				c[d][1]=j;//d为y的个数 
			} 
		}
	}
	//全方位搜索
	while(d){
		int i=c[d][0],j=c[d][1];
		for(int k=1;k<=8;k++){//全方位递归搜索 
			if(a[i+x[k]][j+y[k]]=='i'){
				if(f(i+x[k],j+y[k],x[k],y[k],3)){
					s[i][j]=1;
				} 
			}
		}
		d--;
	} 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(s[i][j]) cout<<a[i][j];//染了色,便正常输出
			else cout<<"*";//否则输出“*” 
		}
		cout<<endl; 
	}
	return 0;
}

②这道题是一个DFS的题目,从八向搜索。可以设置一个八向的常量数组,搜索每一个方向,如果满足条件就递归,否则结束递归。当搜索到第7个单词‘g’时,用vis保存路径,此次DFS也就结束了。

首先在矩阵中搜索,找到y和i相连的方向k,以k方向进行DFS。

#include <bits/stdc++.h>
using namespace std;
const int maxn=100+10;
struct node
{
    int x,y;
}c[maxn];//记录路径
char fz[maxn][maxn],stand[]="yizhong";//fz保存单词矩阵,stand保存保准的“yizhong”便于匹配
int vis[maxn][maxn];//保存路径,是否该点为答案
int dir[][2]={{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};//八向的常量数组
void dfs(int x,int y,node c[],int k,int cur)
{
    if(cur==7){
        for(int i=0;i<7;i++)
            vis[c[i].x][c[i].y]=1;
    }
    else{
        int dx=x+dir[k][0];//沿着正确的k方向搜索
        int dy=y+dir[k][1];
        if(cur==6||fz[dx][dy]==stand[cur+1]){
            c[cur].x=x;c[cur].y=y;
            dfs(dx,dy,c,k,cur+1);
        }
    }
}
int main()
{
    //freopen("input.txt","r",stdin);
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%s",fz[i]);
    memset(vis,0,sizeof(vis));
    for(int i=0;i<n;i++)//搜索y,i相连的可能的方向k,以k为方向进行DFS
        for(int j=0;j<n;j++)
            if(fz[i][j]=='y') for(int k=0;k<8;k++){
                    int x=i+dir[k][0];
                    int y=j+dir[k][1];
                    if(fz[x][y]=='i')
                        dfs(i,j,c,k,0);
                    }
    for(int i=0;i<n;i++){//输出结果
        for(int j=0;j<n;j++)
            if(vis[i][j]) printf("%c",fz[i][j]);
            else printf("*");
        printf("\n");
    }
    return 0;
}

12 P2404 自然数的拆分问题

#include<bits/stdc++.h>
using namespace std;
int a[10001]={1},n;
void print(int t){
	for(int i=1;i<=t-1;++i)//输出一种拆分方案
		cout<<a[i]<<"+"; 
	cout<<a[t]<<endl;
}
void dfs(int s,int t){
	for(int i=a[t-1];i<=s;i++){
		if(i<n){//当前数i要大于等于前一位数,且不超过n
			a[t]=i;//保存当前拆分的数i
			s-=i;//s减去数i,s的值将继续拆分
			if(s==0) print(t);//当s=0时,拆分结束输出结果
			else dfs(s,t+1);//当s>0时,继续递归
			s+=i;//回溯:加上拆分的数,以便产生所有可能的拆分
		}
	}
}
int main(){
	cin>>n;
	dfs(n,1);//将要拆分的数n传递给s
	return 0;
}

13 P1596 [USACO10OCT]Lake Counting S

        从任意的W开始,不停地把邻接的部分用“.”代替。1次DFS后与初始的这个W连接的所有W就都被替换成了“.”,因此直到图中不再存在W为止,总共进行DFS的次数就是答案了。8个方向共对应了8种状态转移,每个格子作为DFS的参数至多被调用一次,所以复杂度为O(8*N*M)=O(N*M)。(白书)

#include<bits/stdc++.h>
using namespace std;
const int maxn=100+10;
int n,m;
char ch,field[maxn][maxn];//园子
void dfs(int x,int y){//现在位置(x,y)
	//将现在所在位置替换为.
	field[x][y]='.';
	
	//循环遍历移动的8个方向
	for(int dx=-1;dx<=1;dx++){
		for(int dy=-1;dy<=1;dy++){
			//向x方向移动dx,向y方向移动dy,移动的结果为(nx,ny)
			int nx=x+dx,ny=y+dy;
			//判断(nx,ny)是不是在园子内,以及是否有积水
			if(0<=nx&&nx<n&&0<=ny&&ny<m&&field[nx][ny]=='W'){
				dfs(nx,ny);
			} 
		}
	}
	return;
} 

void solve(){
	int res=0;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			if(field[i][j]=='W'){
				//从有W的地方开始dfs
				dfs(i,j);
				res++; 
			}
		}
	}
	printf("%d\n",res);
}
int main(){
	//freopen("P1596_4.in","r",stdin);
	scanf("%d %d\n",&n,&m);
	for(int i=0;i<n;i++){
		for(int j=0;j<=m;j++){
			scanf("%c",&ch);
			field[i][j]=ch;
		}
	}
	solve();
	return 0;
}

14 P1162 填涂颜色

染色法

#include<bits/stdc++.h>
using namespace std;
int a[32][32],b[32][32];
int dx[5]={0,-1,1,0,0};
int dy[5]={0,0,0,-1,1};//第一个表示不动,是充数的,后面的四个分别是上下左右四个方向
int n;
void dfs(int x,int y){ 
	if(x<0||x>n+1||y<0||y>n+1||a[x][y]!=0) return;//如果搜过头或者已经被搜过了或者本来就是墙的就往回
	a[x][y]=1;//染色
	for(int i=1;i<=4;i++) dfs(x+dx[i],y+dy[i]);//向四个方向搜索 
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>b[i][j];
			if(b[i][j]==0) a[i][j]=0;
			else a[i][j]=2;
		}
	}
	dfs(0,0);//搜索 从(0,0)开始搜 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[i][j]==0) cout<<2<<" ";//如果染过色以后i,j那个地方还是0,说明没有搜到,就是周围有墙,当然就是被围住了,然后输出2
			else cout<<b[i][j]<<" ";//因为被染色了,本来没有被围住的水和墙都染成了1,所以就输出b[i][j]
		}
		cout<<"\n";//换行 
	} 
	return 0;
}

15 P1032 [NOIP2002 提高组] 字串变换

//待看

“题意已经把做法写得特别露骨了。。。最小步数,最多6个变换规则。。。。广搜自不必说,不仅可以寻找解而且还能判断步数(根据广搜首解最优的性质可以得到)。

开两个数组记录串的转换关系,然后以a串(原串)为起点开始搜索,搜索目标是b串。

需要一个map记录某个串是不是被搜到过,如果已经搜过了就不再继续搜 。

我们枚举当前队列中队头那个串的每一个位置,对每一个位置枚举所有可能的转换手段,然后去尝试拼接。

拼接函数借鉴了一下楼上stdcall大爷题解的思路,对于一个试图要改变的串str,我们试图在它的第i位用第j种手段改变,首先判断是否可行,然后再逐位拼接。并且如果拼接出的串是合法的,那么我们就把这个串继续压入队列,再次搜索,中间记录一下步数step和ans。

最后输出ans时判断,如果ans超过了步数限制直接输出无解,否则输出步数。

不过我发现,ans等于0时应该也是无解,这样会导致如果用ans<=10来判断是不是超出步数会WA掉第三个点。”

//bfs
#include<bits/stdc++.h>
using namespace std;
const int maxn=15;
struct node{//方便搜索,也可以使用pair简化 
	string str;
	int step; 
};
string a,b;
string orginal[maxn];
string translated[maxn];
int n,ans;
map<string,int> ma;//很重要的东西,用来判重,否则会TLE在第3点和第5点

string trans(const string& str,int i,int j){
	string ans="";
	if(i+orginal[j].length() > str.length()) return ans;
	for(int k=0;k<orginal[j].length();k++){
		if(str[i+k] != orginal[j][k]){
			return ans;
		}
	}
	ans=str.substr(0,i);
	ans+=translated[j];
	ans+=str.substr(i+orginal[j].length());
	return ans;
}

void bfs(){
	queue<node> q;
	node s;
	s.str=a;
	s.step=0;
	q.push(s);
	
	while(!q.empty()){
		node u=q.front();
		q.pop();
		string temp;
		
		if(ma.count(u.str) == 1){//剪枝,判断重复的路径
			continue; 
		}
		if(u.str == b){
			ans=u.step;
			break;
		}
		ma[u.str]=1;
		for(int i=0;i<u.str.length();i++){//枚举当前串所有可能位置
			for(int j=0;j<n;j++){//枚举所有可能手段
			 	temp=trans(u.str,i,j);
				if(temp!=""){
					node v;
					v.str=temp;
					v.step=u.step+1;
					q.push(v);
				}	
			} 
		}
	}
	if(ans>10||ans==0) cout<<"NO ANSWER!\n";
	else cout<<ans<<"\n"; 
}
int main(){
	cin>>a>>b;
	while(cin>>orginal[n]>>translated[n]){
		n++;
	}
	bfs();
	return 0;
}

题解

//KMP
#include<bits/stdc++.h>
#define debug cout << "debug"<<endl

using namespace std;
#define il inline
#define re register
typedef long long ll;

string a,b;

struct Node {//用于queue中存放,一个是字串,一个是搜索的“深度”
	string data;
	int step;
	Node(string _data,int _step):data(_data),step(_step) {}
	Node() {}
};
queue<Node>q;
string change[10];//改成哪个
string diff[10];//改哪个
/*即
搜索diff[i]
改成change[i]
*/

int nxt[10][10000];//kmp的next数组
map<string,bool>mp;//用于判重,避免重复搜索
il void get_next(int x)//找next,具体的可以翻翻网上的Blog。
{
	re int i,j=0;
	for (i=2; i<diff[x].length(); i++) {
		while (j&&diff[x][i]!=diff[x][j+1]) j=nxt[x][j];
		if (diff[x][j+1]==diff[x][i]) j++;
		nxt[x][i]=j;
	}
}

il void KMP(string a,int x,int step)//寻找匹配的串,顺便修改并添加到queue中
{
	string z=a;
	a=" "+a;//神奇的操作,。。。
	re int i,j=0;
	for (i=1; i<a.length(); i++) {
		while (j>0&&diff[x][j+1]!=a[i])	j=nxt[x][j];
		if (diff[x][j+1]==a[i]) j++;
		if (j==diff[x].length()-1) {//找到了~
			re int t= i-diff[x].length()+1;//记录位置
			string tmp=z.substr(0,t)+change[x]+z.substr(t+diff[x].length()-1);//修改(就不用replace,(真香))
			q.push(Node(tmp,step+1));
			j=nxt[x][j];//继续找
/*
第一次交由于脑子不好,找了一遍就return了。
*/
		}
	}
	return;
}

int cn=0;
int main()
{
	//freopen("in.txt","r",stdin);
	cin >> a >> b;
	string t1,t2;
	while (cin >>t1>>t2) {
		change[++cn]=t2;
		diff[cn]=" "+t1;//继续神奇的操作
		get_next(cn);
	}
	q.push(Node(a,0));
	while (!q.empty()) {
		Node now=q.front();
		q.pop();
		string x=now.data;
		if (mp[x]) continue;//map判重
		mp[x]=1;//标记
		if (now.step>10) {//找不到(因为bfs是按照step:1,2,3...来找的,所以一旦到了STEP11时一定无解了)
			puts("NO ANSWER!");
			exit(0);
		}
		if (x==b) {//找到,由于搜索有序,step一定是最小的
			cout << now.step<<endl;
			exit(0);
		}
		for (re int i=1; i<=cn; i++) {//枚举所有模式串,匹配文本串
			KMP(x,i,now.step);
		}
	}
	puts("NO ANSWER!");//最后由于map的判重,可能导致queue为空,于是到达这里的数据肯定是无解的
	exit(0);
}

16 P1825 [USACO11OPEN]Corn Maze S

#include<bits/stdc++.h>
using namespace std;
int a[500][500],qx,qy,zx,zy,cz1,cz2,bj[500][500],n,m;
int d[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
struct node{
	int x;
	int y;
	int bs;
}ans[101010];
int cz(int x,int y){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(!(i==x&&j==y)){//注意不能与原点相同
				if(a[i][j]==a[x][y]){//直接判断匹配 
					cz1=i;
					cz2=j;
					return 0; 
				}
			}
		}
	}
}
int bfs(){
	int head=0,tail=1;
	do{
		head++;
		if(a[ans[head].x][ans[head].y]>='A'&&a[ans[head].x][ans[head].y]<='Z'){//判断是不是脚踩传送门
			cz(ans[head].x,ans[head].y);//找到对应的传送门坐标
			ans[head].x=cz1;
			ans[head].y=cz2;
		}
		for(int i=0;i<4;i++){
			int xx=ans[head].x+d[i][0];
			int yy=ans[head].y+d[i][1];
			if(a[xx][yy]!=0&&bj[xx][yy]==0){//广搜
				bj[xx][yy]=1;
				tail++;
				ans[tail].x=xx;
				ans[tail].y=yy;
				ans[tail].bs=ans[head].bs+1;
				if(xx==zx&&yy==zy){//判断目标 
					printf("%d\n",ans[tail].bs);
					return 0; 
				} 
			}
		}
	}while(head<tail);
}
int main(){
	char s;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;++j){
			cin>>s;//输入预处理
			if(s=='.') a[i][j]=1;
			if(s>='A'&&s<='Z') a[i][j]=s;
			if(s=='@'){
				qx=i;
				qy=j;
				a[i][j]=1;
			}
			if(s=='='){
				zx=i;
				zy=j;
				a[i][j]=1;
			} 
		}
	}
	bj[qx][qy]=1;//注意一定要标记起点
	ans[1].x=qx;
	ans[1].y=qy;
	bfs(); 
	return 0;
}

②双向广搜基本思路:从起点和终点分别广搜,每次选择状态少的队列进行扩展,当两个方向的搜索第一次相遇时,就找到了最优解,直接返回答案

在bfs()里,我们写了双向广搜的基本思路,接下来,我们看到扩展用到了expand()函数,这个函数就是扩展和判断是否到达终点的函数,接下来我们看怎么写expand()

#include<bits/stdc++.h>

using namespace std;

char c[310][310];
int n, m, sx, sy, ex, ey, dx[5] = { 0,-1,1,0,0 }, dy[5] = { 0,0,0,-1,1 }, cnt[2][310][310] = { 0 }, vis[2][310][310] = { 0 };
queue<pair<int, int> >Q[2];

void handle(int& x, int& y)
{
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			if (c[x][y] == c[i][j] && (x != i || y != j)) 
			{ x = i, y = j; return; }
}

int expand(int sub)
{
	int nowx = Q[sub].front().first, nowy = Q[sub].front().second; Q[sub].pop();
	for (int i = 1; i <= 4; i++)
	{
		int newx = nowx + dx[i], newy = nowy + dy[i];
		if (newx >= 1 && newy >= 1 && newx <= n && newy <= m && c[newx][newy] != '#' && !vis[sub][newx][newy])
		{
			if (c[newx][newy] >= 'A' && c[newx][newy] <= 'Z')
			{
				int x = newx, y = newy;
				vis[sub][newx][newy] = 1, cnt[sub][newx][newy] = cnt[sub][nowx][nowy] + 1; handle(newx, newy);
			}
			else vis[sub][newx][newy] = 1;
			Q[sub].push(make_pair(newx, newy)), cnt[sub][newx][newy] = cnt[sub][nowx][nowy] + 1;
			if (vis[1 - sub][newx][newy])
			{
				if (c[newx][newy] >= 'A' && c[newx][newy] <= 'Z')
				{
					if (vis[1 - sub][newx][newy] == 2 || vis[sub][newx][newy] == 2)
						return cnt[1 - sub][newx][newy] + cnt[sub][newx][newy];
				}
				else return cnt[1 - sub][newx][newy] + cnt[sub][newx][newy];
			}
		}
	}
	return 0;
}

int bfs()
{
	Q[0].push(make_pair(sx, sy)), Q[1].push(make_pair(ex, ey));
	while (!Q[0].empty() && !Q[1].empty())
		if (Q[0].size() < Q[1].size()) { int tmp = expand(0); if (tmp) return tmp; }
		else { int tmp = expand(1); if (tmp) return tmp; }
	return -1;
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		getchar();
		for (int j = 1; j <= m; j++)
		{
			c[i][j] = getchar();
			if (c[i][j] == '@') sx = i, sy = j, vis[0][sx][sy] = 1;
			if (c[i][j] == '=') ex = i, ey = j, vis[1][ex][ey] = 1;
		}
	}
	printf("%d", bfs());
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值