2020合肥市信息学竞赛试卷(初中组)
1.小C的数学(math)
小C想要成为一名OIer,于是他提前学习数学,为OI做好铺垫。这一天,他的数学老师给了一道题:给定正整数a,以及给定一个区间[b,c],其中b,c均为整数(b,c保证非负)。寻找所有合法的x,满足b≤x≤c,并且a能够整除x,即x除以a的余数为0。
可小C很懒,不想找出来所有的解,他只想知道这样的x有多少个。
输入格式
共一行,依次三个整数a,b,c,如题目所描述。
输出格式
仅一行一个数,表示答案。
输入样例1
2 3 6
输出样例1
2
样例解释1
x可以为4,6,总共有2个。
输入样例2
3 1 7
输出样例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。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
long long a,b,c,ans;
cin>>a>>b>>c;
if(b==0) ans=c/a+1;
else ans=c/a-(b-1)/a;
cout<<ans;
return 0;
}
2.小C的数组(array)
题目描述
小C终于成为一名萌新OIer,最近他在学习数组。小C要练习数组。一次,小C得到了一个长度为n的数组a。现在,对于每一个下标小C想找出比i小且距离i最近的下标j,使得满足aifa;,如果不存在,则j=0。记下标i对应的答案f=j,小C为了确保自己的程序正确,想让你来检查f数可你不能告诉他整个答案,你只需要告诉他f数组所有元素的和即可。
输入格式
共两行,第一行一个正整数n,表示数组长度;第二行n个正整数,第i个表示ai。
输出格式
仅一行一个数,表示f数组所有元素的和。
输入样例1
6
1 1 2 3 2 1
输出样例1
14
样例解释1
f依次为(0,0,2,3,4,5),总和为14。
输入样例2
12
3 3 3 3 2 2 2 2 4 4 1 1
输出样例2
52
样例解释2
f依次为(0,0,0,0,4,4,4,4,8,8,10,10),总和为52。
数据范围
对于40%的数据:n≤1000,1≤ai≤10,且保证数据随机;
对于70%的数据:n≤10^6,1≤ai≤10,且保证数据随机;
对于90%的数据:n≤10^6,1≤ai≤10;
对于100%的数据:n≤10^6,1≤ai≤1000。
随机数据的生成方式如下:对于每一个ai,等概率地从1到10中产生。
#include <iostream>
#include <cstdio>
using namespace std;
const int N=1000005;
int n,a[N];
int main()
{
scanf("%d",&n);
long long ans=0;
for(int i=1,j=0;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]!=a[i-1]) j=i-1;
ans+=j;
}
printf("%lld\n",ans);
return 0;
}
3.小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整除,两者至少一种成立。
输入格式
共两行,第一行有一个正整数n,表示数列A的长度;第二行有n个正整数,第i个数表示ai。
输出格式
仅一行一个数,表示子序列长度的最大值。
输入样例1
5
1 2 3 8 16
输出样例1
4
样例解释1
最长子序列为{1,2,8,16},长度为4。
输入样例2
10
1 4 6 3 9 11 16 24 42 36
输出样例2
4
样例解释2
最长长度为4,选取方案不唯一,其中一种最长的子序列为{1,6,3,24}。
数据范围
对于所有数据:0<N≤3×10^6,10<ai≤10^7。
如有需要,请使用scanf 读入以及减少使用STL,以降低程序本身带来的常数。
在原序列中选择最长子序列,满足子序列中任意两个元素之间存在倍数关系。
算法:动态规划
状态:f[v]表示V在序列中出现时的最长子序列长度。
状态转移:f[v]=max(f[v*2],f[v*3],f[v*4]...f[v*k])v*k<=maxv
递推的方向:从最大值(maxv)向最小值(1)推
初值:f[maxv]=cnt(maxv)cnt就是maxv出现的次数
//动规 80分解法
#include <iostream>
#include <cstdio>
using namespace std;
const int N=1000005;
int n,f[N],cnt[N];
int main()
{
scanf("%d",&n);
int maxv=0,x;
for(int i=1;i<=n;i++){
scanf("%d",&x);
maxv=max(maxv,x);
cnt[x]++;
}
f[maxv]=cnt[maxv];
int ans=f[maxv];
for(int i=maxv-1;i>=1;i--){
if(cnt[i]==0) continue;
f[i]=cnt[i];
for(int k=2;k*i<=maxv;k++){
f[i]=max(f[i],cnt[i]+f[k*i]);
ans=max(ans,f[i]);
}
}
printf("%d\n",ans);
return 0;
}
//埃氏筛 100分解法
#include <iostream>
#include <cstdio>
using namespace std;
const int N=1000005;
int n,f[N],cnt[N];
bool isprimes[N];
int primes[N],pn;
void fastPrimes(int n){//线性筛
isprimes[0]=isprimes[1]=true;
for(int i=2;i<=n;i++){
if(!isprimes[i]) primes[++pn]=i;
for(int j=1;i*primes[j]<=n;j++){
isprimes[i*primes[j]]=true;
if(i%primes[j]==0) break;
}
}
}
int main()
{
scanf("%d",&n);
int maxv=0,x;
for(int i=1;i<=n;i++){
scanf("%d",&x);
maxv=max(maxv,x);
cnt[x]++;
}
fastPrimes(maxv);
f[maxv]=cnt[maxv];
int ans=f[maxv];
for(int i=maxv-1;i>=1;i--){
//if(cnt[i]==0) continue;
f[i]=cnt[i];
for(int j=1;j<=pn&&primes[j]*i<=maxv;j++){
f[i]=max(f[i],cnt[i]+f[primes[j]*i]);
ans=max(ans,f[i]);
}
}
printf("%d\n",ans);
return 0;
}
4.小C的树(tree)
题目描述
定义:树是一个有n个点n-1条边的无向连通图,任意两个点间恰有一条简单路径。最未端仅有一条边相连的节点称为叶子。小C家有一个后院,院子的中央种有一棵树。小C喜欢在雨后初睛时到院子里观察。
这棵参天大树可以抽象成一棵n个节点的树,节点从1到n编号,树的根在1号节点。第i个节点有一个正整数a;表示该处的美感值。
小C尤其喜欢观察蚂蚁。这天,小C捉来了m只蚂蚁。他想挑选树上的m个节点,每个节点放上一只蚂蚁。小C知道蚂蚁在树上时,会一直朝着根的方向移动,中间会经过一个个节点,最后到达根节点,之后,蚂蚁便到达了地面。
热爱自然的小C想知道这m只蚂蚁经过的节点的美感值和的最大值是多少,于是请你来帮帮忙。注意经过的节点包括初始节点,且多次访问的节点仅算入一次。
输入格式
共三行,第一行有两个正整数n,m,表示树的大小和选取的节点数;第二行有n个非负整数,第i个数表示ai;第三行有n-1个正整数,第i个数f,表示节点i+1的父亲节点。保证0<fi≤i。
输出格式
仅一行一个数,表示美感值和的最大值。
输入样例1
5 2
1 2 3 1 3
1 1 2 2
输出样例1
9
样例解释1
这棵树的形态如下图:
选择{3,5}两个节点,则经过的节点集合为{1,2,3,5}。
红色数字为每个节点的美感值,故美感值和为1+2+3+3=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
样例解释2
选取{4,5,10}三个节点,得到的美感值和为24,方案可能不唯一。
数据范围
对于所有数据:0<m≤n≤5×10^6,0≤ai≤5。
由于每个节点仅能被访问一次,每个节点仅能属于一条链路。
链路:从叶子节点到根节点的一条路径。
问题转换:
给定一棵树,要求分解成若干条互不相交的链路,求在这么链路中选择m条链路的和的最大值。(贪心算法)
解题框架
1.将树分解为多条链 a.dfs
2.对链从小到大排序 a.sort
3.取前m个链,求和 a.遍历
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=5000010;
int h[N],w[N],e[2*N],ne[2*N],idx;
int n,m,sum[N],son[N],s[N],k;
bool st[N];
void add(int a,int b){//链式前向星
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
//树链剖分
void dfs(int u,int father){//u表示当前节点father父节点
sum[u]=w[u];//sum保存以u为根节点的重链路的和
for(int i=h[u];~i;i=ne[i]){//遍历所有的子节点
int j=e[i];
if(j==father) continue;
dfs(j,u);
if(sum[son[u]]<sum[j]){
sum[u]=sum[j]+w[u];
st[son[u]]=false;
son[u]=j;st[j]=true;
}
}
}
int main()
{
scanf("%d %d",&n,&m);
memset(h,-1,sizeof(h));
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=2;i<=n;i++){
int j;scanf("%d",&j);
add(i,j);add(j,i);
}
dfs(1,-1);
//将所有的轻节点和全部放到s数组
for(int i=1;i<=n;i++){
if(st[i]==false) s[++k]=sum[i];
}
sort(s+1,s+k+1,greater<int>());//从大到小排序
int ans=0;
for(int i=1;i<=m&&i<=k;i++) ans+=s[i];
printf("%d\n",ans);
return 0;
}