2020年合肥市信息学市赛初中组

本文介绍了2020年合肥市信息学市赛初中组的多个题目,涉及数学、数组、数论和树形结构的算法问题。题目包括找满足条件的整数个数、数组中不同元素的最接近下标和最长倍数子序列。文章提供了样例输入输出及解题思路,如利用数学性质优化复杂度,通过递推和动态规划求解问题。
摘要由CSDN通过智能技术生成

2020年合肥市信息学市赛初中组-T1-小 C 的数学(math)

题目描述

小 C 想要成为一名 OIer,于是他提前学习数学,为 OI 做好铺垫。这一天,他的数学老师给了一道题:给定正整数 a,以及给定一个区间 [b,c],其中 b,c 均为整数(b,c 保证非负)。寻找所有合法的 x,满足b≤x≤c,并且 a 能够整除 x,即 x 除以 a 的余数为 00。
可小 C 很懒,不想找出来所有的解,他只想知道这样的 x 有多少个。

输入格式

共一行,依次三个整数 a,b,c,如题目所描述。

输出格式

仅一行一个数,表示答案。

样例

输入数据#1

2 3 6

输出数据#1

2

解释#1

x 可以为 4,6,总共有 2 个。

输入数据#2

3 1 7

输出数据#2

2

解释#2

x 可以为 3,6,总共有 2 个。

数据范围

对于 40% 的数据:0<a≤10^3,0≤b≤c≤10^3;

对于 70%的数据:0<a≤10^7,0≤b≤c≤10^7;

对于 100% 的数据:0<a≤10^9,0≤b≤c≤10^18。

解析

考点:数学,质数

思路:

首先看数据范围,要用 long long。

再考虑时间复杂度,如果用循环,那么为 O(c-b)O(c−b),有超时风险。

优化:

因为要找的是 a的倍数,而 a的倍数都是固定的,b∼c 之间有多少个 a的倍数是很好算的,拿 1∼c 中的个数减去 1∼b 中的个数就可以了。需要注意的是如果 b 能整除 a,那么最终个数要多一个。

参考代码:

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

int main(){
    long long a, b, c, ans = 0;
    cin >> a >> b >> c;
    cout << c/a - b/a + (b%a==0?1:0);
    return 0;
}

2020年合肥市信息学市赛初中组-T2-小C的数组(array)

题目描述

小 C 终于成为一名萌新 OIer,最近他在学习数组。

小 C 要练习数组。一次,小 C 得到了一个长度为 n 的数组 a。

现在,对于每一个下标 i,小 C 想找出比 i 小. 且距离 i 最近的下标 j,使得满足 a[i] != a[j],如果不存在,则 j = 0。记下标 i 对应的答案f[i] = j,小 C 为了确保自己的程序正确,想让你来检查 f 数组。

可你不能告诉他整个答案,你只需要告诉他 f 数组所有元素的和即可。

输入格式

从文件 array.in 中读取数据。

共两行,第一行一个正整数 n,表示数组长度;

第二行 n 个正整数,第 i 个表示 ai。

输出格式

输出到文件 array.out 中。

仅一行一个数,表示 f 数组所有元素的和。

输入输出样例

输入样例1:
6 
1 1 2 3 2 1
输出样例1:
14
输入样例2:
12 
3 3 3 3 2 2 2 2 4 4 1 1
输出样例2:
52

说明

【样例 1 解释】

f[i]依次为 (0, 0, 2, 3, 4, 5),总和为 14。

【样例 2 解释】

f[i]依次为 (0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 10, 10),总和为 52。

【数据范围】

对于 40% 的数据:n ≤ 1000,1 ≤ a[i] ≤ 10,且保证数据随机;

对于 70% 的数据:n ≤ 10^6,1 ≤ a[i] ≤ 10,且保证数据随机;

对于 90% 的数据:n ≤ 10^6,1 ≤ a[i] ≤ 10;

对于 100% 的数据:n ≤ 10^6,1 ≤ a[i] ≤ 1000。

随机数据的生成方式如下:对于每一个 a[i],等概率地从 1 到 10 中产生。

解析

考点:递推,前缀和,数组标记

思路

注意几个点:

(1)数据量范围非常大,要输入最多 100万个数,因此最好用 scanf,题目也提示了。

(2)考虑时间复杂度。写个双重for循环,暴力枚举计数?O(n^2),肯定超时了。

如何优化?

考虑:要找的是每个数字前面和它不相同的数字的下标,而且是最近的,那么和它相同并且在它前面的数字就不需要考虑了,也就不需要暴力枚举循环了。

所以可以用标记法,统计连续相同的数字及其个数,这样就可以压缩数组的大小了,枚举每一个数字前比它小的数字,也就只用直接在新的数组中找它的前面的一个就可以了。

更简洁的是用递推法,当 a[i]==a[i−1] 时,很明显,f[i]=f[i−1],否则不相等,那么按照 f[i] 的定义,f[i]=i−1。

(3)再次考虑数据范围,要不要用 long long?考虑 f 数组是一个下标的和数组,那么最大就是当每两个相邻数字都不相同时最大,\sum_{i=1}^{n}i-1,即 n^2/2。10^12/2 超 int 范围了,需要用 long long。

暴力枚举的代码(70分)

参考代码

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

const int N = 1e6+5;
long long n, a[N], ans;

int main(){
    scanf("%lld", &n);
    for(int i = 1; i <= n; i++){
        scanf("%lld", &a[i]);
        int j = i-1;
        for(; j > 0 && a[j] == a[i]; j--) ;
        ans += j;
    }
    printf("%lld", ans);
    return 0;
}

AC代码:

参考代码:

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

const int N = 1e6+5;
long long n, t, pre=-1, ans;
long long b[N], c[N], sum[N], len=1;  // 统计连续相同数字 b:数字 c:个数 sum:前缀个数和
int main(){
    cin >> n; 
    for(int i = 1; i <= n; i++){
        cin >> t;
        if(t != pre)
            b[len] = t, c[len] = 1, sum[len] = sum[len-1] + c[len-1], len++;
        else
            c[len-1]++;
        pre = t;
    }
    
    for(int i = 1; i < len; i++)
        ans +=  sum[i]*c[i];
    cout << ans;
    return 0;
}

更简洁的递推解法:

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

const int N = 1e6+5;
long long n, a[N], f[N], ans;

int main(){
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        if(a[i] == a[i-1])
            f[i] = f[i-1];
        else
            f[i] = i-1;
        ans += f[i];
    }
    cout << ans;
    return 0;
}

2020年合肥市信息学市赛初中组-T3-小C的数(number)

题目描述

小 C 非常喜欢数字。

这次,他得到了一个长度为 n 的正整数数列 A,第 i 项为 ai。

现在,小 C 想要找出来 A 中最长的子序列B = {b1, b2, · · · , bm},使得对于任意的二元组(i, j),bi 和 bj 满足倍数关系

小 C 突然不会最长不下降子序列了,于是找到了你来帮他求出最长的子序列的长度。

子序列:对于长度为 n 的数列 A,对于 B = {b1, b2, · · · , bm},若 b1 = ap1, b2 = ap2, · · · , bm = apm,则必须要满足 1 ≤ p1 < p2 < · · · < pm ≤ n,这样的数列 B 称为 A 的子序列。其中子序

列 B 可以为空。

倍数关系:对于两个数 a, b,两者成倍数关系,即 a 能被 b 整除,或者 b 能被 a 整除,两者至少一种成立。

输入格式

从文件 number.in 中读取数据。

共两行,第一行有一个正整数 n,表示数列 A 的长度;

第二行有 n 个正整数,第 i 个数表示 ai。

输出格式

输出到文件 number.out 中。

仅一行一个数,表示子序列长度的最大值。

输入输出样例

输入样例1:
5 
1 2 3 8 16
输出样例1:
4
输入样例2:
10 
1 4 6 3 9 11 16 24 42 36
输出样例2:
4

说明

【样例 1 解释】

最长子序列为 {1, 2, 8, 16},长度为 4。

【样例 2 解释】

最长长度为 4,选取方案不唯一,其中一种最长的子序列为 {1, 6, 3, 24}。

【数据范围】

对于所有数据:0 < N ≤ 3 × 106,0 < ai ≤ 107。

【提示】

如有需要,请使用scanf 读入以及减少使用STL,以降低程序本身带来的常数。

解析

考点:动态规划,线性dp

1.搜索+剪枝骗分(40分)

#include <bits/stdc++.h>
using namespace std;
int n, a[3000000], b[3000000], ans = 0, len;
void dfs(int x){
    if(x == n+1){
        if(len < ans) return;
        int pre = -1;
        for(int i = n; i >= 1; i--){
            if(b[i] == 1) {
                if(pre != -1) {
                    if(pre % a[i] != 0)
                        return;
                }
                pre = a[i];
            }
        }
        ans = max(ans, len);
        return ;
    }
    // 放
    b[x] = 1;  len++;
    dfs(x+1);
    b[x] = 0;  len--;
    // 不放
    dfs(x+1);

}
int main(){
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }
    sort(a+1, a+n+1);
    if(n == 3){
        if(a[2]%a[1]==0 && a[3]%a[2] == 0)
            cout << 3;
        else if(a[2]%a[1]==0 || a[3]%a[2] == 0 || a[3] % a[1] == 0)
            cout << 2;
        else
            cout << 1;
    }else{
        dfs(1);
        cout << ans;
    }
    return 0;
}

2.线性DP,倒序枚举

因为选择的是数字集合,跟顺序没关系,而且又是要互成倍数

所以,一个数字如果选,就都选,如此就想到用数组标记统计每个数字出现的次数

然后在标记数组上做一个从大到小的倒序的类似埃氏筛的 DP 更新过程。

参考代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e7+5;
int n, t, num[N], maxn, ans;
int f[N]; // f[i]: 以 i 作为最小值的子序列的最大长度
int main(){
    ios::sync_with_stdio(0);
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> t;
        num[t]++;
        maxn = max(maxn, t);
    }
    f[maxn] = num[maxn];
    ans = f[maxn];
    for(int i = maxn - 1; i; i--){
        if(num[i]==0) continue;
        f[i] = num[i];  // 1 倍 i 构成的子序列的长度
        for(int j = 2;j * i <= maxn; j++){
            if(num[j * i])  // i 的 j 倍数字存在,就更新 f[i] 和 ans
                f[i] = max(f[i], num[i] + f[j * i]);
        }
        ans = max(ans, f[i]);
    }
    cout << ans;
    return 0;
}

2020年合肥市信息学市赛初中组-T4-小C 的树(tree)

题目描述

定义:树是一个有 n 个点 n-1 条边的无向连通图,任意两个点间恰有一条简单路径。最末端仅有一条边相连的节点称为叶子。

小 C 家有一个后院,院子的中央种有一棵树。小 C 喜欢在雨后初晴时到院子里观察。这棵参天大树可以抽象成一棵 n 个节点的树,节点从 1 到 n 编号,树的根在 1 号节点。第 i 个节点有一个正整数 ai 表示该处的美感值。

小 C 尤其喜欢观察蚂蚁。这天,小 C 捉来了 m 只蚂蚁。他想挑选树上的 m 个节点,每个节点放上一只蚂蚁。小 C 知道蚂蚁在树上时,会一直朝着根的方向移动,中间会经过一个个节点,最后到达根节点,之后,蚂蚁便到达了地面。

热爱自然的小 C 想知道这 m 只蚂蚁经过的节点的美感值和的最大值是多少,于是请你来帮帮忙。注意经过的节点包括初始节点,且多次访问的节点仅算入一次。.

输入格式

从文件 tree.in 中读取数据。

共三行,第一行有两个正整数 n, m,表示树的大小和选取的节点数;

第二行有 n 个非负整数,第 i 个数表示 ai;

第三行有 n - 1 个正整数,第 i 个数 fi 表示节点 i + 1 的父亲节点。保证 0 < fi ≤ i。

输出格式

输出到文件 tree.out 中。

仅一行一个数,表示美感值和的最大值。

输入输出样例

输入样例1:
5 2
1 2 3 1 3
1 1 2 2

输出样例1:
9

输入样例2:
10 3
3 4 2 2 3 1 3 3 5 4
1 2 1 1 3 3 2 4 6

输出样例2:
24

说明

【样例 1 解释】

这棵树的形态如下图:

选择 {3, 5} 两个节点,则经过的节点集合为 {1, 2, 3, 5}。

红色数字为每个节点的美感值,故美感值和为 1 + 2 + 3 + 3 = 9。可以证明这个值是最大值。

【样例 2 解释】

选取 {4, 9, 10} 三个节点,得到的美感值和为 24。方案可能不唯一。

【数据范围】

对于所有数据:0 < m ≤ n ≤ 5 × 106,0 ≤ ai ≤ 5。

部分测试点放 n = 的形式,方便选手直接判断是否为对应测试点。

【提示】

如有需要,请使用scanf 读入以及减少使用STL,以降低程序本身带来的常数。

解析

考点:二叉树,树的遍历,贪心

思路:

剖树为链

树链剖分+贪心: 贪心地想了一下,肯定从叶子开始往上找,和会更大,蚂蚁只有放在叶子节点上才能最优,将所有节点按照到根节点的点权之和从大到小排序 。
由于每个节点仅能被访问一次,每个节点仅能属于一条链路。
链路:从叶子节点到根节点的一条路径 。
问题转化为:给定一棵树,要求分解成若干条互不相交的链路,求从这些链路中选取m条链路的和的最大值 。
1.将树分解为多条链   dfs 求出以每个节点到根节点的最大点权和dis[i],并记录从哪个叶子节点经过当前节点是的当前链路点权和最大 leaf[i] 
2.枚举每个叶子节点,由下而上去统计和当前叶子节点同一个链路的点权和 
2.对链从小到大排序  sort
3.取前m个链,求和    遍历 

领接表参考代码

#include<iostream>
#include<cstdio>
#include<vector> 
#include<algorithm>
using namespace std; 
const int N=5*1e6+5,M=N*5;
//dis[i] 记录i到根节点的最大点权和 
//d[i] 记录i到根节点的最大点权和(去重,已经被其他节点计算过的去掉)
//leaf[i]记录经过节点i的最长链(点权和最大)的叶子节点编号 
int n,m,a[N],p[N],leaf[N],d[N],dis[N],ans;
vector<int> G[N]; 
void dfs(int r,int pa){ //r当前节点 pa双亲 
	dis[r]=dis[pa]+a[r];
	leaf[r]=r;  //初始值就是自己,如果是叶子节点就是自己 ,不是后面会更新 
	for(int i=0;i<G[r].size();i++){
		dfs(G[r][i],r);
	} 
	//枚举结点r的所有孩子,找到经过该节点每个孩子节点所经过的链路,且点权和最大,更新leaf 
	int maxd=0;
	for(int i=0;i<G[r].size();i++){
		//G[r][i] r的第i个孩子,G[r][i] r的第i个孩子从属于哪个链路的叶子节点路过
		//dis[G[r][i]] 从哪个叶子节点出发到根节点且经过r的第i个孩子点权和最大 
		if(dis[leaf[G[r][i]]]>maxd){
			maxd=dis[leaf[G[r][i]]];
			leaf[r]=leaf[G[r][i]];//更新r节点的leaf值,leaf[r]的值为从根节点到叶子节点权值和最大且经过节点r的链路中的叶子节点编号 
		}
	} 
}
int main(){
	scanf("%d %d",&n,&m);
	//读取数据
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=2;i<=n;i++){
		scanf("%d",&p[i]);
		G[p[i]].push_back(i); //i是p[i]的孩子 
	} 
	dfs(1,0);
	//枚举计算所有叶节点,记录每个叶子节点所在链路的最大点权和,同一个链leaf值相同 
	for(int i=1;i<=n;i++){
		if(leaf[i]==i){ //叶子节点 
			d[i]+=a[i];; //累加自身
			int fa=p[i];
			//双亲存在,且经过双亲的叶子节点链就是自己 
			while(fa&&leaf[fa]==i){
				d[i]+=a[fa];
				fa=p[fa]; 
			} 
		}
	} 
	sort(d+1,d+1+n);
	for(int i=n;i>n-m;i--){
		ans+=d[i];
	}
	printf("%d",ans);
	return 0;
}

链式前向星写法:

#include<iostream>
#include<cstdio>
#include<vector> 
#include<algorithm>
using namespace std;   
const int N=5*1e6+5;
//dis[i] 记录i到根节点的最大点权和 
//d[i] 记录i到根节点的最大点权和(已经被计算过的去掉)
//leaf[i]记录经过节点i的最长链的编号 
int n,m,a[N],p[N],leaf[N],d[N],dis[N],ans;
int k,h[N];
struct edge{
	int from,to,next;
};
edge e[2*N];
//链式前向星做法 
void add_edge(int u,int v){ //父子 
	k++;
	e[k].from=u;
	e[k].to=v;
	e[k].next=h[u];
	h[u]=k;
} 
void dfs(int r,int pa){ //r当前节点 pa双亲 
	dis[r]=dis[pa]+a[r];
	leaf[r]=r;  //初始值就是自己,如果是叶子节点就是自己 ,不是后面会更新 
	//枚举r的每一个孩子 
	for(int i=h[r];i!=0;i=e[i].next){
		int j=e[i].to;
		dfs(j,r);
	} 
	
	//枚举结点r的所有孩子 
	int maxn=0;
	for(int i=h[r];i!=0;i=e[i].next){
		//找到当前节点所有孩子中最大的链 
		if(dis[leaf[e[i].to]]>maxn){
			maxn=dis[leaf[e[i].to]];
			leaf[r]=leaf[e[i].to];
		}
	} 
}
int main(){
	scanf("%d %d",&n,&m);
	//读取数据
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=2;i<=n;i++){
		scanf("%d",&p[i]);
		add_edge(p[i],i);
	} 
	dfs(1,0);
	//枚举计算所有叶节点,记录每个叶子节点所在链路的最大点权和,同一个链leaf值相同 
	for(int i=1;i<=n;i++){
		if(leaf[i]==i){ //叶子节点 
			d[i]+=a[i];; //累加自身
			int fa=p[i];
			//双亲存在,且经过双亲的叶子节点链就是自己 
			while(fa&&leaf[fa]==i){
				d[i]+=a[fa];
				fa=p[fa]; 
			} 
		}
	} 
	sort(d+1,d+1+n);
	for(int i=n;i>n-m;i--){
		ans+=d[i];
	}
	printf("%d",ans);
	return 0;
}

一等分数线240,自行参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值