洛谷题单 【算法1-7】搜索

P2392 kkksc03考前临时抱佛脚

思路

  • 由于只能一科一科的复习,所以其实就是找出一个算法能够把n个数分成两堆,使得这两堆的差值最小
  • 贪心的想法是不行的
  • 用搜索做,实质就是枚举。每次决定选还是不选这个数,一旦所选的数总和超过sum/2就返回

实现

#include<bits/stdc++.h>
using namespace std;
int sum;
int a[4][21];
int len[4];
int total[4];
int min_;
void dfs(int x,int y,int s)
{
	if(y==len[x]-1 || s>=(total[x]/2.0))
	{
		if(max(s,total[x]-s)<min_)
		min_ = max(s,total[x]-s);
		
		return ;
	}
	dfs(x,y+1,s+a[x][y]);
	dfs(x,y+1,s);
}
int ans;
int main()
{
	for(int i=0;i<4;i++)
	{
		cin>>len[i];
	}
	for(int i=0;i<4;i++)
	{
		for(int j=0;j<len[i];j++)
		{
			cin>>a[i][j];
			total[i]+=a[i][j];
		}
		min_ = 2000000;
		dfs(i,0,0);
		ans+=min_;
	}
	cout<<ans;
	return 0;
}

P2036 [COCI2008-2009#2] PERKET

思路

dfs,注意初始条件应该是酸度为1,苦度为0。注意判断什么配料都没有加的情况。

实现

#include<bits/stdc++.h>
using namespace std;
int a[11];
int b[11];
int n;
int min_ = 10e9;
void dfs(int x,int y,int i)
{
	if(i==n)
	{
		if(x==1 && y==0)
		return ;
		if(abs(x-y)<min_)
		min_ = abs(x-y);
		
		return ;
	}
	dfs(x*a[i],y+b[i],i+1);
	dfs(x,y,i+1);
}
int ans;
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	{
		scanf("%d%d",&a[i],&b[i]);
	}
	dfs(1,0,0);
	cout<<min_;
	return 0;
}

P1101 单词方阵

思路

用dfs有一些不便之处,因为它不仅要求相邻,还要沿着同一方向连续摆放,dfs就必须多一个参数。不如搜索遍全方阵,找到一个’y’就停下来,把八个方向都试一次。

实现

#include<bits/stdc++.h>
using namespace std;
const int maxn = 110;
const int dx[] = {1, 1, 1, 0, 0, -1,-1,-1 };  
const int dy[] = {1, 0,-1, 1,-1, 0 , 1,-1 };
const string cmp = "yizhong"; 

char A[maxn][maxn], ans[maxn][maxn];
int mark[maxn][maxn], n;

void dfs(int x,int y) {
	for(int i = 0;i < 8;i++) { 
		int flag = 1;
		for(int j = 1;j <= 6;j++) {    
			int nx = x + j*dx[i];    
			int ny = y + j*dy[i];
			if(nx < 1 || nx > n || ny < 1 || ny > n) {  
				flag = 0;
				break;
			}
			if(cmp[j] != A[nx][ny]) {  
				flag = 0;
				break;
			}
		}
		if(flag == 0) continue;
		for(int j = 0;j <= 6;j++) { 
			int nx = x + j*dx[i];
			int ny = y + j*dy[i];
			ans[nx][ny] = A[nx][ny];
		}
	}
	return;
}

int main() {
	cin >> n;
	for(int i = 1;i <= n;i++) {
		for(int j = 1;j <= n;j++) {
			cin >> A[i][j];
		}
	}
	
	for(int i = 1;i <= n;i++) { 
		for(int j = 1;j <= n;j++) {
			if(A[i][j] == 'y') dfs(i,j);
		}
	}
	for(int i = 1;i <= n;i++) { 
		for(int j = 1;j <= n;j++) {
			if(ans[i][j] == 0) ans[i][j] = '*';
			cout << ans[i][j];
		}
		cout << endl;
	}
	return 0;
} 

P2404 自然数的拆分问题

思路

用回溯做,sum表示当前已选数之和,a表示上一个数。那这次选的时候必须要大于等于a才能不重复。

实现

#include<bits/stdc++.h>
using namespace std;
int n;

void dfs(int sum,int a,vector<int> &ans) {
	if(sum>n) return ;
	else if(sum==n)
	{
		cout<<ans[0];
		for(int i=1;i<ans.size();i++)
		cout<<"+"<<ans[i];
		cout<<endl;
	 } 
	for(int i=a;i<n;i++)
	{
		ans.push_back(i);
		dfs(sum+i,i,ans);
		ans.pop_back();
	}
}

int main() {
	cin>>n;
	vector<int> ans;
	dfs(0,1,ans);
	return 0;
} 

P1596 [USACO10OCT]Lake Counting S

思路

用dfs把所有经过的’w’改为’.',数有多少个连通块即可

实现

#include<bits/stdc++.h>
using namespace std;
int m,n;
int ans;
char p[110][110];
void dfs(int x,int y) {
	if(x<0 || x>=m || y<0 || y>=n || p[x][y]=='.') return ;
	p[x][y] = '.';
	dfs(x+1,y);
	dfs(x-1,y);
	dfs(x,y+1);
	dfs(x,y-1);
	dfs(x+1,y+1);
	dfs(x+1,y-1);
	dfs(x-1,y+1);
	dfs(x-1,y-1);
}

int main() {
	cin>>m>>n;
	for(int i=0;i<m;i++)
	{
		scanf("%s",p[i]);
	}
	for(int i=0;i<m;i++)
	{
		for(int j=0;j<n;j++)
		{
			if(p[i][j]=='W')
			{
				dfs(i,j);
				ans++;
			}
		}
	}
	cout<<ans;
} 

P1162 填涂颜色

思路

读入数据的时候将1修改为-1,dfs将外面所有的0修改为1,输出时分类即可

实现

#include<bits/stdc++.h>
using namespace std;
int n;
int ans;
int p[32][32];
void dfs(int x,int y) {
	if(x<0 || x>=n || y<0 ||y>=n || p[x][y]!=0) return ;
	p[x][y] = 1;
	dfs(x+1,y);
	dfs(x-1,y);
	dfs(x,y+1);
	dfs(x,y-1);
//	dfs(x+1,y+1);
//	dfs(x+1,y-1);
//	dfs(x-1,y+1);
//	dfs(x-1,y-1);
}

int main() {
	cin>>n;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
		scanf("%d",&p[i][j]);
		if(p[i][j]==1) p[i][j] = -1;
	}
	}

	for(int i=0;i<n;i++)
	{
		if(p[i][0]==0)
		{
			dfs(i,0);
		}
		if(p[i][n-1]==0)
		{
			dfs(i,n-1);
		}
	}
	for(int i=0;i<n;i++)
	{
		if(p[0][i]==0)
		{
			dfs(0,i);
		}
		if(p[n-1][i]==0)
		{
			dfs(n-1,i);
		}
	}
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			if(p[i][j] == 1) cout<<"0 ";
			else if(p[i][j] == -1) cout<<"1 ";
			else cout<<"2 ";
		}
		
		cout<<endl;
	}
} 

P1443 马的遍历

思路

bfs,每个点上的值就是第一次经过该点时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};
queue<pair<int,int> > q;
int f[500][500];
bool vis[500][500];
int main()
{
	int n,m,x,y;
	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;
}

P1135 奇怪的电梯

思路

bfs 和上题一样

实现

#include<bits/stdc++.h>
using namespace std;
#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};
queue<pair<int,int> > q;
int f[210]; 
bool vis[210];
int main()
{
	int n,x,y;
	int step = 0;
	memset(vis,false,sizeof(vis));
	cin>>n>>x>>y;
	for(int i=1;i<=n;i++) cin>>f[i];
	
	vis[x]=true;
	q.push(make_pair(x,0));
	int xx;
	while(!q.empty())
	{
		xx=q.front().first;
		step = q.front().second;
		q.pop();
		if(xx==y) break;
		
		int u=xx+f[xx];
		if(u>=1 && u<=n && !vis[u])
		{
			vis[u] = true;
		    q.push(make_pair(u,step+1));
		}
		
		u=xx-f[xx];
		
		if(u>=1 && u<=n && !vis[u])
		{
			vis[u] = true;
		    q.push(make_pair(u,step+1));
		}
	}
	if(xx==y)
	cout<<step;
	else
	cout<<"-1";
	return 0;
}

P2895 [USACO08FEB]Meteor Shower S

思路

首先用f数组记录下每块地化为焦土的时间(安全的地方为-1):

  • 一开始把所有的地的时间都设置为-1
  • 读入数据x,y,time,若 f [ x ] [ y ] = = − 1 f[x][y]==-1 f[x][y]==1 f [ x ] [ y ] > t i m e f[x][y]>time f[x][y]>time则更新 f [ x ] [ y ] f[x][y] f[x][y]
  • 向上下左右四个方向,以同样的判断方法更新紧挨的四块地

然后浅用一个bfs,队列里存储x,y,time,表示在time时刻到达了 ( x , y ) (x,y) (x,y)处。每次向外扩散时仅当 f [ u ] [ v ] > t i m e + 1 f[u][v]>time+1 f[u][v]>time+1 ( u , v ) (u,v) (u,v)未被访问过。bfs直到队列空或者找到一个 f [ x ] [ y ] = = − 1 f[x][y]==-1 f[x][y]==1

实现

#include<bits/stdc++.h>
using namespace std;
#include<bits/stdc++.h>
using namespace std;
const int dx[4]={-1,1,0,0};
const int dy[4]={0,0,1,-1};
int f[500][500]; 
bool vis[500][500];
typedef struct
{
	int xx;
	int yy;
	int time;
}node;
queue<node> q;
int main()
{
	int n;
	cin>>n;
	memset(f,-1,sizeof(f)); //开始时全部初始化为-1 
	memset(vis,false,sizeof(vis));
	int time,x,y,u,v;
	for(int i=0;i<n;i++) 
	{
		scanf("%d%d%d",&x,&y,&time);
		if(f[x][y]>time || f[x][y]==-1)
		f[x][y] = time;
		for(int j=0;j<4;j++)
		{
			u = x+dx[j];
			v = y+dy[j];
			if(u<0 || v<0 || u>498 ||v>498) continue;
			if(f[u][v]>time || f[u][v]==-1)
			f[u][v] = time;
		}
    }
//	for(int i=0;i<6;i++)
//	{
//		for(int j=0;j<6;j++)
//		cout<<f[i][j]<<" ";
//		cout<<endl;
//	}
	vis[0][0]=true;
	node e1,e2;
	e1.xx = 0;
	e1.yy = 0;
	e1.time = 0;
	q.push(e1);
	while(!q.empty())
	{
		x=q.front().xx;
		y = q.front().yy;
		time = q.front().time;
		q.pop();
		if(f[x][y]==-1) break;
		for(int j=0;j<4;j++)
		{
			u = x+dx[j];
			v = y+dy[j];
			if(u<0 || v<0 || u>498 ||v>498) continue;
			
			if((f[u][v]==-1||f[u][v]>time+1) && !vis[u][v])
			{
				vis[u][v] = 1;
				e2.xx = u;
				e2.yy = v;
				e2.time = time+1;
				q.push(e2);
			}
		}
	}
	if(f[x][y]==-1)
	cout<<time;
	else
	cout<<"-1";
	return 0;
}

P1019 [NOIP2000 提高组] 单词接龙

思路

首先进行预处理, d i c [ i ] [ j ] dic[i][j] dic[i][j]存第i个和第j个串重叠部分的长度。
每个单词最多用两次可以用一个vis数组,初始化为2,dfs时减1。

实现

来自洛谷题解区 MorsLin

#include<bits/stdc++.h>
using namespace std;
int n,dic[21][21],vis[21],ans;
string words[21];
char s;
void f(string a,string b,int x,int y)
{
    int a1=a.size()-1,b1=b.size()-1; 
    for(int i=0;i<=b1;i++)  //从第一个开始枚举 
    {
        if(a[0]==b[i])  //如果a的首字母和b中间的字母相同 ,则判断它们能不能接在一起 
        {
            int pos=0,tot=0;  //pos是当前a的第几个字母,tot是a和b的重合部分长度 
            for(int j=i;j<=b1;j++)
            {
                if(a[pos]==b[j])
                {
                    tot++;
                    pos++;
                    if(j==b1&&tot!=min(a1,b1)+1) //如果枚举到了最后,并且a和b没有包含关系,说明可以这么接 
                      dic[x][y]=tot;  //记录最小重叠部分的长度 
					  				  //之所以不break,是因为后面可能还会枚举到更小的接法 
                      				  //比如 chsese 和 sesettt 显然 chsesesettt 要比chsesettt更优 
                }
                else break;
            }
        }
    }
}
void dfs(int pos,int sum)
{
    ans=max(ans,sum);  //实时更新ans 
    for(int i=1;i<=n;i++)
    {
        if(dic[i][pos]&&vis[i])
        {
            vis[i]--;
            int suml=sum+words[i].size()-dic[i][pos]; //接上新单词"龙"的长度为=旧的长度+新单词长度-重复部分长度 
            dfs(i,suml);  //接上新单词继续搜索 
            vis[i]++;
        }
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>words[i];
        vis[i]=2; //初始化vis数组,每个单词能用两次 
    }	
    cin>>s;
    for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
      	f(words[i],words[j],i,j); //预处理dic数组 
      	
    for(int i=1;i<=n;i++)
    {
        if(words[i][0]==s)  //找到开始部分 
        {
            vis[i]--;
            dfs(i,words[i].size()); //深搜 
            vis[i]++;
        }  
    }
    cout<<ans;
    
    return 0;
}

欢迎指正-

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值