【指指点点】你的题做完了吗
《要求》 今天听我讲课的学弟学妹们给我点个赞【强制性任务】
好的那咱们现在开始讲题
请听题!
A.线性筛素数
题目概述
给定一个范围 n,有 q 个询问,每次输出第 k 小的素数。
输入:
第一行包含两个正整数 n,q,分别表示查询的范围和查询的个数。
接下来 q 行每行一个正整数 k,表示查询第 k 小的素数。
输出:
输出 q 行,每行一个正整数表示答案。
解析
感觉他在题目背景上提示了需要用std::ios::sync_with_stdio(0)来加速,所以理论上大概率是要卡时间的,直接上理论上最快方法欧拉筛。
本着作死实践的精神,我试了一下cin,确实有一个超时,但这不是更搞笑的,最搞笑的是同样的代码我提交的第二次就过了[我只是想起来第一遍没截图所以才尝试的第二遍]
最好笑的是我想探究这个原因的时候,我换了一个账号提交,交了四次,都AC了,这好像在嘲笑我第一遍交的手气就是个非酋????今天手气太差了抽卡只有小保底,1:100的时间限制也被我刷出来了
【汗流浃背了】这并不鼓励大家比赛的时候去赌这个概率
代码
#include <cstdio>
#include<iostream>
#include <cstring>
using namespace std;
bool visit[100000010];//1表示素数,根据数据范围,这个设的尽可能大n<=1e8
int Prime[100000010];
int sum = 0;//计数
void E(int n)
{
memset(visit, 1, sizeof(visit));//初始状态全部设为1,即全部都是素数
visit[1] = 0;//1不是素数
for (int i = 2; i <= n; i++)
{
if (visit[i])
Prime[++sum] = i;
for (int j = 1; j <= sum&& i * Prime[j] <= n; j++)//i * Prime[j] <= n;不越界/不多算
{
visit[i * Prime[j]] = 0;
if (i % Prime[j] == 0)//欧拉筛的关键判断及其跳出
break;
}
}
}
int main()
{
ios::sync_with_stdio(0);//加这个是为了加速,当时我没加也过了
int n, q;
cin >> n >> q;
E(n);//一次运算 全部标注
while (q--)
{
int k;
cin>>k;
cout<<Prime[k]<<endl;
}
return 0;
}
纯属模板题,不会写的去给我背模板
讲完课的第八十分钟,只有十五个人写出来了o(TヘTo)
B.数字游戏
题目概述:
解析
翻译一下:
上帝给出数字Q,K为先手,以Q的因数代替Q,再由C操作,规定没有数字写写出的玩家胜利
K:6->2 && 3,用3代替或者是用2代替本质上是一样的,因为都是素数
C:2->选0,C胜利
本质上是博弈论+求素数
所以这道题本质上针对Q的输入,分为三种情况:
1、Q为质数【若是第一次就无法写出数字,则认为第一次写出的可以制胜的最小数字为 0】这种情况下输出0
2、Q为两个质数的乘积=Q仅有两个因子,这样先手方必败
3、Q为多个(n>2)个指数的乘积=Q有多个因子,那我们努力让先手(我方)在状态2的情况下,让后手(对方)得到状态2的情况,这样才是我放的必胜策略
注意!
所以需要开long long
代码
#include<iostream>
#include<stdio.h>
using namespace std;
int main()
{
long long a[1000005];
long long ans = 0;
long long Q;
cin >> Q;
for (long long i = 2; i * i <= Q; i++)
while (Q % i == 0)//纯暴力分解质因子,貌似对速度要求不大也,毕竟不是循环输入,而是单次的
{
a[++ans] = i;
Q /= i;
}
if (Q != 1)
a[++ans] = Q;
if (ans == 1)
{
cout << "1" << endl;
cout << 0 << endl;
}
else if (ans == 2)
cout << "2";
else
cout << "1" << endl << a[1] * a[2];
return 0;
}
C.组合数问题
题目概述:
解析
当我拿到这个题的时候汗流浃背了,上课忘记讲杨辉三角了
不慌,不会杨辉三角也能过
杨辉三角
杨辉三角的输出:
//使第一列和对角线元素的值为1
for (i=1;i<n;i++)//前两行全为1,拿出来单独处理
{
a[i][i]=1;//使最右侧边全为1
a[i][1]=1;//使最左侧边全为1
}
//从第三行开始处理
for (i=3;i<n;i++) //三行开始出现变化
for (j=2;j<=i-1;j++)
a[i][j]=a[i-1][j-1]+a[i-1][j];//每个数等于它上方两数之和,如a32=a21+a22
所以在本题中,c(i,j)的值就是第i行第j列
代码
#include <iostream>
#include <cstring>
using namespace std;
int t, k, n, m;
int C[2005][2005], s[2005][2005];
void Y()
{
C[1][1] = 1;
for (int i = 0; i <= 2000; i++)
C[i][0] = 1;
for (int i = 2; i <= 2000; i++)
{
for (int j = 1; j <= i; j++)
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % k;
}
for (int i = 2; i <= 2000; i++)
{
for (int j = 1; j <= i; j++)
{
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
if (C[i][j] == 0)
s[i][j] += 1;
}
s[i][i + 1] = s[i][i];
}
}
int main()
{
memset(C, 0, sizeof(C));
memset(s, 0, sizeof(s));
cin >> t >> k;
Y();
while (t--)
{
cin >> n >> m;
if (m > n) m = n;
cout << s[n][m] << endl;
}
return 0;
}
这个题第一遍提交的时候CE了,居然不算惩罚时长【可喜可贺】
D.排列排序
这就是一道排序,和排列数和今天上午讲的课没有任何关系
题目概述
解析:
区间是变动的,换句话说,区间两端都是变动的,所以可以分别指向区间两端……🤔设置两个数i和j,i指向区间左端点,j指向区间右端点,然后开始进行枚举。
这里有一个让题目变得简单的条件,就是说【直到 p 中元素的值从 1 到 n 按升序排序】,也就是说,在正常排序的情况下,这个位置的下标值应该和元素值相同在。换句话说,元素值与下标值相同就代表元素处于正确的位置,那么i+=1;
那么元素值与下标值不同的时候,就代表着此时该元素处于不正确的位置上,此时i指向的数字,作为排序区间的左端点,此时右指针初始化为j=i+1,开始进行j++直到【判断_此刻>=区间最大值】①,此时j作为排序的右端点。此时区间长度j-i+1
之后再有 i=j+1重复上述过程
①为啥是区间最大值呢?
比如区间是[2,4],长度为3,该区间内有一个数字5,是区间最大值,此刻区间右端点(4)还没有大于等于5,此刻区间还没有到最大值对应的位置,需要更进一步,让区间右端点再往后移动。
代码:
#include <iostream>
#include <algorithm>
using namespace std;
int a[1000005];
int main()
{
int t;
cin >> t;
while (t--)
{
int n;
int sum = 0;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
int i = 1;
while (i <= n)
{
if (a[i] == i)
i++;
else
{
int mmax = a[i];
int j = i + 1;
mmax = max(mmax, a[j]);
while (mmax > j)
{
j++;
mmax = max(mmax, a[j]);
}
sum += j - i + 1;
i = j + 1;
}
}
cout << sum << endl;
}
return 0;
}
E.最小公倍数之和
题目概述
解析:
……今下午我和你们一块做的题,结果被一杀,是我低估,非常佩服拿了这道题一血的那位同学,感兴趣的话可以分享一下你的代码,如果你愿意的话我可以贴在这道题下面。同时欢迎所有做出来任意一道题的同学,都可以投稿你的想法。
当我被WA的时候我震惊了,质疑自己,谁能想到呢自以为看懂了题目
仔细看看,更加完蛋,没讲莫比乌斯反演那这道题就有点尴尬了,我们简单说说,毕竟莫比乌斯八百年不考一次,天梯赛肯定不考,大家可以跳过,感兴趣的同学可以继续看看。
最后是为了防止你们提前AK掉
整体来讲先枚举公因数做一下预处理,然后求解∑u(x)*x,最后暴力一下
只能说这题是纯数论,确实有很多知识点是咱们没有学过的,能看得懂就看懂了,看不懂就算了去做别的题吧,一般来讲国内性质的比赛很少比这样难的。咱鼓励不会的题该放弃放弃
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const long long maxn = 50010;
long long n, A[maxn], m, a[maxn], f[maxn], ans, tot;
long long cur, pri[maxn], mu[maxn];
bool tf[maxn];
main()
{
cin >> n;
for (long long i = 1; i <= n; i++)
{
scanf("%lld", A + i);
a[A[i]]++;
m = 50000;
}
for (long long i = 1; i <= m; i++)
for (long long j = i; j <= m; j += i)
f[i] += a[j] * j;
for (long long i = 1; i <= m; i++)
f[i] = f[i] * f[i];
mu[1] = 1;
for (long long i = 2; i <= m; i++)
{
if (!tf[i])
{
pri[++cur] = i;
mu[i] = -1;
}
for (long long j = 1; j <= cur && pri[j] <= m / i; j++)
{
tf[i * pri[j]] = true;
if (i % pri[j] == 0)
{
mu[i * pri[j]] = 0;
break;
}
else
mu[i * pri[j]] = -mu[i];
}
}
for (long long d = 1; d <= m; d++)
{
tot = 0;
for (long long g = 1; g <= m / d; g++)tot += mu[g] * f[g * d];
ans += tot / d;
}
cout << ans;
return 0;
}
小结
最后总结一下,本次题目里面:
第一题是模板题,一小时内做出来才算是及格线【勉强】
第二题是博弈论,很大程度上是一个数学敏感度,这个确实是有灵感这么一说,所以当作长经验值了。
第三题比较难但还在可以接受的范围内,大家熟悉一下杨辉三角,基本的推导公式:【每个数等于它上方两数之和】可能会出现考题or找规律的题找出来就是杨辉三角
第四题也比较简单,一个思维上的变通?常规题目了算是,应该做出来的。如果觉得自己肯定作对但是最后WA的可以看一下是不是数组没有开的足够大或者没有开对
第五题属于不用看了,熟悉一下最大公因数和最小公倍数的计算代码吧
and then 大家还可以看一下今天ppt里面提到的题目,更贴合今天的讲课内容。
最后给一下求最小公约数的代码(完整版)虽然和今天的题没有一点关系
#include<iostream>
#include<vector>
using namespace std;
//求最大公约数
int gcb(int a, int b)
{
int c = 0;
while (c = a % b)
{
a = b;
b = c;
}
return b;
}
int lcm(int a, int b)
{
return a * b / gcb(a, b);
}