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 数组是一个下标的和数组,那么最大就是当每两个相邻数字都不相同时最大,,即 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,自行参考。