第十一届蓝桥杯大赛个人赛决赛(软件类)C/C++大学B组题解

因能力有限,题解部分参照前辈想法,并加以博主思考。
如有不足,欢迎指正~!

后半部分题解较为潦草简单,详细思路后补~
其中试题G样例通过,实际思路上有些错误;
试题J规模、时间过不了;
样例F、I尚未解决~


试题A:美丽的2

题目:

【问题描述】
小蓝特别喜欢2,今年是公元2020年,他特别高兴。
他很好奇,在公元1年到公元2020年(包含)中,有多少个年份的数位中包含数字2?

【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案:
563

题解:
本题是道简单的签到题,题目明确提出:
在 [2,2020] 中,有多少个数包含数字2

遍历每个数中的每位数,倘若是2,则计数

加注释代码如下……

#include<iostream>
#include<cstdio>

using namespace std;

int cnt=0; // 计数 

bool judge(int); // 判断 

int main(){
	
	int i;
	
	for(i=1;i<=2020;++i){
		if(judge(i)) ++cnt;
	} // 遍历 
	
	printf("%d",cnt);
	
	return 0;
}

bool judge(int n){
	
	while(n){
		if(n%10==2) return true;
		n/=10;
	} // 遍历并判断各数位上的数是否等于2 
	
	return false;
}

试题B:扩散

题目:

【问题描述】

小蓝在一张无限大的特殊画布上作画。
这张画布可以看成一个方格图,每个格子可以用一个二维的整数坐标表示。
小蓝在画布上首先点了一下几个点:(0, 0), (2020, 11), (11, 14), (2000, 2000)。只有这几个格子上有黑色,其它位置都是白色的。
每过一分钟,黑色就会扩散一点。具体的,如果一个格子里面是黑色,它就会扩散到上、下、左、右四个相邻的格子中,使得这四个格子也变成黑色(如果原来就是黑色,则还是黑色)。
请问,经过 2020 分钟后,画布上有多少个格子是黑色的。

【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案:
20312088

题解:
本题有两种解法
1、广搜BFS
2、数学分析(姑且叫这个吧

Method 1(多源BFS):
题目要求是有四个点,分别向四周扩散2020次,计算被污染的格子数量

很明显能够想到使用BFS,这里有三个注意点
1、画布无限大,0不做边界,因此我们需要在给出点的基础上做偏移
2、本题给出四个点,与其说多源BFS,实际就是将四点都压入广搜队列中依次遍历
3、除却坐标 ( x , y ) (x,y) (x,y) 外,还有时间 t t t t < = 2020 t<=2020 t<=2020 )。因此博主在解决本题时,额外建立结构 pt (x,y,t)

运行时间较长,要好几秒

加注释代码如下……

// Method 1
#include<iostream>
#include<cstdio>
#include<queue>

using namespace std;

const int MAXN=1e4;
const int T=4000; // 偏移量t

struct pt{
	int x,y,t; // 坐标(x,y)以及遍历的时间点t 
	pt(int tx,int ty,int tt):x(tx),y(ty),t(tt){ } // 含参构造函数 
}; // 画布点的结构 

int turn[4][2]={-1,0,1,0,0,1,0,-1};
int ans=0;

bool table[MAXN+10][MAXN+10]; // 二维画布 
int p[4][2]={{0,0},{2020,11},{11,14},{2000,2000}}; // 初始四点 

int main(){
	int i;
	int x,y,t;
	int tx,ty;
	
	// pt结构的队列,用于bfs
	queue<pt> q;
	
	// 将4个点置入队列,并在画布标记 
	for(i=0;i<4;++i){
		x=p[i][0]; y=p[i][1];
		q.push(pt(x+T,y+T,0)); // 要加偏移量
		table[x+T][y+T]=1;
	}
	
	// BFS
	while(!q.empty()){
		++ans;
		
		// 将结构队列首元素的坐标和时间分别赋值给x,y,t 
		x=q.front().x;
		y=q.front().y;
		t=q.front().t;
		q.pop();
		
		// 边界状态(时间点t>=2020),即经过2020分钟后 
		if(t>=2020) continue;
		
		for(i=0;i<4;++i){
			
			tx=x+turn[i][0];
			ty=y+turn[i][1];
			
			if(!table[tx][ty]){
				table[tx][ty]=1;
				q.push(pt(tx,ty,t+1));
			}
			
		}// 遍历上下左右,将未被标记的格子入队 
		
	}
	
	printf("%d",ans);
	
	return 0;
} 

Method 2(数学分析):
该方法运行时间比多源BFS更少些,但是不知道为什么测试数据的时候会显示运行错误

分析题目发现,扩散结果如下图所示一般:
扩散形状图
据此,博主想到了刚学高级语言程序设计时的一道有关输出的简单样题:

输出由 * 组成的图形:
*
* *
* * *
* *
*

明显发现,本题扩散结果类似上述形状

从四个点单独考虑、分别遍历,最后再计算被遍历次数大于0的格子个数即可

加注释代码如下……

// Method 2
#include<iostream>
#include<cstdio>

using namespace std;

const int MAXN=1e4;
const int T=4000; // 转移量 

int table[MAXN+10][MAXN+10];

// 各点初始坐标 
int p[4][2]={ {0, 0}, {2020, 11}, {11, 14}, {2000, 2000} }; 

// 单格(x,y)扩散时,画布的变化 
void change(int,int); 

// 获取画布黑色格子个数(table[i][j]>0) 
int getAns();

int main(){
	int i,x,y;
	
	for(i=0;i<4;++i){
		x=p[i][0]+T; y=p[i][1]+T;
		change(x,y);
	}// 从四个点分别扩散 
	
	printf("%d",getAns());
	
	return 0;
}

void change(int x,int y){
	int i,j;
	
	for(i=0;i<=2020;++i){
		for(j=0;j<=2020-i;++j){
			++table[x+i][y-j];
			++table[x+i][y+j];
			++table[x-i][y-j];
			++table[x-i][y+j];
		}
	}// 寻找每行的被标记始末位置,进行变化 
	
}

int getAns(){
	int i,j;
	int ans=0;
	
	for(i=0;i<=MAXN;++i){
		for(j=0;j<=MAXN;++j){
			if(table[i][j])++ans;
		}
	}//遍历画布,获取黑色格子数量 
	
	return ans;
}

试题C:阶乘约数

题目

【问题描述】
定义阶乘 n! = 1 × 2 × 3 × · · · × n。
请问 100! (100 的阶乘)有多少个正约数。

【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案:
39001250856960000

题解:
本题求解数 n! 的正约数个数
解决本题时,需使用素数筛(如埃式筛法等)以及约数个数定理( f ( n ) = ∏ i = 1 k ( a i + 1 ) f(n) =\displaystyle\prod_{i=1}^k(a_{i}+1) f(n)=i=1k(ai+1) a i a_{i} ai 是第i个质因数的个数)

本题是求阶乘的约数个数,阶乘 n ! = 1 × 2 × 3 × . . . × n n!=1×2×3×...×n n!=1×2×3×...×n
可以利用这个定义,通过循环分别获取 [ 1 , n ] [1,n] [1,n] 中的每个数所包含的质因数及其个数,并将每个质因数个数累加起来,最后根据约数个数定理求积即可

加注释代码如下……

#include<iostream>
#include<cstdio>
#include<cmath> 
#include<map>

using namespace std;

typedef long long LL;
typedef map<int,int>::iterator iter;

map<int,int> prime; // 素数个数 

// 埃式筛 
void findPrime(int);

// 获取正约数个数 
LL getAns();

int main(){
	int i;
	
	for(i=2;i<=100;++i){
		findPrime(i);
	} // 遍历阶乘所有乘数 
	
	printf("%lld",getAns()); // 输出 
	
	return 0;
} 

void findPrime(int n){
	int cnt; // 当前素数个数 
	
	
	// 素数筛↓ 
	for(int i=2;i<=sqrt(n)&&n>1;++i){
		cnt=0;
		
		if(n%i==0){
			while(n%i==0){
				n/=i;
				++cnt;
			}
		}
		
		prime[i]=prime[i]+cnt;
	}
	
	if(n>1){
		prime[n]=prime[n]+1;
	} 
	
}

LL getAns(){
	
	LL ans=1;
	
	for(iter it=prime.begin();it!=prime.end();++it){
		ans*= (it->second+1);
	}// 约数公式 ans = sum( numPrime[i] + 1 )
	 
	return ans;
}

试题D:本质上升序列

题目:

【问题描述】
小蓝特别喜欢单调递增的事物。
在一个字符串中,如果取出若干个字符,将这些字符按照在字符串中的顺序排列后是单调递增的,则成为这个字符串中的一个单调递增子序列。
例如,在字符串 lanqiao 中,如果取出字符 n 和 q,则 nq 组成一个单调递增子序列。类似的单调递增子序列还有 lnq、i、ano 等等。
小蓝发现,有些子序列虽然位置不同,但是字符序列是一样的,例如取第二个字符和最后一个字符可以取到 ao,取最后两个字符也可以取到 ao。小蓝认为他们并没有本质不同。
对于一个字符串,小蓝想知道,本质不同的递增子序列有多少个?
例如,对于字符串 lanqiao,本质不同的递增子序列有 21 个。它们分别是 l、a、n、q、i、o、ln、an、lq、aq、nq、ai、lo、ao、no、io、lnq、anq、lno、ano、aio。
请问对于以下字符串(共 200 个小写英文字母,分四行显示):(如果你把以下文字复制到文本文件中,请务必检查复制的内容是否与文档中的一致。在试题目录下有一个文件 inc.txt,内容与下面的文本相同)

tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhf
iadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqij
gihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmad
vrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhfwl

本质不同的递增子序列有多少个?

【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案:
3616159

题解:
1、dp去重(代码如下)
2、前缀和(新思路,尚未实现)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm> 

using namespace std;

char str[205];
int ans=0;
int dp[205];

int main(){
	
	scanf("%s",str);
	
	int len=strlen(str);
	int i,j;
	
	fill(dp,dp+200,1);
	
	for(i=0;i<len;++i){
		for(j=0;j<i;++j){
			if(str[j]<str[i]) dp[i]+=dp[j];
			if(str[j]==str[i])dp[i]-=dp[j];
		}
		ans+=dp[i];
	}
	
	printf("%d",ans);
	
	return 0;
}

试题E:玩具蛇

题目:

【问题描述】
小蓝有一条玩具蛇,一共有 16 节,上面标着数字 1 至 16。每一节都是一个正方形的形状。相邻的两节可以成直线或者成 90 度角。
小蓝还有一个 4 × 4 的方格盒子,用于存放玩具蛇,盒子的方格上依次标着字母 A 到 P 共 16 个字母。
小蓝可以折叠自己的玩具蛇放到盒子里面。他发现,有很多种方案可以将玩具蛇放进去。
下图给出了两种方案:
玩具蛇方案样例
请帮小蓝计算一下,总共有多少种不同的方案。如果两个方案中,存在玩具蛇的某一节放在了盒子的不同格子里,则认为是不同的方案。

【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案:
552

题解:
DFS(常规)

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

int turn[4][2]={-1,0,1,0,0,1,0,-1}; // 用于点位移 
bool table[5][5]; // 标记数组 
int ans=0;

void dfs(int,int,int); // dfs(x,y,index)

int main(){
	
	int i,j;
	
	for(i=1;i<=4;++i){
		for(j=1;j<=4;++j){
			memset(table,0,sizeof(table));
			table[i][j]=1;
			dfs(i,j,1);
		}
	} // 改变起点位置,多源dfs 
	
	printf("%d",ans);
	
	return 0;
}

void dfs(int x,int y,int index){
	if(index>=16){
		++ans; return;
	} // dfs边界 
	
	int i;
	int tx,ty;
	
	for(i=0;i<4;++i){
		tx=x+turn[i][0];
		ty=y+turn[i][1];
		
		// 不符合条件 continue 
		if(tx<1||ty<1||tx>4||ty>4||table[tx][ty]) continue;
		
		table[x][y]=1; // 格子被使用 
		dfs(tx,ty,index+1); // 继续深搜 
		table[x][y]=0; // 恢复标志状态 
	} // 遍历四周,符合条件则继续深搜 
}

试题F:皮亚诺曲线距离

题目:

【问题描述】
皮亚诺曲线是一条平面内的曲线。
下图给出了皮亚诺曲线的 1 阶情形,它是从左下角出发,经过一个 3 × 3 的方格中的每一个格子,最终到达右上角的一条曲线。
在这里插入图片描述
下图给出了皮亚诺曲线的 2 阶情形,它是经过一个 3 2 × 3 2 3^2 × 3^2 32×32 的方格中的每一个格子的一条曲线。它是将 1 阶曲线的每个方格由 1 阶曲线替换而成。
在这里插入图片描述
下图给出了皮亚诺曲线的 3 阶情形,它是经过一个 3 3 × 3 3 3^3 × 3^3 33×33的方格中的每一个格子的一条曲线。它是将 2 阶曲线的每个方格由 1 阶曲线替换而成。
在这里插入图片描述
皮亚诺曲线总是从左下角开始出发,最终到达右上角。
我们将这些格子放到坐标系中,对于 k k k 阶皮亚诺曲线,左下角的坐标是(0, 0),右上角坐标是 ( 3 k 3^k 3k − 1, 3 k 3^k 3k − 1),右下角坐标是 ( 3 k 3^k 3k − 1, 0),左上角坐标是(0, 3 k 3^k 3k − 1)。
给定 k k k 阶皮亚诺曲线上的两个点的坐标,请问这两个点之间,如果沿着皮亚诺曲线走,距离是到少?

【输入格式】
输入的第一行包含一个正整数 k k k,皮亚诺曲线的阶数。
第二行包含两个整数 x 1 x_1 x1, y 1 y_1 y1,表示第一个点的坐标。
第三行包含两个整数 x 2 x_2 x2, y 2 y_2 y2,表示第二个点的坐标。

【输出格式】
输出一个整数,表示给定的两个点之间的距离。

【样例输入】
1
0 0
2 2
【样例输出】
8
【样例输入】
2
0 2
0 3
【样例输出】
13
【评测用例规模与约定】
对于 30% 的评测用例, 0 ≤ k ≤ 10 0 ≤ k ≤ 10 0k10
对于 50% 的评测用例, 0 ≤ k ≤ 20 0 ≤ k ≤ 20 0k20
对于所有评测用例, 0 ≤ k ≤ 100 0 ≤ k ≤ 100 0k100, 0 ≤ x 1 , y 1 , x 2 , y 2 < 3 k 0 ≤ x_1, y_1, x_2, y_2 < 3k 0x1,y1,x2,y2<3k, x 1 , y 1 , x 2 , y 2 ≤ 1 0 18 x_1, y_1, x_2, y_2 ≤ 10^{18} x1,y1,x2,y21018。数据保证答案不超过 1 0 18 10^{18} 1018

试题G:游园安排

题目:

【问题描述】
L 星球游乐园非常有趣,吸引着各个星球的游客前来游玩。小蓝是 L 星球游乐园的管理员。
为了更好的管理游乐园,游乐园要求所有的游客提前预约,小蓝能看到系统上所有预约游客的名字。个游客的名字由一个大写英文字母开始,后面跟0 个或多个小写英文字母。游客可能重名。
小蓝特别喜欢递增的事物。今天,他决定在所有预约的游客中,选择一部分游客在上午游玩,其他的游客都在下午游玩,在上午游玩的游客要求按照预约的顺序排列后,名字是单调递增的,即排在前面的名字严格小于排在后面的名字。
一个名字 A 小于另一个名字 B 是指:存在一个整数 i,使得 A 的前 i 个字母与 B 的前 i 个字母相同,且 A 的第 i+ 1 个字母小于 B 的第 i+ 1 个字母。(如果 A 不存在第 i + 1 个字母且 B 存在第 i + 1 个字母,也视为 A 的第 i + 1 个字母小于 B 的第 i + 1 个字母)
作为小蓝的助手,你要按照小蓝的想法安排游客,同时你又希望上午有尽量多的游客游玩,请告诉小蓝让哪些游客上午游玩。如果方案有多种,请输出上午游玩的第一个游客名字最小的方案。如果此时还有多种方案,请输出第一个游客名字最小的前提下第二个游客名字最小的方案。如果仍然有多种,依此类推选择第三个、第四个……游客名字最小的方案。

【输入格式】
输入包含一个字符串,按预约的顺序给出所有游客的名字,相邻的游客名字之间没有字符分隔。

【输出格式】
按预约顺序输出上午游玩的游客名单,中间不加任何分隔字符。

【样例输入】
WoAiLanQiaoBei

【样例输出】
AiLanQiao

【评测用例规模与约定】
对于 20% 的评测数据,输入的总长度不超过 20 个字母。
对于 50% 的评测数据,输入的总长度不超过 300 个字母。
对于 70% 的评测数据,输入的总长度不超过 10000 个字母。
对于所有评测数据,每个名字的长度不超过 10 个字母,输入的总长度不超过1000000 个字母。

#include<iostream>
#include<cstdio>
#include<string>
#include<vector>
#include<algorithm>

using namespace std;

struct node{
	string str; // 排好队的字符串 
	string last; // 最后一个游客名字 
	int lenth; // 队伍长度 
	node(string str,string last,int lenth){
		this->str=str;
		this->last=last;
		this->lenth=lenth;
	}//含参构造 
};

typedef vector<node>::iterator iter;

bool cmp(node a,node b){
	if(a.lenth>b.lenth){
		return true;
	}
	return a.str<b.str;
}

vector<node> vec;

int main(){
	
	char ch;
	string s;
	do{
		ch=getchar(); // 开始输入 
		if(ch<'a'){
			if(s!=""){
				for(iter it=vec.begin();it!=vec.end();++it){
					if(it->last<s){
						it->str+=s;
						it->last=s;
						++it->lenth;
					}
				} // 遍历 
				vec.push_back(node(s,s,1));
			}
			s=ch;
		} // 输入新名字 
		else{
			s+=ch;
		}
	}while(ch!='\n');
	
	sort(vec.begin(),vec.end(),cmp);
	
	cout<<(vec.begin())->str;
	
	return 0;
} 

试题H:答疑

题目:

【问题描述】
有 n 位同学同时找老师答疑。每位同学都预先估计了自己答疑的时间。
老师可以安排答疑的顺序,同学们要依次进入老师办公室答疑。
一位同学答疑的过程如下:

  1. 首先进入办公室,编号为 i 的同学需要 s i s_i si 毫秒的时间。
  2. 然后同学问问题老师解答,编号为 i 的同学需要 a i a_i ai 毫秒的时间。
  3. 答疑完成后,同学很高兴,会在课程群里面发一条消息,需要的时间可以忽略。
  4. 最后同学收拾东西离开办公室,需要 e i e_i ei 毫秒的时间。一般需要 10 秒、20 秒或 30 秒,即 e i e_i ei 取值为 10000,20000 或 30000。


一位同学离开办公室后,紧接着下一位同学就可以进入办公室了。
答疑从 0 时刻开始。老师想合理的安排答疑的顺序,使得同学们在课程群里面发消息的时刻之和最小。

【输入格式】
输入第一行包含一个整数 n,表示同学的数量。
接下来 n 行,描述每位同学的时间。其中第 i 行包含三个整数 s i s_i si, a i a_i ai, e i e_i ei,意义如上所述。

【输出格式】
输出一个整数,表示同学们在课程群里面发消息的时刻之和最小是多少。

【样例输入】
3
10000 10000 10000
20000 50000 20000
30000 20000 30000

【样例输出】
280000

【样例说明】
按照 1, 3, 2 的顺序答疑,发消息的时间分别是 20000, 80000, 180000。

【评测用例规模与约定】
对于 30% 的评测用例,1 ≤ n ≤ 20。
对于 60% 的评测用例,1 ≤ n ≤ 200。
对于所有评测用例,1 ≤ n ≤ 1000,1 ≤ s i s_i si ≤ 60000,1 ≤ a i a_i ai ≤ 1000000, e i e_i ei ∈ {10000, 20000, 30000},即 e i e_i ei 一定是 10000、20000、30000 之一。

题解:
开始想的复杂了,感觉要用dp+前缀和,后来看了下其他博主的思路,发现该题正解是多条件的整体排序(也就是用个结构,写个cmp函数或重载小于号,再用sort快排

#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

typedef long long LL;

const int MAXN=1000;

struct student{
	int s,e; // s(start + act), e(end)
	int num; // num(s+a+e)
	student(){
		s=0,e=0,num=0;
	} // 默认构造函数 
	student(int s,int a,int e){
		this->s=s+a;
		this->e=e;
		this->num=s+a+e;
	} // 含参构造函数
	bool operator<(student tmp){
		if(this->num==tmp.num) return this->s<tmp.s;
		return this->num<tmp.num;
	} // 重载 <  
};

int n; // 同学的数量 
student vec[MAXN+10];  

void inif(); // 初始化 

LL ans();

int main(){
	inif();
	
	printf("%lld",ans());
	
	return 0;
}
// 初始化
void inif(){
	int i,s,a,e;
	
	scanf("%d",&n);
	
	for(i=0;i<n;++i){
		scanf("%d%d%d",&s,&a,&e); 
		
		vec[i]=student(s,a,e);
	}
	
}

LL ans(){
	int i;
	LL t=0,tmp=0;
	
	sort(vec,vec+n); // 排序 
	
	for(i=0;i<n;++i){
		tmp+=vec[i].s;
		t+=tmp;
		tmp+=vec[i].e;
	} //计算 t 
	
	return t;
}

试题I:出租车

问题:

【问题描述】
小蓝在 L 市开出租车。
L 市的规划很规整,所有的路都是正东西向或者正南北向的,道路都可以看成直线段。东西向的道路互相平行,南北向的道路互相平行,任何一条东西向道路垂直于任何一条南北向道路。
从北到南一共有 n 条东西向道路,依次标号为 H1, H2, · · · , Hn。从西到东一共有 m 条南北向的道路,依次标号为 S 1, S 2, · · · , S m。
每条道路都有足够长,每一条东西向道路和每一条南北向道路都相交,Hi 与 S j 的交叉路口记为 (i, j)。
从 H1 和 S 1 的交叉路口 (1, 1) 开始,向南遇到的路口与 (1, 1) 的距离分别是 h1, h2, · · · , hn−1,向东遇到路口与 (1, 1) 的距离分别是 w1, w2, · · · , wm−1。
道路的每个路口都有一个红绿灯。
时刻 0 的时候,南北向绿灯亮,东西向红灯亮,南北向的绿灯会持续一段时间(每个路口不同),然后南北向变成红灯,东西向变成绿灯,持续一段时间后,再变成南北向绿灯,东西向红灯。
已知路口 (i, j) 的南北向绿灯每次持续的时间为 gi j,东西向的绿灯每次持续的时间为 ri j,红绿灯的变换时间忽略。
当一辆车走到路口时,如果是绿灯,可以直行、左转或右转。如果是红灯,可以右转,不能直行或左转。如果到路口的时候刚好由红灯变为绿灯,则视为看到绿灯,如果刚好由绿灯变为红灯,则视为看到红灯。
每段道路都是双向道路,道路中间有隔离栏杆,在道路中间不能掉头,只能在红绿灯路口掉头。掉头时不管是红灯还是绿灯都可以直接掉头。掉头的时间可以忽略。
小蓝时刻 0 从家出发。今天,他接到了 q 个预约的订单,他打算按照订单的顺序依次完成这些订单,就回家休息。中途小蓝不准备再拉其他乘客。
小蓝的家在两个路口的中点,小蓝喜欢用 x1, y1, x2, y2 来表示自己家的位置,即路口 (x1, y1) 到路口 (x2, y2) 之间的道路中点的右侧,保证两个路口相邻(中间没有其他路口)。请注意当两个路口交换位置时,表达的是路的不同两边,路中间有栏杆,因此这两个位置实际要走比较远才能到达。
小蓝的订单也是从某两个路口间的中点出发,到某两个路口间的中点结束。小蓝必须按照给定的顺序处理订单,而且一个时刻只能处理一个订单,不能图省时间而同时接两位乘客,也不能插队完成后面的订单。
小蓝只对 L 市比较熟,因此他只会在给定的 n 条东西向道路和 m 条南北向道路上行驶,而且不会驶出 H1, Hn, S 1, S m 这几条道路所确定的矩形区域(可以到边界)。
小蓝行车速度一直为 1,乘客上下车的时间忽略不计。
请问,小蓝最早什么时候能完成所有订单回到家。

【输入格式】
输入第一行包含两个整数 n, m,表示东西向道路的数量和南北向道路的数量。
第二行包含 n − 1 个整数 h1, h2, · · · , hn−1。
第三行包含 m − 1 个整数 w1, w2, · · · , wm−1。
接下来 n 行,每行 m 个整数,描述每个路口南北向绿灯的时间,其中的第i 行第 j 列表示 gi j。
接下来 n 行,每行 m 个整数,描述每个路口东西向绿灯的时间,其中的第i 行第 j 列表示 ri j。
接下来一行包含四个整数 x1, y1, x2, y2,表示小蓝家的位置在路口 (x1, y1)到路口 (x2, y2) 之间的道路中点的右侧。
接下来一行包含一个整数 q,表示订单数量。
接下来 q 行,每行描述一个订单,其中第 i 行包含八个整数 xi1, yi1, xi2, yi2, xi3, yi3, i4, yi4,表示第 i 个订单的起点为路口 (xi1, yi1) 到路口 (xi2, yi2) 之间的道路中点的右侧,第 i 个订单的终点为路口 (xi3, yi3) 到路口 (xi4, yi4) 之间的道路中点的右侧。

【输出格式】
输出一个实数,表示小蓝完成所有订单最后回到家的最早时刻。四舍五入保留一位小数。

【样例输入】
2 3
200
100 400
10 20 10
20 40 30
20 20 20
20 20 20
2 1 1 1
1
2 2 1 2 1 2 1 3

【样例输出】
1620.0

【样例说明】
小蓝有一个订单,他的行车路线如下图所示。其中 H 表示他家的位置,S表示订单的起点,T 表示订单的终点。小明在最后回家时要在直行的红绿灯路口等绿灯,等待时间为 20。
在这里插入图片描述
【评测用例规模与约定】
对于 20% 的评测用例,1 ≤ n, m ≤ 5,1 ≤ q ≤ 10。
对于 50% 的评测用例,1 ≤ n, m ≤ 30,1 ≤ q ≤ 30。
对于所有评测用例,1 ≤ n, m ≤ 100,1 ≤ q ≤ 30,1 ≤ h1 < h2 < · · · < hn−1 ≤100000,1 ≤ w1 < w2 < · · · < wm−1 ≤ 100000,1 ≤ gi j ≤ 1000,1 ≤ ri j ≤ 1000,给定的路口一定合法。

试题J:质数行者

问题:

【问题描述】
小蓝在玩一个叫质数行者的游戏。
游戏在一个 n × m × w 的立体方格图上进行,从北到南依次标号为第 1 行到第 n 行,从西到东依次标号为第 1 列到第 m 列,从下到上依次标号为第 1 层到第 w 层。
小蓝要控制自己的角色从第 1 行第 1 列第 1 层移动到第 n 行第 m 列第 w层。每一步,他可以向东走质数格、向南走质数格或者向上走质数格。每走到一个位置,小蓝的角色要稍作停留。
在游戏中有两个陷阱,分别为第 r 1 r_1 r1 行第 c 1 c_1 c1 列第 h 1 h_1 h1 层和第 r 2 r_2 r2 行第 c 2 c_2 c2 列第 h 2 h_2 h2 层。这两个陷阱的位置可以跨过,但不能停留。也就是说,小蓝不能控制角色某一步正好走到陷阱上,但是某一步中间跨过了陷阱是允许的。
小蓝最近比较清闲,因此他想用不同的走法来完成这个游戏。所谓两个走法不同,是指小蓝稍作停留的位置集合不同。
请帮小蓝计算一下,他总共有多少种不同的走法。
提示:请注意内存限制,如果你的程序运行时超过内存限制将不得分。

【输入格式】
输入第一行包含两个整数 n, m, w,表示方格图的大小。
第二行包含 6 个整数, r 1 r_1 r1, c 1 c_1 c1, h 1 h_1 h1, r 2 r_2 r2, c 2 c_2 c2, h 2 h_2 h2,表示陷阱的位置。

【输出格式】
输出一行,包含一个整数,表示走法的数量。答案可能非常大,请输出答案除以 1000000007 的余数。

【样例输入】
5 6 1
3 4 1 1 2 1

【样例输出】
11

【样例说明】
用 (r, c, h) 表示第 r 行第 c 列第 h 层,可能的走法有以下几种:

  1. (1, 1, 1) − (1, 3, 1) − (1, 6, 1) − (3, 6, 1) − (5, 6, 1)。
  2. (1, 1, 1) − (1, 3, 1) − (3, 3, 1) − (3, 6, 1) − (5, 6, 1)。
  3. (1, 1, 1) − (1, 3, 1) − (3, 3, 1) − (5, 3, 1) − (5, 6, 1)。
  4. (1, 1, 1) − (3, 1, 1) − (3, 3, 1) − (3, 6, 1) − (5, 6, 1)。
  5. (1, 1, 1) − (3, 1, 1) − (3, 3, 1) − (5, 3, 1) − (5, 6, 1)。
  6. (1, 1, 1) − (3, 1, 1) − (5, 1, 1) − (5, 3, 1) − (5, 6, 1)。
  7. (1, 1, 1) − (3, 1, 1) − (5, 1, 1) − (5, 4, 1) − (5, 6, 1)。
  8. (1, 1, 1) − (1, 4, 1) − (1, 6, 1) − (3, 6, 1) − (5, 6, 1)。
  9. (1, 1, 1) − (1, 6, 1) − (3, 6, 1) − (5, 6, 1)。
  10. (1, 1, 1) − (3, 1, 1) − (3, 6, 1) − (5, 6, 1)。
  11. (1, 1, 1) − (3, 1, 1) − (5, 1, 1) − (5, 6, 1)。


【评测用例规模与约定】
对于 30% 的评测用例 1 ≤ n, m,w ≤ 50。
对于 60% 的评测用例 1 ≤ n, m,w ≤ 300。
对于所有评测用例,1 ≤ n, m, w ≤ 1000,1 ≤ r 1 r_1 r1, r 2 r_2 r2 ≤ n, 1 ≤ c 1 c_1 c1, c 2 c_2 c2 ≤ m, 1 ≤ h 1 h_1 h1, h 1 h_1 h1 ≤ w,陷阱不在起点或终点,两个陷阱不同。

题解:
水平有些,只能按照记忆化搜索写,已经想不到还怎么怎么减规模了,请大佬们多指点指点本蒟蒻。

#include<iostream>
#include<cstdio>

using namespace std;

const int MAXN=300+10; // 规模限制 
const int MOD=1e9+7; // 取模值 

int prime[MAXN],np=0; // 存储0~1000的素数,np记录范围内素数个数 
void findPrime(); // 素数筛

int n,m,w; // 1 ≤ n, m, w ≤ 1000
int r[2],c[2],h[2]; // 陷阱 
int dp[MAXN][MAXN][MAXN]; // 记忆化搜索三维数组 

void inif(); // 初始化 
bool judge(int,int,int); // 判断有无遇到陷阱 
void getAns(); // 解题 

int main(){
	
	inif();
	getAns();
	
	printf("%d",dp[n][m][w]);
	
	return 0;
}
// 素数筛
void findPrime(){
	
	int i,j;
	bool isPrime[MAXN];

	
	for(i=2;i<=MAXN;++i){
		
		if(isPrime[i]) continue;
		
		prime[np++]=i;
		
		for(j=i;j<=MAXN;j+=i){
			isPrime[j]=1;
		}
		
	}
	
}
 // 初始化 
void inif(){
	int i;
	
	scanf("%d%d%d",&n,&m,&w);
	for(i=0;i<2;++i){
		scanf("%d%d%d",r+i,c+i,h+i);
	}
	
	dp[1][1][1]=1;
	findPrime();
	
} 
// 判断有无遇到陷阱
bool judge(int i,int j,int k){
	for(int l=0;l<2;++l){
		if(i==r[l]&&j==c[l]&&k==h[l]) return true;
	}
	return false;
} 
// 解题 
void getAns(){
	int i,j,k,l;
	int tmp,cnt;
	
	for(i=1;i<=n;++i){
		
		for(j=1;j<=m;++j){
			
			for(k=1;k<=w;++k){
				
				if(dp[i][j][k]==0||judge(i,j,k)) continue;
				
				for(l=0;l<np;++l){
					
					cnt=0;
					
					tmp=prime[l];
					
					if(i+tmp<=n){
						++cnt;
						dp[i+tmp][j][k]+=dp[i][j][k];
						dp[i+tmp][j][k]%=MOD;
					}
					if(j+tmp<=m){
						++cnt;
						dp[i][j+tmp][k]+=dp[i][j][k];
						dp[i][j+tmp][k]%=MOD;
					}
					if(k+tmp<=w){
						++cnt;
						dp[i][j][k+tmp]+=dp[i][j][k];
						dp[i][j][k+tmp]%=MOD;
					}
					if(cnt==0) break;
				}
				
			}
			
		}
		
	}
	
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值