蓝桥杯习题笔记-算法训练(1-5)

1、区间k的大数查询

问题描述
给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个。

输入格式
第一行包含一个数n,表示序列长度。

第二行包含n个正整数,表示给定的序列。

第三个包含一个正整数m,表示询问个数。

接下来m行,每行三个数l,r,K,表示询问序列从左往右第l个数到第r个数中,从大往小第K大的数是哪个。序列元素从1开始标号。

输出格式
总共输出m行,每行一个数,表示询问的答案。
样例输入
5
1 2 3 4 5
2
1 5 2
2 3 2
样例输出
4
2
数据规模与约定
对于30%的数据,n,m<=100;

对于100%的数据,n,m<=1000;

保证k<=(r-l+1),序列中的数<=106。

#include<iostream>
using namespace std;
int main()
{
	int n,m,num[1001],temp[1000],i,j,k,l,r,max,p,t;
	cin>>n;
	for(i=0;i<n;i++)
		cin>>num[i];
	cin>>m;
	while(m--)
	{
		cin>>l>>r>>k;
		j=0;
		for(i=l-1;i<r;i++)
			temp[j++]=num[i];
		for(i=0;i<j;i++)
		{
			for(p=i+1;p<j;p++)
				if(temp[i]<temp[p])
				{
					t=temp[i];
					temp[i]=temp[p];
					temp[p]=t;
				}
		}
		cout<<temp[k-1]<<endl;

	}
	return 0;
}

2、最大最小公倍数

问题描述
已知一个正整数N,问从1~N中任选出三个数,他们的最小公倍数最大可以为多少。

输入格式
输入一个正整数N。

输出格式
输出一个整数,表示你找到的最小公倍数。
样例输入
9
样例输出
504
数据规模与约定
1 <= N <= 106。

#include<iostream>
using namespace std;
int main()
{
	long long n;
	cin>>n;
	if(n<3)
		cout<<n;
	else
	{
		if(n%2==1)
			cout<<n*(n-1)*(n-2)<<endl;
		else
		{
			if(n%3)
				cout<<n*(n-1)*(n-3)<<endl;
			else
				cout<<(n-1)*(n-2)*(n-3)<<endl;
		}
	}
	return 0;
}

tips

  • n 为int 型时,n的值在1~1000000是没问题的,但最大最小公倍数的值超出了int 的范围,所以应该将 n 定义为long long 型。
  • n为奇数时,最大最小公倍数为n*(n-1)(n-2),因为n和(n-1)、(n-2)三个数一定互质,n为偶数时,n和(n-1)互质,n和(n-2)有公因数2,但若n不是三的倍数,则n和(n-3)互质,若n 时3的倍数,则(n-1)(n-2)*(n-3)为最大最小公倍数。

3、k好数

问题描述
如果一个自然数N的K进制表示中任意的相邻的两位都不是相邻的数字,那么我们就说这个数是K好数。求L位K进制数中K好数的数目。例如K = 4,L = 2的时候,所有K好数为11、13、20、22、30、31、33 共7个。由于这个数目很大,请你输出它对1000000007取模后的值。

输入格式
输入包含两个正整数,K和L。

输出格式
输出一个整数,表示答案对1000000007取模后的值。
样例输入
4 2
样例输出
7
数据规模与约定
对于30%的数据,KL <= 106;

对于50%的数据,K <= 16, L <= 10;

对于100%的数据,1 <= K,L <= 100。

#include<iostream>
typedef long long ll;
using namespace std;
int main()
{
	int K,L;
	const ll N=1000000007;
	ll sum=0;
	cin>>K>>L;
	if(L==1)
	{
		if(K==1)
			cout<<0;
		if(K>1)
			cout<<(K-1)%N;
	}
	if(L>1)
	{
		int i,j,k,a[105][105];
		for(i=0;i<K;i++)//当只有1位数时,以0~k-1为开头的数都只有一个。
			a[1][i]=1;
		for(i=2;i<=L;i++)
			for(j=0;j<K;j++)
			{
				a[i][j]=0;
				for(k=0;k<K;k++)
				{
					if(k!=j-1&&k!=j+1)//两位及两位数以上以0~k-1开头的数,每一个数
					{ //字对应的好数的个数都是i-1行和j不相邻的k对应的好数的个数之和
						a[i][j]+=a[i-1][k];
						a[i][j]%=N;//取余的规律 (a+b)%p=(a % p + b % p) % p 
					}
				}
			}
			for(i=1;i<K;i++)
				sum+=a[L][i];
			sum%=N;//取余的规律 (a+b)%p=(a % p + b % p) % p 
			cout<<sum;
	}	
	return 0;
}
 

(代码参考别人的,注释按照自己理解写的)
tips

  • 动态规划特点:
    把原始问题划分成一系列子问题;
    求解每个子问题仅一次,并将其结果保存在一个表中,以后用到时直接存取,不重复计算,节省计算时间
    自底向上地计算。
    整体问题最优解取决于子问题的最优解(状态转移方程)(将子问题称为状态,最终状态的求解归结为其他状态的求解)

4、节点选择(树形动态规划)

问题描述
有一棵 n 个节点的树,树上每个节点都有一个正整数权值。如果一个点被选择了,那么在树上和它相邻的点都不能被选择。求选出的点的权值和最大是多少?

输入格式
第一行包含一个整数 n 。

接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值。

接下来一共 n-1 行,每行描述树上的一条边。

输出格式
输出一个整数,代表选出的点的权值和的最大值。
样例输入
5
1 2 3 4 5
1 2
1 3
2 4
2 5
样例输出
12
样例说明
选择3、4、5号点,权值和为 3+4+5 = 12 。
数据规模与约定
对于20%的数据, n <= 20。

对于50%的数据, n <= 1000。

对于100%的数据, n <= 100000。

权值均为不超过1000的正整数。

#include<iostream>
#include<vector>
using namespace std;
vector<vector<int> >link;
int dp[100005][2];
void dfs(int son,int par)//par是访问过的元素,若son是第一个根节点,则将par的值为0
{
	int temp,i;
	for(i=0;i<link[son].size();i++)
	{
		temp=link[son][i];
		if(temp!=par)//当temp是为访问过的元素时
		{
			dfs(temp,son);
			dp[son][0]+=max(dp[temp][0],dp[temp][1]);//父结点(不选)+=max(子结点(不选),子结点(选))
			dp[son][1]+=dp[temp][0];//父结点(选)+= 子结点(不选)
		 } 
		}
	}
}
int main( )
{
	int n,i,j,a,b;
	cin>>n;
	for(i=1;i<=n;i++)
		cin>>dp[i][1];
	link.resize(n+1);//给link分配(n+1)的容量
	for(i=1;i<n;i++)
	{
		cin>>a>>b;
		link[a].push_back (b);//a.push_back(b)在a的最后一个向量后插入一个元素,其值为5
		link[b].push_back (a);
	}
	dfs(1,0);//1为第一个结点,结点1为根节点,所以其父节点为0
	cout<<max(dp[1][0],dp[1][1]);
	return 0;
}

tips

  • dp[i][0] i为不选的结点,dp[i][1] i为被选择的结点。当结点i被选择时,其子节点一定不选,当结点i不选时,其子结点有的被选有的不被选(被选择的是最大值)。用dfs深度优先搜索,由最底层的累加,累加,累加,直到回到根节点,搜索结束,根节点的dp[1][0],dp[1][1]的较大值为权值和的最大值。
  • C++ vector类为内置数组提供了一种替代表示,与string类一样 vector 类是随标准 C++引入的标准库的一部分,使用时需包含头文件:#include
    点击查看vector使用总结
    vector可以理解为一个容器,则vector<vector >理解为容器的元素还是容器。(或者二维数组??)
  • DFS:事实上,深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次.
    举例说明之:下图是一个无向图,如果我们从A点发起深度优先搜索(以下的访问次序并不是唯一的,第二个点既可以是B也可以是C,D),则我们可能得到如下的一个访问过程:A->B->E(没有路了!回溯到A)->C->F->H->G->D(没有路,最终回溯到A,A也没有未访问的相邻节点,本次搜索结束).
    简要说明深度优先搜索的特点:每次深度优先搜索的结果必然是图的一个连通分量.深度优先搜索可以从多点发起.如果将每个节点在深度优先搜索过程中的"结束时间"排序(具体做法是创建一个list,然后在每个节点的相邻节点都已被访问的情况下,将该节点加入list结尾,然后逆转整个链表),则我们可以得到所谓的"拓扑排序",即topological sort. [1]
    在这里插入图片描述

5、最短路

问题描述
给定一个n个顶点,m条边的有向图(其中某些边权可能为负,但保证没有负环)。请你计算从1号点到其他点的最短路(顶点从1到n编号)。

输入格式
第一行两个整数n, m。

接下来的m行,每行有三个整数u, v, l,表示u到v有一条长度为l的边。

输出格式
共n-1行,第i行表示1号点到i+1号点的最短路。
样例输入
3 3
1 2 -1
2 3 -1
3 1 2
样例输出
-1
-2
数据规模与约定
对于10%的数据,n = 2,m = 2。

对于30%的数据,n <= 5,m <= 10。

对于100%的数据,1 <= n <= 20000,1 <= m <= 200000,-10000 <= l <= 10000,保证从任意顶点都能到达其他所有顶点。

#include<iostream>
#include<queue>
#include<string.h>
using namespace std;
const int N=20002,M=200002,Max=60000;

int u,v,l,n,m,sta=0;
int head[N],nex[M],ver[M],edge[M],d[N];
bool been[N];
queue<int> q;

void add(int u,int v,int l)
{
	edge[++sta]=l;ver[sta]=v;nex[sta]=head[u];head[u]=sta;
}

void spfa(int aim)
{
	memset(d,Max,sizeof(d));
	memset(been,0,sizeof(been));
	been[aim]=1;
	q.push (aim);
	d[aim]=0;
	while(q.size ())
	{
		int a=q.front ();q.pop ();
		been[a]=0;
		for(int i=head[a];i;i=nex[i])
		{
			int z=edge[i],x=ver[i];
			if((d[a]+z)<d[x])
			{
				d[x]=d[a]+z;
				if(!been[x])
					q.push (x),been[x]=1;
			}
		}
	}
}

int main()
{
	cin>>n>>m;
	for(int i=0;i<m;i++)
	{
		cin>>u>>v>>l;
		add(u,v,l);
	}
	spfa(1);
	for(int i=2;i<=n;i++)
		cout<<d[i]<<endl;
	return 0;
}

tips

  • spfa算法:采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点(一个结点可能优化多次,即出队后可能会再次入队),优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v(结点v可能不止一个)进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。 详细解释(附图)

  • 数据结构queue
    队列:先进先出(First in First out)
    头文件:#include
    常见函数:
    定义一个queue的变量 queue M
    查看是否为空 M.empty() 是的话返回1,不是返回0;
    输出现有元素的个数 M.size()
    显示第一个元素 M.front()
    显示最后一个元素 M.back()
    从已有元素后面增加元素a : M.push(a)
    清除第一个元素 M.pop()

  • 函数memset:内存赋值函数,用来给某一块内存空间进行赋值的;
    头文件: include<string.h>
    原型:void *memset(void *s, int v, size_t n);
    这里s可以是数组名,也可以是指向某一内在空间的指针;v为要填充的值;n为要填充的字节数;
    例子见上面代码.
    memset是逐字节拷贝的,在给char以外的数组赋值时,只能初始化为0或者-1。

  • 关键字sizeof详细解释

int main()
{
	int d[5];char been[5];bool a[5];
	memset(d,-1,5);
	memset(a,0,sizeof(a));
	memset(been,'0',sizeof(been));
	cout<<sizeof(d)<<' '<<sizeof(been)<<' '<<sizeof(a)<<endl;//20 5 5
	for(int i=0;i<5;i++)
		cout<<d[i]<<' ';//-1 -858993409 -858993460 -858993460 -858993460
	cout<<endl;
	for(int i=0;i<5;i++)
		cout<<a[i]<<' ';//0 0 0 0 0
	cout<<endl;
	for(int i=0;i<5;i++)
		cout<<been[i]<<' ';//0 0 0 0 0
	cout<<endl;
	memset(d,-1,sizeof(d));
	for(int i=0;i<5;i++)
		cout<<d[i]<<' ';//-1 -1 -1 -1 -1
	cout<<endl;
	memset(d,3,sizeof(d));
	for(int i=0;i<5;i++)
		cout<<d[i]<<' ';//50529027 50529027 50529027 50529027 50529027(非常大的数)
	cout<<endl;
}
  • int类型作为全局变量和静态变量时如果不初始化,其初值默认为0。但不初始化不稳定。

6、安慰牛奶(最小生成树)

问题描述
Farmer John变得非常懒,他不想再继续维护供奶牛之间供通行的道路。道路被用来连接N个牧场,牧场被连续地编号为1到N。每一个牧场都是一个奶牛的家。FJ计划除去P条道路中尽可能多的道路,但是还要保持牧场之间 的连通性。你首先要决定那些道路是需要保留的N-1条道路。第j条双向道路连接了牧场Sj和Ej(1 <= Sj <= N; 1 <= Ej <= N; Sj != Ej),而且走完它需要Lj的时间。没有两个牧场是被一条以上的道路所连接。奶牛们非常伤心,因为她们的交通系统被削减了。你需要到每一个奶牛的住处去安慰她们。每次你到达第i个牧场的时候(即使你已经到过),你必须花去Ci的时间和奶牛交谈。你每个晚上都会在同一个牧场(这是供你选择的)过夜,直到奶牛们都从悲伤中缓过神来。在早上 起来和晚上回去睡觉的时候,你都需要和在你睡觉的牧场的奶牛交谈一次。这样你才能完成你的 交谈任务。假设Farmer John采纳了你的建议,请计算出使所有奶牛都被安慰的最少时间。

输入格式
第1行包含两个整数N和P。

接下来N行,每行包含一个整数Ci。

接下来P行,每行包含三个整数Sj, Ej和Lj。

输出格式
输出一个整数, 所需要的总时间(包含和在你所在的牧场的奶牛的两次谈话时间)。
样例输入
5 6
10
10
20
6
30
1 2 5
2 3 5
2 4 12
3 4 17
2 5 15
3 5 6
样例输出
176
数据规模与约定
5 <= N <= 10000,N-1 <= P <= 100000,0 <= Lj <= 1000,1 <= Ci <= 1,000。
tips

  • 最小生成树(MST):一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。 在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集(即)且为无循环图,使得 w(T) 最小,则此 T 为 G 的最小生成树。最小生成树其实是最小权重生成树的简称。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值