HPU2020蓝桥杯省赛训练(一) 题解

目录

A

算法提高 找素数

B

算法训练 区间k大数查询

C

算法训练 操作格子

D

算法提高 士兵排队问题

E

基础练习 十六进制转八进制

F

算法训练 最大最小公倍数

G

算法提高 欧拉函数

H

算法训练 乘法次数

I

算法训练 方格取数


​​​​

A

算法提高 找素数

资源限制

时间限制:1.0s   内存限制:256.0MB

问题描述

  给定区间[L, R] , 请计算区间中素数的个数。

输入格式

  两个数L和R。

输出格式

  一行,区间中素数的个数。

样例输入

2 11

样例输出

5

数据规模和约定

  2 <= L <= R <= 2147483647 R-L <= 1000000

思路:数据范围过大.普通的素数打表肯定是不行的,注意使用R-L <= 1000000这个条件,对sqrt(R)以内的素数进行打表,筛出L到R中素数的倍数(即合数),保存和数-L的状态,遍历得到结果.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
typedef long long ll;

using namespace std;

const int N = 1000000 + 10;

int prime[N];
int num[N];
int ans[N];
int cnt=0;

void prim(int n)
{
	for(int i=2; i<=n; i++)
	{
		if(!prime[i])
		{
			num[cnt++] = i;
			for(int j=i*2; j<=n; j+=i)
			{
				prime[j] = 1;
			}
		}					
	} 
}


int main()
{
	int l,r;
	cin>>l>>r;
	prim((int)sqrt(r));
	for(int i=0; i<cnt; i++)
	{
		int a = num[i];
		for(ll j=(l+a-1)/a*a; j<=r; j+=a)//注意减1,否则容易漏掉l-l+a中a的倍数.
		{
			if(j == a) continue;
			else ans[j-l] = 1;
		}
	}
	int cnt = 0;
	for(int i=0; i<=r-l; i++)
	{
		if(ans[i] == 0) cnt++;
	}
	cout<<cnt;
	return 0;
}

B

算法训练 区间k大数查询

时间限制:1.0s   内存限制:256.0MB

问题描述

给定一个序列,每次询问序列中第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。

 思路:把原数组L-R复制到处理数组中,从大到小进行排序,输出第K个.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
typedef long long ll;

using namespace std;

const int N = 1000000 + 10;

int ans;

int num[N];
int temp[N];
bool cmp(int a, int b)
{
	return a > b;
}
int main()
{
	int n,m;
	cin>>n;
	for(int i=1; i<=n; i++)
		scanf("%d",&num[i]);
	cin>>m;
	int l,r,k;
	while(m--)
	{
		scanf("%d %d %d",&l,&r,&k);
		int cnt=1;
		for(int i=l; i<=r; i++)
			temp[cnt++] = num[i];
		sort(temp + 1, temp + cnt, cmp);
		printf("%d\n",temp[k]);
	}
	return 0; 
} 

 

C

算法训练 操作格子

资源限制

时间限制:1.0s   内存限制:256.0MB

问题描述

有n个格子,从左到右放成一排,编号为1-n。

共有m次操作,有3种操作类型:

1.修改一个格子的权值,

2.求连续一段格子权值和,

3.求连续一段格子的最大值。

对于每个2、3操作输出你所求出的结果。

输入格式

第一行2个整数n,m。

接下来一行n个整数表示n个格子的初始权值。

接下来m行,每行3个整数p,x,y,p表示操作类型,p=1时表示修改格子x的权值为y,p=2时表示求区间[x,y]内格子权值和,p=3时表示求区间[x,y]内格子最大的权值。

输出格式

有若干行,行数等于p=2或3的操作总数。

每行1个整数,对应了每个p=2或3操作的结果。

样例输入

4 3
1 2 3 4
2 1 3
1 4 3
3 1 4

样例输出

6
3

数据规模与约定

对于20%的数据n <= 100,m <= 200。

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

对于100%的数据1 <= n <= 100000,m <= 100000,0 <= 格子权值 <= 10000。

思路:线段树模板题,需要同时维护一个maxn值和一个sum值.

#include<iostream>
#include<climits>
#define ll long long
using namespace std;

const int N = 1e5 + 10;
int n,m;

struct node{
	int l,r;
	int maxn;
	int sum;
}tree[4*N];

int num[N];

void build(int u,int l,int r)
{
	if(l == r) 
	{
		tree[u] = {l,r,num[r],num[r]};
	}
	else
	{
		int mid = (r + l) >> 1;
		build(u << 1, l, mid);
		build(u << 1 | 1, mid + 1, r);		
		tree[u] = {l,r};
		tree[u].maxn = max(tree[u << 1].maxn, tree[u << 1 | 1].maxn);
		tree[u].sum = tree[u << 1].sum + tree[u << 1 | 1].sum;
	}
}

int query1(int u, int l ,int r)
{
	if(tree[u].l >= l && tree[u].r <= r)	return tree[u].maxn;
	int maxnn = INT_MIN;
	int mid = (tree[u].l + tree[u].r) >> 1;
	if(mid >= l) maxnn = query1(u << 1, l, r);
	if(mid < r) maxnn = max(maxnn, query1(u << 1 |  1, l, r));
	return maxnn;
}

int query2(int u, int l ,int r)
{
	if(tree[u].l >= l && tree[u].r <= r)	return tree[u].sum;
	int sum = 0;
	int mid = (tree[u].l + tree[u].r) >> 1;
	if(mid >= l) sum = query2(u << 1, l, r);
	if(mid < r) sum += query2(u << 1 |  1, l, r);
	return sum;
}

void modify(int u, int x, int v)
{
	if(tree[u].l == tree[u].r) 
	{
		tree[u].maxn = v;
		tree[u].sum = v;
	}
	else
	{
		int mid =(tree[u].l + tree[u].r) >> 1;
		if(x <= mid) modify(u << 1, x, v);
		else modify(u << 1 | 1,x,v);
		tree[u].sum = tree[u << 1].sum + tree[u << 1 | 1].sum;
		tree[u].maxn = max(tree[u << 1].maxn, tree[u << 1 | 1].maxn);
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1; i<=n; i++)
	{
		scanf("%d",&num[i]);
	}		
	build(1,1,n);
	for(int i=0; i<m; i++)
	{
		int opt;
		cin>>opt;
		if(opt == 1)
		{
			int x, y;
			cin>>x>>y;
			modify(1,x,y);
		}
		else if(opt == 2)
		{
			int l,r;
			cin>>l>>r;
			printf("%lld\n",query2(1,l,r));
		}
		else if(opt == 3)
		{
			int l,r;
			cin>>l>>r;
			cout<<query1(1,l,r)<<endl;
		}
	}
	return 0;
}

D

算法提高 士兵排队问题

资源限制

时间限制:1.0s   内存限制:256.0MB

试题

  有N个士兵(1≤N≤26),编号依次为A,B,C,…,队列训练时,指挥官要把一些士兵从高到矮一次排成一行,但现在指挥官不能直接获得每个人的身高信息,只能获得“P1比P2高”这样的比较结果(P1、P2∈A,B,C,…,Z,记为 P1>P2),如”A>B”表示A比B高。
  请编一程序,根据所得到的比较结果求出一种符合条件的排队方案。
  (注:比较结果中没有涉及的士兵不参加排队)

输入要求

  比较结果从文本文件中读入(文件由键盘输入),每个比较结果在文本文件中占一行。

输出要求

  若输入数据无解,打印“No Answer!”信息,否则从高到矮一次输出每一个士兵的编号,中间无分割符,并把结果写入文本文件中,文件由键盘输入:

样例输入

A>B
B>D
F>D

样例输出

AFBD

思路:拓扑排序模板题,判断有没有环,无环即可.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
typedef long long ll;

using namespace std;

const int N = 300;

int in[N];
vector <char> v[N];
vector <char> ans;
bool st[N];

int main()
{
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	char c[4];
	while(~scanf("%s",c+1))
	{
		char x = c[1];
		char y = c[3];
		v[x].push_back(y);
		in[y]++;
		st[x]=st[y]=1;
	}
	queue<char> q;
	int total = 0;
	for(int i='A'; i<='Z'; i++)
	{
		if(in[i] == 0 && st[i] == 1)
		{
			q.push(i);
		}
		if(st[i] == 1) total++;
	}
	while(!q.empty())
	{
		char tem = q.front();
		total--;
		ans.push_back(tem);
		q.pop();
		for(int i=0; i<v[tem].size(); i++)
		{
			in[v[tem][i]]--;
			if(in[v[tem][i]] == 0) q.push(v[tem][i]);
		}
	}
 	if(total == 0)
	{
		for(int i=0; i<ans.size(); i++)
			cout<<ans[i];
	}
	else puts("No Answer!");
	return 0;
}

E

基础练习 十六进制转八进制

资源限制

时间限制:1.0s   内存限制:512.0MB

问题描述
  给定n个十六进制正整数,输出它们对应的八进制数。

输入格式
  输入的第一行为一个正整数n (1<=n<=10)。
  接下来n行,每行一个由0~9、大写字母A~F组成的字符串,表示要转换的十六进制正整数,每个十六进制数长度不超过100000。

输出格式
  输出n行,每行为输入对应的八进制正整数。

  【注意
  输入的十六进制数不会有前导0,比如012A。
  输出的八进制数也不能有前导0。

样例输入
  2
  39
  123ABC

样例输出
  71
  4435274

  提示
  先将十六进制数转换成某进制数,再由某进制数转换成八进制。

思路:利用sscanf的16进制读取功能每六位读取一次,再利用printf的8进制的输出功能输出,注意对不足六位的16进制数及转化后不足八位的16进制数进行特殊处理.

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
 
const int N = 100000 + 10;
 
char num[N];
 
int main()
{
	int n;
	cin>>n;
	for(int i=0; i<n; i++)
	{
		scanf("%s",num);
		int len = strlen(num);
		int x;
		if(len % 6)//如果16进制数不能刚好分成多个8位8进制数,则在开头对多出来的部分特殊处理 
		{
			switch(len % 6)
			{
				case 1:
					sscanf(num,"%1x",&x);
					printf("%o",x);
					break;//别忘了break 
				case 2:
					sscanf(num,"%2x",&x);
					printf("%o",x);
					break;
				case 3:
					sscanf(num,"%3x",&x);
					printf("%o",x);
					break;
				case 4:
					sscanf(num,"%4x",&x);
					printf("%o",x);
					break;
				case 5:
					sscanf(num,"%5x",&x);
					printf("%o",x);	
					break;				
			}			
		}
		int j = len % 6;
		while(j < len)
		{
			sscanf(num+j,"%6x",&x);
			if(j==0) printf("%o",x);
			else printf("%08o",x);	//在16进制数的开头之后可能有6位数太小,转化成8进制数不足8位,需要在不足的位置添0		
			j+=6;
		} 
		puts("");
	}	
	return 0;
}

 

F

算法训练 最大最小公倍数

资源限制

时间限制:1.0s   内存限制:256.0MB

问题描述

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

输入格式

输入一个正整数N。

输出格式

输出一个整数,表示你找到的最小公倍数。

样例输入

9

样例输出

504

数据规模与约定

1 <= N <= 106。

思路:这题的关键在与只选取3个数,我们知道如果只选择两个数的话,那么肯定选两个相邻的两数,因为他两互质所以最小公倍数是最大的。所以面对要选择三个数,我们也可以设法选出3个互质的数。对于相邻的a,b,c三个数有奇偶奇,偶奇偶,可以发现奇偶奇三个数一定是互质的,a与b互质,b与c互质,a与b之间不可有共因子2,|c-a|<3,所以也不可能存在共因子3,之后的更别说了。
然后是偶奇偶,其最大最小公倍数必定至少除2,所以可以尝试把其中一个偶换成奇数,且不构成3的倍数,或者整体-1,变成奇偶奇。

#include <iostream>
#include <algorithm>
#include <map>
#include <string>
#include <queue>
#include <vector>
using namespace std;
const int maxn = 300;
typedef pair<int,int> pii;
typedef long long ll;
 
ll N;
int main(){
    cin>>N;
    ll mx = 0;
    if(N<3) mx = N;
    else if(N&1){ //奇偶奇
        mx = N*(N-1)*(N-2);
    }else{
        if(N%3 !=0){ //N和N-3不是3的倍数
            mx = N*(N-1)*(N-3);
        }else{ //整体-1
            mx = (N-1)*(N-2)*(N-3);
        }
    }
    cout<<mx<<endl;
    return 0;
}

G

算法提高 欧拉函数

资源限制

时间限制:1.0s   内存限制:512.0MB

说明

  2016.4.5 已更新试题,请重新提交自己的程序。

问题描述

  给定一个大于1,不超过2000000的正整数n,输出欧拉函数,phi(n)的值。
  如果你并不了解欧拉函数,那么请参阅提示。

输入格式

  在给定的输入文件中进行读入:
  一行一个正整数n。

输出格式

  将输出信息输出到指定的文件中:
  一行一个整数表示phi(n)。

样例输入

17

样例输出

16

提示

  欧拉函数phi(n)是数论中非常重要的一个函数,其表示1到n-1之间,与n互质的数的个数。显然的,我们可以通过定义直接计算phi(n)。
  当然,phi(n)还有这么一种计算方法。
  首先我们对n进行质因数分解,不妨设n=p1^a1 * p2^a2 * ... * pk^ak (这里a^b表示a的b次幂,p1到pk为k个互不相同的质数,a1到ak均为正整数),那么
  phi(n)=n(1-(1/p1))(1-(1/p2))....(1-(1/pk))
  稍稍化简一下就是
  phi(n)=n(p1-1)(p2-1)...(pk-1)/(p1*p2*...*pk)

计算的时候小心中间计算结果超过int类型上界,可通过调整公式各项的计算顺序避免(比如先做除法)!

用到的算法:欧拉筛
线性复杂度,把前maxn个数的欧拉值筛选出来

#include <iostream>
#include <algorithm>
#include <map>
#include <string>
#include <queue>
#include <vector>
using namespace std;
const int maxn = 2e6+10;
typedef pair<int,int> pii;
typedef long long ll;
 
ll N;
int E[maxn];
void init()
{
    E[1] = 1;
    for(int i=2;i<maxn;i++){
        if(!E[i])
            for(int j=i;j<maxn;j+=i){
                if(!E[j]) E[j]=j;
                E[j] = E[j]/i*(i-1);
            }
    }
}
int main(){
    init();
    cin>>N;
    cout<<E[N]<<endl;
    return 0;
}

 

H

算法训练 乘法次数

资源限制

时间限制:1.0s   内存限制:999.4MB

问题描述

  给你一个非零整数,让你求这个数的n次方,每次相乘的结果可以在后面使用,求至少需要多少次乘。如24:2*2=22(第一次乘),22*22=24(第二次乘),所以最少共2次;

输入格式

  第一行m表示有m(1<=m<=100)组测试数据;
  每一组测试数据有一整数n(0<n<=100000000);

输出格式

  输出每组测试数据所需次数s;

样例输入

3
2
3
4

样例输出

1
2
2

用到的算法:快速幂

思路:明显可以看出,求出n次幂的最快方法应该是不断累乘,即2^4 * 2^4,2^8 * 2^8,尽快求出n的二进制最高位的2^poww,再乘以已经算出过的低位即可.

#include <iostream>
#include <cmath>
using namespace std;
typedef long long ll;
const ll mod = 1000000007;
int T;
ll ksm(ll a,ll b){
    int poww = 0,cnt = 0;
    while(b){
        if(b&1) {
            cnt += 1;
        }
        a = a*a% mod;// 也可以不写
        poww++;
        b>>=1;
    }
    return poww+cnt-2;
}
int main(){
    cin>>T;
    while(T--){
        ll b;scanf("%lld",&b);
        printf("%lld\n",ksm(2,b));
    }
 
    return 0;
}

I

算法训练 方格取数

资源限制

时间限制:1.0s   内存限制:256.0MB

问题描述
  设有N*N的方格图(N<=10),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。
  某人从图的左上角的A 点(1,1)出发,可以向下行走,也可以向右走,直到到达右下角的B点(N,N)。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
  此人从A点到B 点共走两次,试找出2条这样的路径,使得取得的数之和为最大。
入格式
  输入的第一行为一个整数N(表示N*N的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的0表示输入结束。
出格式
  只需输出一个整数,表示2条路径上取得的最大的和。

  8
  2 3 13
  2 6 6
  3 5 7
  4 4 14
  5 2 21
  5 6 4
  6 3 15
  7 2 14
  0 0 0
样例输出
  67

思路:四维DP,两个人同时走.

#include<iostream>
#include<map>
#include<cmath>
using namespace std;

const int N = 11;

int val[N][N];
int dp[N][N][N][N];

int main()
{
	int n;
	cin>>n;
	int x,y,z;
	while(cin>>x>>y>>z&&x&&y&&z)
	{
		val[x][y] = z;
	}
	dp[1][1][1][1] = val[1][1];
	for(int x1=1; x1<=n; x1++)
	{
		for(int y1=1; y1<=n; y1++)
		{
			for(int x2=1; x2<=n; x2++)
			{
				for(int y2=1; y2<=n; y2++)
				{
					if(x1+y1 != x2+y2) continue;
					int tem = 0;
					tem = max(dp[x1-1][y1][x2-1][y2],dp[x1-1][y1][x2][y2-1]);
					tem = max(dp[x1][y1-1][x2-1][y2], tem);
					tem = max(dp[x1][y1-1][x2][y2-1], tem);
					dp[x1][y1][x2][y2] = tem + val[x1][y1] + val[x2][y2];
					if(x1 == x2 && y1 == y2) dp[x1][y1][x2][y2] -=val[x1][y1];
				}
			}
		}
	}
	cout<<dp[n][n][n][n];
	return 0;
}


//8
//2 3 13
//2 6 6
//3 5 7
//4 4 14
//5 2 21
//5 6 4
//6 3 15
//7 2 14
//0 0 0

时间限制:1.0s   内存限制:256.0MB资源限制

问题描述

对于长度为5位的一个01串,每一位都可能是0或1,一共有32种可能。它们的前几个是:

00000

00001

00010

00011

00100

请按从小到大的顺序输出这32种01串。

输入格式

本试题没有输入。

输出格式

输出32行,按从小到大的顺序每行一个长度为5的01串。

样例输出

00000
00001
00010
00011
<以下部分省略>

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
typedef long long ll;

using namespace std;

int num[100];

int main()
{
	for(int i=0; i<32; i++)
	{
		int cnt=0;
		int j=i;
		while(j)
		{
			num[cnt++] = j % 2;
			j = j/2;
		}
		for(int k=4; k>=0; k--)
			cout<<num[k];
		puts("");
	} 
}

 部分题解代码来自:https://blog.nowcoder.net/n/09f531a212f54db98f1aceee72468bfb?tdsourcetag=s_pctim_aiomsg

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值