分裂

A

有一个圆环,圆环有26字母按顺序循环排列。起始点为A,每次只能顺时针或逆时针移动一格。有一个字符串,问移动到所有字符需要几步。按照给出字符串顺序。
输入:一串字符
输出:最少步数

分析

枚举所有字符。设当前字符为c,从当前字符到下一个字符new有两个方向,移动步数分别为|c-new|和26-|c-new|,取最小值。

代码

#include<iostream>
#include<algorithm>
#include<string>

using namespace std;
string s;
int main(){
	cin>>s;
	char c='a';
	int x=0,y;
	string::iterator ite=s.begin();
	while(*ite!='\0'){
		y=abs(c-*ite);
		x+=min(y,26-y);
		c=*ite;
		ite++;
	}
	cout<<x<<endl;
}

B

一个煎包店有两个消费方式。
1、一次性买两个煎包
2、买一个,送一个消费券,凭券第二天可免费得到一个煎包。
愚蠢的咕咕咚在煎包店附近参加考试,他每天要来吃煎包。抠门的他在这带不了几天,不希望走的时候手里有消费券。
输入:n咕咕咚开始天数(这个数范围挺大的,具体多少忘了。。。),然后那个数表示第 i 天吃a[ i ]个煎包。(a[ i ]<e5)
输出:如何选择保证考试结束没有消费券剩余和浪费,如果可行输出yes否则no

分析

可以理解为一个DFS问题。
第一步转化为坐标问题。DFS递归时需要知道天数t和煎包数a[i],但是天数和煎包数索引i相等,只需要选择一个就行。一个坐标t,显然不足以解决问题,另一个坐标在下一步讨论。
第二步坐标中怎么行走。如何行走,在本题中相当于选哪种购买方式。
三种情况:1、买两个煎包 2、买一个,留给第二天一个 3、一个不买
知道天数t,就知道需要几个煎包,但前一天的购买方式(例如方式 2 消费)会影响这一天。因此第二个坐标出来了,m前一天方式 2 购买量(前一天为今天购买的量)。定义坐标( t , m)

解题1:
最简单方法是,枚举 i 和 j (两种方式的个数),不断DFS递归。
其中,i< (a[i]-m)/2 , j<a[i]-m 。 (a[i]-m,第二天需要再次购买的煎包数)

代码1

void DFS(int t,int m){
	if(t==n&&m==0){
		b=1;
		return;
	}
	if(t>=n||m>a[t]||b){
		q=0;
		return;
	}
	int u=(a[i]-m)/2,v=a[i]-m;
	for(int i=0;i<u;i++){
		for(int j=0;j<v;j++){
		//////////////////////省略
		//////////////////////
		DFS(t+1,j);
		}
	}
}

解题2:
测试数据很大,方法一根本不可能通过。
思考一下,我们知道这一天需要的煎包数,又知道两种购买方式,列出公式 a[ t ]= 2i + j。很明显 i 和 j成线性关系。也就是说知道 i 就能算出 j,不需要两个都枚举。
继续优化。
注意DFS参数。t不用考虑,m是为第二天购买的数量。也就是说,我们只需要知道 j 就可以了。
如果当天需要的煎包(减去前一天的 a[ t ]- m )数为奇数,j 最小值为1 ,否则为 0。如果减少一个方式1,需要增加两个方式2.
下面直接看代码

代码2

bool b;
int a[100000];
int n;
void DFS(int t,int m){
	if(t==n&&m==0){
		b=1;
		return;
	}
	if(t>=n||m>a[t]||b){
		q=0;
		return;
	}
	int j=0,i=a[t]-m;
	if(i%2){
		j=1;	
	}
	while(j<=i&&!b){
		
		DFS(t+1,j);
		j+=2;
	}
}

由于数据很大,没必要枚举所有情况,如果已经找到可行路径就可以结束。

分裂

有一个宇宙射线在二维平面不断分裂,每次会想两个方向45度角分裂出两个新射线(比如正在向上走,会分裂两个方向左上角和右上角),且威力不变,初始方向向上。
输入:下面给出一个数n,表示可分裂n-1次。接着n个数,第 i 次行走a[ i ]格。每次最多走5格
输出:求射线走了几个格`

分析

依然用DFS。
这道题可以暴力破解,对每一个走过的点做标记,记录所有标记的点。唯一的难点是,方向。
我们可以定义一个组合(x,y),x上下 y左右,都取1、0、-1,表示在坐标上的移动方向(x=0,y=0表示没有方向,所以不存在)。
方向
情况1,x=0时。y=1或-1表示上下,那么下一个方向是,左上(下)和右上(下)。即(-1,y)和( 1,y)。
情况2,x!=0且y!=0时。当前方向为左(右)上(下)四种情况,不过它下一个方向必定是正方向(上、下、左、右),(0,y)和(x,0)
情况3,x!=0,y=0时。当前方向为左或右,下一个方向(x,1)和(x,-1)

代码1

void DFS(int a,int b,int x,int y,int k){
	for(int i=0;i<step[k];i++){
		a+=x;b+=y;
		if(m[a][b]){
			m[a][b]=0;
				sum++;
	} 
	if(k<n){
	if(x){
		if(y){
				DFS(a,b,x,0,k+1);
				DFS(a,b,0,y,k+1);
		}
		else{
				DFS(a,b,x,1,k+1);
				DFS(a,b,x,-1,k+1);
		}
	}
	else{
			DFS(a,b,1,y,k+1);
			DFS(a,b,-1,y,k+1);

	}
	}
}

上面代码处理小数据没有问题,但是对于大数据需要5s以上。

优化

优化一:初始方向是向上,那么从向两边分裂开始,整个图就是以初始方向为轴两边对称(假设对称线为x=X)。第一次分裂只分裂两个点,判定一个点的时候,如果该点(a为该点横坐标)在对称线两边(X!=a),记录两个点(横坐标为a和2X-a,纵坐标相等b)。如果该点在对称线(X=a),只记录一个点(a,b)。
优化二:当分裂次数多时(n>20)分裂线有2的n-1次方,射线是在一个很像的图里不停地重复。如果遇到坐标一样、分裂步数一样、方向一样,那么只需要继续一条即可,另一台提前结束。这就需要定义一个更大的数组,只需要标记用bool节省空间。
其实这个优化是拿内存换时间。

扫描二维码关注公众号,回复: 10314246 查看本文章

代码2

void DFS(int a,int b,int x,int y,int k){
	for(int i=0;i<step[k];i++){
		a+=x;b+=y;
		if(m[a][b]){
			m[a][b]=0;
			m[300-a][b]=0;
			if(a==150) 
				sum++;
			else
				sum+=2;
		}	
	} 
	if(k<n){
	if(x){
		if(y){
			if(!mm[a][b][k+1][kk[x+1][1]]){
				mm[a][b][k+1][kk[x+1][1]]=1;
				DFS(a,b,x,0,k+1);
			}
			if(!mm[a][b][k+1][kk[1][y+1]]){
				mm[a][b][k+1][kk[1][y+1]]=1;
				DFS(a,b,0,y,k+1);
			}
		}
		else{
			if(!mm[a][b][k+1][kk[x+1][2]]){
				mm[a][b][k+1][kk[x+1][2]]=1;
				DFS(a,b,x,1,k+1);
			}
			if(!mm[a][b][k+1][kk[x+1][0]]){
				mm[a][b][k+1][kk[x+1][0]]=1;
				DFS(a,b,x,-1,k+1);
			}
		}
	}
	else{
		if(!mm[a][b][k+1][kk[2][y+1]]){
			mm[a][b][k+1][kk[2][y+1]]=1;
			DFS(a,b,1,y,k+1);
		}
		if(!mm[a][b][k+1][kk[0][y+1]]){
			mm[a][b][k+1][kk[0][y+1]]=1;
			DFS(a,b,-1,y,k+1);
		}
	}
	}
}
void df(){
	int b=150;
	for(int i=0;i<step[1];i++,b++){
		m[150][b]=0;
	}
	sum+=step[1];
	DFS(150,b,-1,1,2);
}

kk[][]定义的是方向,其中kk[ 0 ][ 0 ]无效。

发布了7 篇原创文章 · 获赞 2 · 访问量 202

猜你喜欢

转载自blog.csdn.net/Schrodingerofcat/article/details/104865999