NOIP2018提高组金牌训练营——数论专题
地址
https://www.51nod.com/live/liveDescription.html#!liveId=23
1187 寻找分数
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 10000)
第2 - T + 1行:每行4个数,a,b,c,d,中间用空格分隔。(1 <= a,b,c,d <= 10^9)
输出共T行,对应符合条件的分数。
4
1 3 1 2
2 1 3 1
2 1 4 1
1000 1001 1001 1002
2/5
5/2
3/1
2001/2003
一开始想到二分q,因为q越大显然选择越多(其实是错的)
有了q之后设p=1
可以用lcm把三个式子的分母通分,得到分子
然后可以找在新的分子中存不存在a<p<c 写完后发现过不了样例
然后发现有极少极少的数不满足单调性,大部分都是满足的
但是这样就不能二分了
怎么办??
我直接枚举,非常暴力。
我想着应该会超时
我一交上去
我靠25个点过了22个点
3个点没过是因为WA,最后一个点900多毫秒,卡过去了
其实考试这样的话我就满意了
然后我觉得没过是因为取lcm的时候会爆long long
我就改成了unsigned long long
多过了一个点。
写高精度??
不行,这样就会超时。
所以这就是我这种做法的极限了,25个点能过23个点
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; typedef unsigned long long ull;
ull a, b, c, d;
ull ansp, ansq; ull gcd(ull a, ull b) { return !b ? a : gcd(b, a % b); }
ull lcm(ull a, ull b) { return a / gcd(a, b) * b; } bool check(ull q)
{
ull t = lcm(lcm(b, q), d);
ull p = t / q, ta = t / b * a, tc = t / d * c;
t = ta / p + ;
if(ta < t * p && t * p < tc)
{
ansp = t;
ansq = q;
return true;
}
return false;
} int main()
{
int T;
scanf("%d", &T); while(T--)
{
scanf("%llu%llu%llu%llu", &a, &b, &c, &d); ull t = gcd(a, b);
a /= t; b /= t;
t = gcd(c, d);
c /= t; d /= t; for(register ull ans = ; ; ans++)
if(check(ans))
{
printf("%llu/%llu\n", ansp, ansq);
break;
}
} return ;
}
正解用到了一个叫做类欧几里得算法的东西
很骚……第一次写
形式和欧几里得很像。
对于这道题,大概就是不断的减去整数部分
直到a/b和c/d都不是整数部分,就取倒数
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; typedef long long ll;
ll p, q; void sim_gcd(ll a, ll b, ll c, ll d)
{
if(!a)
{
p = ;
q = d / c + ;
return;
}
else if(a >= b)
{
sim_gcd(a % b, b, c - d * (a / b), d);
p += q * (a / b);
return;
}
else if(c > d)
{
p = q = ;
return;
}
sim_gcd(d, c, b, a);
swap(p, q);
} int main()
{
int T;
scanf("%d", &T); while(T--)
{
ll a, b, c, d;
scanf("%lld%lld%lld%lld", &a, &b, &c, &d);
sim_gcd(a, b, c, d);
printf("%lld/%lld\n", p, q);
} return ;
}
1060 最复杂的数
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 100)
第2 - T + 1行:T个数,表示需要计算的n。(1 <= n <= 10^18)
共T行,每行2个数用空格分开,第1个数是答案,第2个数是约数的数量。
5
1
10
100
1000
10000
1 1
6 4
60 12
840 32
7560 64
反素数,之前写过,这次当作复习了
注意剪枝
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; typedef long long ll;
ll p[] = {, , , , , , , , , , , , , , , , }; //
ll ans_num, ans, n; void dfs(ll now, ll i, ll time, ll num)
{
if(i >= || now > n) return;
if(num > ans_num) { ans_num = num, ans = now; }
else if(num == ans_num && now < ans) ans = now; ll t = p[i];
_for(j, , time)
{
if(now > n / t + ) return; //这个减枝非常重要,不加就TLE,同时乘法变除
dfs(now * t, i + , j, num * (j + ));
t *= p[i];
}
} int main()
{
int T;
scanf("%d", &T); while(T--)
{
scanf("%lld", &n);
ans = n; ans_num = ;
dfs(, , , );
printf("%lld %lld\n", ans, ans_num);
} return ;
}
1179 最大的最大公约数
第1行:一个数N,表示输入正整数的数量。(2 <= N <= 50000)
第2 - N + 1行:每行1个数,对应输入的正整数.(1 <= S[i] <= 1000000)
输出两两之间最大公约数的最大值。
4
9
15
25
16
5
这道题没有做出来,是看题解的。
题解有个非常关键的转换的地方
这道题就是求公约数里面的最大值
不要局限在两两个数之间
所以我们就枚举所有公约数,然后取最大。
非常暴力,但是复杂度是nlogn的
怎么枚举呢
对于当前备选答案d
看d,2d,3d,4d中有没有一个kd是两个数的公约数
输入的时候把约数建立一个桶就好了
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 1e6 + ;
int s[MAXN], n; int main()
{
scanf("%d", &n);
_for(i, , n)
{
int x; scanf("%d", &x);
for(register int i = ; (long long)i * i <= x; i++)
if(x % i == )
{
s[i]++;
if((long long)i * i != x) s[x / i]++;
}
} int ans = ;
REP(i, , MAXN)
{
int t = i, cnt = ;
for(register int j = ; t * j < MAXN; j++)
if(s[t * j] > )
{
ans = max(ans, t);
break;
}
}
printf("%d\n", ans); return ;
}
1434 区间LCM
多组测试数据,第一行一个整数T,表示测试数据数量,1<=T<=5
每组测试数据有相同的结构构成:
每组数据一行一个整数N,1<=N<=1000000。
每组数据一行输出,即M的最小值。
3
1
2
3
2
4
6
这道题依然没做出来……
怎么这么弱……
我思考这道题的时候没有去想多个数的最大公因数和最小公倍数的本质 最小公倍数的本质是每个数的质因子取最大值,最大公因数是取最小值
可以用这个性质来做
对于每一个质数p
算出最大的幂k
然后求tp^k>n最小的tp^k 那么m一定大于等于这个值
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; typedef long long ll;
const int MAXN = 1e6 + ;
bool is_prime[MAXN + ];
vector<int> prime;
ll n, m; void get_prime()
{
memset(is_prime, true, sizeof(is_prime));
is_prime[] = is_prime[] = false;
REP(i, , MAXN)
{
if(is_prime[i]) prime.push_back(i);
REP(j, , prime.size())
{
if(i * prime[j] >= MAXN) break;
is_prime[i * prime[j]] = false;
if(i % prime[j] == ) break;
}
}
} int main()
{
get_prime();
int T;
scanf("%d", &T); while(T--)
{
scanf("%lld", &n); m = ;
if(n == ) { puts(""); continue; }
REP(j, , prime.size())
{
int p = prime[j];
if(p > n) break;
ll k = log(n) / log(p);
ll num = pow(p, k);
ll t = n / num + ;
m = max(m, t * num);
}
printf("%lld\n", m);
} return ;
}
1040 最大公约数之和
1个数N(N <= 10^9)
公约数之和
6
15
这道题依然没做出来,想到了正解的一部分,没有深入去想。
首先gcd(n,i)肯定是n的约数
所以我们考虑n的约数d
这个约数的倍数是kd
我们看一下什么时候gcd(n, kd) = d
这个时候我们要把n变换一下
n = (n / d) * d
即gcd((n / d) * d, kd) = d gcd(n / d, k) = 1
因为n / d >= k
所以就有phi(n/d)个
枚举就好了
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; typedef long long ll; ll euler(ll d)
{
ll res = d;
for(ll i = ; i * i <= d; i++)
if(d % i == )
{
while(d % i == ) d /= i;
res = res * (i - ) / i;
if(d == ) break;
}
if(d > ) res = res * (d - ) / d;
return res;
} int main()
{
ll n, sum = ;
scanf("%lld", &n);
for(ll d = ; d * d <= n; d++)
if(n % d == )
{
sum += d * euler(n / d);
if(d * d != n) sum += (n / d) * euler(d);
}
printf("%lld\n", sum); return ;
}
1225 余数之和
输入1个数N(2 <= N <= 10^12)。
输出F(n) Mod 1000000007的结果。
6
3
很容易推出一个结论
当 i <= p <= n/(n/i)时
n/p的结果是一样的
用这个结果加上等差数列求和就好了
记得2要求逆元
最坑的一点是数据高达10^12
所以两个数一乘就炸
所以要打很多很多的%mod
在这里卡了好久
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; typedef long long ll;
const int mod = ;
ll n, sum; ll power(ll a, int b)
{
ll res = % mod; a %= mod;
for(; b; b >>= )
{
if(b & ) res = res * a % mod;
a = a * a % mod;
}
return res;
} int main()
{
ll t = power((ll), mod - );
scanf("%lld", &n);
sum = (n % mod) * (n % mod) % mod;
for(ll i = ; i <= n;)
{
ll l = i, r = n / (n / i);
sum = (sum - ((n / i) * ((l + r)%mod) % mod * ((r - l + )%mod) % mod * t) % mod + mod) % mod;
i = r + ;
}
printf("%lld\n", sum);
return ;
}
1217 Minimum Modular
第1行:2个数N, K,中间用空格分隔,N表示元素的数量,K为可以移除的数的数量(1 <= N <= 5000, 0 <= K <= 4, 1 <= a[i] <= 1000000)。
输出符合条件的最小的M。
5 1
1
2
10
11
12
4
这道题依然没有做出来,因为一直没有什么思路
正解非常非常精彩!!!!
首先要知道一个结论
p ≡ q (mod M) 当且仅当 M | (p - q) p > q
我就是不知道这个结论(其实这是一个很显然的结论) 那么我们就可以预处理所有数的差值
然后枚举m和m的倍数
看有多少对冲突
如果大于k(k+1)/2,就直接舍去
这个优化很重要,不然会TLE
然后接下来就暴力算出需要删掉多少数
就ok了 其实说到底就是暴力,但是加了一个很牛逼的优化,就大大减少的时间
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 1e6 + ;
int a[MAXN], s[MAXN], b[MAXN];
int n, k; int main()
{
scanf("%d%d", &n, &k);
_for(i, , n) scanf("%d", &a[i]);
_for(i, , n)
_for(j, i + , n)
s[abs(a[i] - a[j])]++; for(int m = n - k; ; m++)
{
int cnt = ;
for(int i = m; i < MAXN; i += m)
cnt += s[i];
if(cnt > k * (k + ) / ) continue; cnt = ;
_for(i, , n)
{
if(b[a[i] % m] == m) { if(++cnt > k) break; }
else b[a[i] % m] = m;
} if(cnt <= k)
{
printf("%d\n", m);
break;
}
} return ;
}
1616 最小集合
A君有一个集合。
这个集合有个神奇的性质。
若X,Y属于该集合,那么X与Y的最大公因数也属于该集合。
但是他忘了这个集合中原先有哪些数字。
不过幸运的是,他记起了其中n个数字。
当然,或许会因为过度紧张,他记起来的数字可能会重复。
他想还原原先的集合。
他知道这是不可能的……
现在他想知道的是,原先这个集合中至少存在多少数。
该集合中一定存在的是{1,2,3,4,6}
第一行一个数n(1<=n<=100000)。
第二行n个数,ai(1<=ai<=1000000,1<=i<=n)。表示A君记起来的数字。
输入的数字可能重复。
输出一行表示至少存在多少种不同的数字。
5
1 3 4 6 6
5
这道题很秀
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std; const int MAXN = 1e6 + ;
int a[MAXN], f[MAXN];
int n, k, maxt; int main()
{
scanf("%d", &n);
_for(i, , n)
{
int x; scanf("%d", &x);
f[x] = ; maxt = max(maxt, x);
} _for(i, , maxt)
for(int j = i * ; j <= maxt; j += i)
if(f[j]) f[i]++; int ans = ;
_for(i, , maxt)
if(f[i])
{
bool ok = true;
for(int j = i * ; j <= maxt; j += i)
if(f[j] == f[i])
{
ok = false;
break;
}
if(ok) ans++;
}
printf("%d\n", ans); return ;
}
总结:
数论最后几道题都没做出来,因为题做得少,思维能力不够和一些结论不知道
不过还是很有收获的。
不要怕打暴力,只要复杂度是对的就行。
很多解法非常暴力但我却直接pass掉了
就像好几道题都是直接暴力枚举n内的数i和i的所有倍数
复杂度是nlogn的
这个时候往往之前做过一些预处理(往往是一个桶)使得对于枚举的每个数可以O(1)的判断
NOIP2018提高组金牌训练营——数论专题的更多相关文章
- NOIP2018提高组金牌训练营——动态规划专题
NOIP2018提高组金牌训练营——动态规划专题 https://www.51nod.com/Live/LiveDescription.html#!#liveId=19 多重背包 二进制优化转化成01 ...
- NOIP2018提高组金牌训练营——搜索专题
NOIP2018提高组金牌训练营——搜索专题 1416 两点 福克斯在玩一款手机解迷游戏,这个游戏叫做”两点”.基础级别的时候是在一个n×m单元上玩的.像这样: 每一个单元有包含一个有色点.我们将用不 ...
- NOIP2018提高组金牌训练营——字符串专题
NOIP2018提高组金牌训练营——字符串专题 1154 回文串划分 有一个字符串S,求S最少可以被划分为多少个回文串. 例如:abbaabaa,有多种划分方式. a|bb|aabaa - 3 个 ...
- [NOIp2018提高组]旅行
[NOIp2018提高组]旅行: 题目大意: 一个\(n(n\le5000)\)个点,\(m(m\le n)\)条边的连通图.可以从任意一个点出发,前往任意一个相邻的未访问的结点,或沿着第一次来这个点 ...
- [NOIp2018提高组]赛道修建
[NOIp2018提高组]赛道修建 题目大意: 给你一棵\(n(n\le5\times10^4)\)个结点的树,从中找出\(m\)个没有公共边的路径,使得第\(m\)长的路径最长.问第\(m\)长的路 ...
- [NOIp2018提高组]货币系统
[NOIp2018提高组]货币系统 题目大意: 有\(n(n\le100)\)种不同的货币,每种货币的面额为\([1,25000]\)之间的一个整数.若两种货币系统能够组合出来的数是相同的的,那我们就 ...
- [NOIp2013提高组]积木大赛/[NOIp2018提高组]铺设道路
[NOIp2013提高组]积木大赛/[NOIp2018提高组]铺设道路 题目大意: 对于长度为\(n(n\le10^5)\)的非负数列\(A\),每次可以选取一个区间\(-1\).问将数列清零至少需要 ...
- NOIP2018提高组省一冲奖班模测训练(六)
NOIP2018提高组省一冲奖班模测训练(六) https://www.51nod.com/Contest/ContestDescription.html#!#contestId=80 20分钟AC掉 ...
- NOIP2018提高组省一冲奖班模测训练(五)
NOIP2018提高组省一冲奖班模测训练(五) http://www.51nod.com/Contest/ContestDescription.html#!#contestId=79 今天有点浪…… ...
随机推荐
- AMPL下载使用
AMPL下载使用 依次执行以下操作 wget https://ampl.com/demo/amplide.linux64.tgz tar xzf amplide.linux64.tgz cd ampl ...
- 循环时自动打开url
'systemutil.Run "C:\Program Files (x86)\HP\QuickTest Professional\samples\flight\app\flight4a.e ...
- Swift入门(四)——可选类型(Optionals)与断言(Assert)
可选类型是什么? 首先看一个问题,Swift中String类型的变量有一个叫做toInt的方法,能够把String类型变量转换为Int类型变量. var stringValue = "5&q ...
- [Android]RecyclerView的简单演示样例
去年google的IO上就展示了一个新的ListView.它就是RecyclerView. 下面是官方的说明,我英语能力有限,只是我大概这么理解:RecyclerView会比ListView更具有拓展 ...
- 本地配置 Redis
1.下载 https://redis.io/ https://github.com/dmajkic/Redis/downloads 2. 2.cmd 运行: 3.切换到另外一个cmd : ok! 关于 ...
- (素数求解)I - Dirichlet's Theorem on Arithmetic Progressions(1.5.5)
Time Limit:1000MS Memory Limit:65536KB 64bit IO Format:%I64d & %I64u Submit cid=1006#sta ...
- MFC C++ 获取外网IP地址
#include <afxinet.h> //GB2312 转换成 Unicode wchar_t* GB2312ToUnicode(const char* szGBString) { U ...
- js 获取手机浏览器类型,修改css文件的class的值
/*========================================= 函数功能:获取浏览器类型 =========================================*/ ...
- [Swift]二进制、八进制、十进制、十六进制之间的转换
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...
- C#读出文本文件内容,遍历数组筛选出 含有汉字对应的拼音字符
情景描述:由于任务需要,现有一用户表数据,用户名 字段 在新增用户时,输入中文和拼音两种,先要区分同时含有中文和拼音字母的用户名.由于数据很多,可以通过一段代码完成查询: 前提:在阅读本文之前可以先了 ...