搜索洛谷题解

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

题目大意:

一个n*n的矩阵中,横竖撇捺中,只能有一个皇后,求出可以摆放的方法。

思路:

首先,对于矩阵中的对角线有如下规律:

对于下标 i,j 来说,正对角线 i-j 相等,副对角线 i+j 相等。

定义四个数组,分别对应横向,竖向,正副对角线,用于判断是否放了皇后。

搜索时,按行进行搜索,当搜索的行数大于矩阵的行数n时,说明搜索完成,对结果计数并判断是否已经输出三次,若不足,输出当前的横向数组。继续搜索其他情况。

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=0x3f3f3f3f;
int n,cnt=0,sum=0;
int h[200]={0},l[200]={0};//横竖
int x1[200]={0},x2[200]={0};//两条对角线

void print(){//输出函数
	for(int i=1;i<=n;i++){
		cout<<h[i]<<" ";
	}
	cout<<endl;
}

void fang(int x,int y){//放置皇后(标记)
	h[x]=y,l[y]=1;
	x1[x+y]=1,x2[x-y+n]=1;
}

void qu(int x,int y){//取消皇后(取消标记)
	h[x]=0,l[y]=0;
	x1[x+y]=0,x2[x-y+n]=0;
}

bool right(int x,int y){//判断能否放置皇后
	if(h[x]==0 && l[y]==0 && x1[x+y]==0 && x2[x-y+n]==0){
		return true;
	}
	return false;
}

void dfs(int x){//x行
	if(x>n){
		cnt++;
		if(sum<3){
			sum++;
			print();
		}
		return ;
	}
	for(int i=1;i<=n;i++){//i列
		if(right(x,i)){
			fang(x,i);
			dfs(x+1);
			qu(x,i);
		}
	}
}
int main(){
	cin>>n;
	dfs(1);
	cout<<cnt<<endl;
	return 0;
}

B.P2392kkksc03考前临时抱佛脚

题目大意:

四门科目有四个练习题集,可以同时计算 2 道不同的题目,但是仅限于同一科。

求能够完成复习的最短时间。

思路:

代码:

#include<bits/stdc++.h>
using namespace std;
int Left,Right,minn,ans;
int s[5];
int 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(){
	cin>>s[1]>>s[2]>>s[3]>>s[4];
	for(int i=1;i<=4;i++){//减少码量
		Left=Right=0;
		minn=19260817;
		for(int j=1;j<=s[i];j++)
			cin>>a[j][i];
		search(1,i);
		ans+=minn;
	}
	cout<<ans;
	return 0;
}

C.P1036[NOIP2002 普及组] 选数

题目大意:

从n个整数中任选k个整数相加,可分别得到一系列的和。计算出和为素数共有多少种。

思路:

利用不降原则,即在选择的时候,按照输入的顺序依次去选择,保证不重不漏。

举个例子,在6个数中选3个的时候:

1 2 3
1 2 4
1 2 5
1 2 6

1 3...
1 4...
1 5...

2...
3...
......

dfs的选择的时候,每一次搜索的下标依次往后移动,并记录上当前的和为多少,方便判断和是否为素数,并记录升序序列下标,方便递归。

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=0x3f3f3f3f;
int n,k,a[25];
ll ans=0;

bool sushu(int x){//判断素数
	if(x==1)return false;
	for(int i=2;i<sqrt(x);i++){
		if(x%i==0)return false;
	}
	return true;
}

void dfs(int m,int sum,int id){//分别代表选择的个数,和,开始下标
	if(m==k){//搜索完成
		if(sushu(sum)==true) ans++;//为素数记数
		return ;
	}
	for(int i=id;i<n;i++){
		dfs(m+1,sum+a[i],i+1);
	}
	return;
}


int main(){
	cin>>n>>k;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	dfs(0,0,0);
	cout<<ans<<endl;
	return 0;
}

D.[COCI2008-2009#2] PERKET

题目大意:

有n种配料,对于每一种配料,我们知道它们各自的酸度s和苦度b。总的酸度为每一种配料的酸度总乘积;总的苦度为每一种配料的苦度的总和,因为没有任何食物以水为配料的(s>1,b>0)。

思路:

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=15;//养成良好习惯
int a[M],b[M],n,ans=0x7f;
//ans初始化为最大值
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*a[i],y+b[i]);
    dfs(i+1,x,y); 
    //这题无需回溯,不明白为何有些题解居然还用全局变量,非得回溯-_-||
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a[i],&b[i]);
        //读入,用cin太慢了
    }
    dfs(1,1,0);
    printf("%d\n",ans);
    return 0;
}

E.[NOIP2000 提高组] 单词接龙

题目大意:

给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,另外相邻的两部分不能存在包含关系

思路:

代码:

#include<bits/stdc++.h>
#define ll long long
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",ans);
    return 0;
}

F.单词方阵

题目大意:

"yizhong"八个方向,有输出,无输出“*”

思路:

代码:

#include<bits/stdc++.h>
using namespace std;
char s[105][105];
const string key="yizhong";
int n,idx[105][105]{};  
void dfs (int dr,int dc,int r,int c,int cnt){
	if(cnt==7)  
		for(int i=1;i<=7;i++)  
			idx[r-dr*i][c-dc*i]=1;
	if( r>=0&&r<n&&c>=0&&c<n &&s[r][c]==key[cnt])   
		dfs(dr,dc,r+dr,c+dc,cnt+1);
}

int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++)    
		scanf("%s",&s[i]);
	for(int i=0;i<n;i++) 
		for(int j=0;j<n;j++) 
			if(s[i][j]=='y') 
		for(int dr=-1;dr<=1;dr++) 
			for(int dc=-1;dc<=1;dc++)
			    if(s[i+dr][j+dc]=='i') 
			    	dfs(dr,dc,i,j,0);
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
		    if(idx[i][j]) 
			    printf("%c",s[i][j]);
		    else printf("*");
	    }
		printf("\n");
	}
	return 0;
}

G.P2404自然数的拆分问题

题目大意:

输入一个 n(1 <= n <= 8),要求你求出n的拆分成一些数字的和。每个拆分后的序列中的数字从小到大排序。输出这些序列,其中字典序小的序列需要优先输出。

思路:

第一种方法:搜索

循环枚举要拆分的数。如果i<ni<n(够减),就当前的拆分数为ii。如果nn被拆完了,则输出。

第二种方法:打表

代码:

方法一:搜索

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=0x3f3f3f3f;
int a[10001]={1},n;

int print(int t){
	for(int i=1;i<=t-1;i++)//输出一种拆分方案
		cout<<a[i]<<"+";
	cout<<a[t]<<endl;
}

int search(int s,int t){
	int i;
	for(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 search(s,t+1);//当s>0时,继续递归
			s+=i;//回溯:加上拆分的数,以便产生所有可能的拆分
		}
    }
}

int main(){
	cin>>n;
	search(n,1);//将要拆分的数n传递给s
	return 0;
}

方法二:打表

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=0x3f3f3f3f;
int n;
int main()
{
	cin>>n;
	if(n==1)printf("\n");
		else if(n==2)printf("1+1\n");
			else if(n==3)printf("1+1+1\n1+2\n");
				else if(n==4)printf("1+1+1+1\n1+1+2\n1+3\n2+2\n");
					else if(n==5)printf("1+1+1+1+1\n1+1+1+2\n1+1+3\n1+2+2\n1+4\n2+3\n");
						else if(n==6)printf("1+1+1+1+1+1\n1+1+1+1+2\n1+1+1+3\n1+1+2+2\n1+1+4\n1+2+3\n1+5\n2+2+2\n2+4\n3+3\n");
							else if(n==7)printf("1+1+1+1+1+1+1\n1+1+1+1+1+2\n1+1+1+1+3\n1+1+1+2+2\n1+1+1+4\n1+1+2+3\n1+1+5\n1+2+2+2\n1+2+4\n1+3+3\n1+6\n2+2+3\n2+5\n3+4\n");
								else printf("1+1+1+1+1+1+1+1\n1+1+1+1+1+1+2\n1+1+1+1+1+3\n1+1+1+1+2+2\n1+1+1+1+4\n1+1+1+2+3\n1+1+1+5\n1+1+2+2+2\n1+1+2+4\n1+1+3+3\n1+1+6\n1+2+2+3\n1+2+5\n1+3+4\n1+7\n2+2+2+2\n2+2+4\n2+3+3\n2+6\n3+5\n4+4\n");
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值