果然如我所料,今天果然是数学,而且我果然爆掉了。。。
又是非常简单的三道题,然而我却狠狠地爆掉了。。。只得了20分的逊狗。。。
T1 跳跃 Jump
题目描述
总共有 L 个荷叶围成一个圆环,我们将最南边的荷叶开始,逆时针依次对荷叶编号为 0、1、2、…、L-1。青蛙 A、B 初始分别在第 x、y 个荷叶上,它们同时开始沿逆时针跳动,每个单位时间,青蛙 A 会向前跳 n 个荷叶,青蛙 B 会向前跳 m 个荷叶。这样一直下去,问它们是否会在某个时刻跳到同一个荷叶上,如果会则输出需要的最少跳跃次数,如果不会则输出一行 “Impossible”。
输入格式
仅一行,五个整数 x, y, n, m, L,含义如题意所述。
输出格式
输出一行,“Impossible”或一个整数,如题意所述。
样例输入
1 5 3 2 10
样例输出
4
数据范围与约定
对于 20%的数据 x, y, m, n, L<100;
对于 100%的数据 x≠y<2000000000, 0<m,n<2000000000, 0<L<21000000
分析
这道题是不是很眼熟,感觉到处都见到过。。。一顿操作猛如虎,发现这是一道扩展欧几里得的裸题。
我们来分析一下这个问题,首先可以想到这是一个追及问题,所以我们很显然可以运用相对运动的思想他们的相对速度(这里我们保证 n > m) 他们的相对距离
(快的那个需要追上这么长距离)。于是乎我们就可以列出这样一个方程
移项可得
你瞅这个式子是不是长得很像裴蜀定理,所以我们转换一下就可以得到这个式子
。这个式子很显然是正确的,可以思考一下(其实就是你跑了这么久之后的距离相当于是跑了几圈加上你原来的距离差,其实就是上面的等式的翻译,你要是还不懂我就真的没办法了,我的讲解能力有限)
于是我们就可以用扩欧来求解这个问题了。但我们求出来的结果是方程的解,所以我们需要给他乘上一个
以保证我们的解的正确性。但是此时我们仍然不能保证它是最小正整数解,我们需要通过微调来解决这个问题。为了方便表述,我们下面用
来表述。
先不看k,我们可以发现,要想仍成立,则我们需要给 x 加上一个数(当然这个数可以是负的), 给 y 减去这一个数, 可以表述为
,根据乘法分配律,我们很显然可以发现 t 需要满足的条件是
,很显然可以发现
,我们又要保证这个数是整数,我们对a和b进行质因数分解就可以发现,要使t2是整数,t1就应当满足
,于是我们对x加多个t1或者-t1就可以得到这个式子的最小正整数解。而我们如果先对算出了这个式子的x再乘上k,我们就会发现,我们刚才加的就不是t1而是
了,这样我们的加上的数只被包含而未被覆盖。也就是说你求出的x值可能不是最小的。所以如果你的代码写成了这样↓
GcdOfSth = ExGcd(Delv, l, ansx, ansy);
ansx = (ansx % l + l) % l;
...
printf("%d", ansx * (Delx / GcdOfSth))
恭喜你,你就挂掉了。
这里我们应当先对式子乘上k然后再加t1就可以解决这个问题。也就是写成这样↓
GcdOfSth = ExGcd(Delv, l, ansx, ansy);
ansx *= (Delx / GcdOfSth);
ansx = (ansx % (l / GcdOfSth) + (l / GcdOfSth)) % (l / GcdOfSth);
...
printf("%d", ansx);
然而的然而,这还没有结束,我们还需要解决无解的情况,根据扩展欧几里得算法的证明(不知道同学的建议翻阅lyd的《算法竞赛进阶指南》(现在好像停止印刷了?)或者使用百度优先搜索),只有时才有解,但是用你的聪明的小脑袋瓜想一想可以发现,当两只青蛙的速度一样而他们的初始位置不相同时一定无解,但保险起见还是建议你写一下扩欧的无解情况。
代码
下面来帖一下我被同学嘲讽为丑陋至极的代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int inpt()
{
int x = 0, f = 1;
char ch;
for(ch = getchar(); (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
if(ch == '-'){
f = -1;
ch = getchar();
}
do{
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}while(ch >= '0' && ch <= '9');
return x * f;
}
int Stn, Stm, n, m, l;
int Delv, Delx;
int ansx, ansy;//对,乱取的名字
int GcdOfSth;//对,又是乱取的名字
int ExGcd(int a, int b, int &x, int &y)
{
if(b == 0){
x = 1, y = 0;
return a;
}
int gcd = ExGcd(b, a % b, x, y);
int z = x;
x = y;
y = z - y * (a / b);
return gcd;
}
signed main()
{
freopen("jump.in", "r", stdin);
freopen("jump.out", "w", stdout);
Stn = inpt(), Stm = inpt();
n = inpt(), m = inpt();
l = inpt();
if(n < m){//保证n要快一点
swap(n, m);
swap(Stn, Stm);
}
if(n == m && Stn != Stm){//虽然保证了Stn != Stm
printf("Impossible");
return 0;
}
Delv = n - m;
Delx = (Stm - Stn + l) % l;
GcdOfSth = ExGcd(Delv, l, ansx, ansy);
ansx *= (Delx / GcdOfSth);//先乘k
ansx = (ansx % (l / GcdOfSth) + (l / GcdOfSth)) % (l / GcdOfSth);//再加t1
if(Delx % GcdOfSth != 0){//无解
printf("Impossible");
return 0;
}
printf("%I64d", ansx);//机房的机子是32位的,所以用不了lld TAT
fclose(stdin);
fclose(stdout);
return 0;
}
/*
1 5 1000000005 1 1000000007
*/
T2 方程 Equation
题目描述
求解
的正整数解 (x, y) 的数目。
输入格式
第一行为一个整数 n。
输出格式
输出一行一个整数,表示满足上式的正整数解 (x, y) 的数目,答案对 1e9+7 取模。
样例输入
4
样例输出
21
数据范围与约定
对于 20%的数据,n <= 5;
对于 40%的数据,n <= 100;
对于 100%的数据,1 <= n <= 10e6
分析
这道题我考场上没有任何思路。
对,又是杰哥给我讲了以后,我发现这道题极度简单。
我们把这个式子通分再左右交叉相乘可以得到再移项,提出y再把它除过去就可以得到
因为
所以
因此
带入上一个式子里就可以得到
这时我们要保证
就需要k是
的因子。所以y的取法就等于k的取法我们只需要求出其质因子的组合数即可(每一个数可以取0~cnt个(cnt是
中这一个质因子的数量))。
代码
再次贴上我丑陋无比的代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
const int MOD = 1e9 + 7;
int inpt()
{
int x = 0, f = 1;
char ch;
for(ch = getchar(); (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
if(ch == '-'){
f = -1;
ch = getchar();
}
do{
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}while(ch >= '0' && ch <= '9');
return x * f;
}
int n;
int Pri[5005], tot;
bool NotPri[MAXN];
void Eth(int n)//埃氏筛
{
memset(NotPri, false, sizeof(NotPri));
for(int i = 2; i * i <= n; i++){
if(NotPri[i]) continue;
Pri[++tot] = i;
for(int j = i; i * j <= n; j++){
NotPri[i * j] = true;
}
}
}
int c[MAXN];
void Divide(int x)//质因数分解
{//这里也可以使用lyd书里的阶乘分解的方法,会快一点,但这个也够用了
for(int i = 2 ; i <= x; i++){
int ii = i;
for(int j = 1; j <= tot; j++){
int p = Pri[j];
if(ii % p == 0){
while(ii % p == 0){
c[p]++;
ii /= p;
}
}
}
if(ii != 1){
c[ii]++;
}
}
}
int ans = 1;
int main()
{
freopen("equation.in", "r", stdin);
freopen("equation.out", "w", stdout);
n = inpt();
Eth(n);
Divide(n);
for(int i = 1; i <= n; i++){
ans = 1ll * ans * (2 * c[i] + 1) % MOD;//组合
}
printf("%d", ans);
fclose(stdin);
fclose(stdout);
return 0;
}
T3 离散对数 log
题目描述
对于
,找到 x 的最小非负整数解,无解则输出一行 “No solution”
输入格式
第一行一个整数 n,表示有 n 组数据。接下来 n 行,每行三个整数 A, B, P。
输出格式
输出 n 行,每行一个整数表示答案,或“No solution”表示找不到正整数解。
样例输入
3
2 1 5
2 3 5
3 3 5
样例输出
0
3
1
数据范围与约定
对于前 20%的数据,保证 P<=1000;
对于前 70%的数据,保证 n<=5;
对于 100%的数据,保证 1<=n<=200,1<=A, B<P,2<=P<
且为质数。
分析
这显然是一道Baby Step, Giant Step算法的裸题。
不会的朋友可以查阅lyd的书或者用百度优先搜索。
这里就只浅浅讲一下,不做赘述。
我们不难发现的值是具有周期性的,最小正周期不超过P。
所以我们可以想到朴素算法就是枚举1~P次方,验证其是否满足。
但是显然这样会导致时间爆炸,于是我们运用BSGS算法,也就是对P进行分块,为了保证复杂度在的范围内,所以我们把它分成
块,每块长
。我们令
则原式可表示为
,由乘法原理可以得到
定义域同上,于是我们可以先枚举
,将 j 以
为关键字存到哈希表中然后再枚举每一个
(注意此时m是定值),在哈希表中查找对应的值,若查找到,则原方程有解为
,反之无解。
代码
依旧是丑陋之极
用map写的话会挂掉30分
#include<bits/stdc++.h>//题意改成非负
using namespace std;
int inpt()
{
int x = 0, f = 1;
char ch;
for(ch = getchar(); (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
if(ch == '-'){
f = -1;
ch = getchar();
}
do{
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}while(ch >= '0' && ch <= '9');
return x * f;
}
int n;
int A, B, P;
map<long long, int> mp;
int BSGS(int a, int b, int p)
{
mp.clear();//清空,一定要清空,别问我怎么知道的,血和泪的教训
b %= p;//这句话完全是废话
if(b == 1){//如果b = 1那么a的0次方就可以满足
return 0;
}
int m = (int)sqrt(p);//分块以降低复杂度
long long num = 1;
for(int i = 0; i < m; i++){
// if(!mp[1ll * b * num % p]){//注释掉的这里是受到了sxk学长的影响(他是这么写的?)
mp[1ll * b * num % p] = i;//利用哈希技巧(map)
// }
num = 1ll * num * a % p;
}
long long val = num;
for(int i = 1; i <= m + 1; i++){
if(mp[val % p]) return i * m - mp[val % p];//找到解
val = 1ll * val * num % p;
}
return -1;//无解
}
int main()
{
// freopen("log.in", "r", stdin);
// freopen("log.out", "w", stdout);
n = inpt();
while(n--){
A = inpt(), B = inpt(), P = inpt();
int ans = BSGS(A, B, P);
if(ans != -1){
printf("%d\n", ans);
}else{
printf("No solution\n");
}
}
fclose(stdin);
fclose(stdout);
return 0;
}
/*
3
2 1 5
2 3 5
3 3 5
*/
这道题用哈希表做才能够得到满分,这里我选用的是一种比较简单粗暴的哈希做法
#include<bits/stdc++.h>//题意改成非负
using namespace std;
const int MAXN = 1e5 + 7;
const int MOD = 1e5 - 3;
const int bas = 233;
int inpt()
{
int x = 0, f = 1;
char ch;
for(ch = getchar(); (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
if(ch == '-'){
f = -1;
ch = getchar();
}
do{
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}while(ch >= '0' && ch <= '9');
return x * f;
}
int n;
int A, B, P;
struct MAP{
int val;
int cnt;
int id;
}mp[MAXN];//这里如果开的太大的话会导致时间爆炸,我也不知道为啥
int Find(int x)//所以这里开够用就行
{
int val = x % MOD;
while(mp[val].cnt && mp[val].val != x){
val += bas;//如果哈希表中这一位被占用且不是这个数则向后走bas位再查找
if(val > MOD){//只要你的数组够大就不会冲突
val -= MOD;
}
}
return val;
}
//map<long long, int> mp;
int BSGS(int a, int b, int p)//这里就是把上一篇的map改成hash就行了
{
// mp.clear();
memset(mp, 0, sizeof(mp));
b %= p;
if(b == 1){
return 0;
}
int m = (int)sqrt(p);
long long num = 1;
for(int i = 0; i < m; i++){
int qwq = Find(1ll * b * num % p);
mp[qwq].val = 1ll * b * num % p;
mp[qwq].id = i;
mp[qwq].cnt++;
num = 1ll * num * a % p;
}
long long val = num;
for(int i = 1; i <= m + 1; i++){
if(mp[Find(val % p)].val) return i * m - mp[Find(val % p)].id;
val = 1ll * val * num % p;
}
return -1;
}
int main()
{
// freopen("log.in", "r", stdin);
// freopen("log.out", "w", stdout);
n = inpt();
while(n--){
A = inpt(), B = inpt(), P = inpt();
int ans = BSGS(A, B, P);
if(ans != -1){
printf("%d\n", ans);
}else{
printf("No solution\n");
}
}
fclose(stdin);
fclose(stdout);
return 0;
}
/*
3
2 1 5
2 3 5
3 3 5
*/
今天果然悲伤,明天还是数学,心情复杂。。。
有错欢迎指正?