• Post author:
  • Post category:其他

目录

一,阶

二,OJ实战

HDU 1210 Eddy’s 洗牌问题

力扣 1806. 还原排列的最少操作步数

CSU 1755 阶数

POJ 3696 The Luckiest number


一,阶

({a}, {m})=1,  则存在 j 满足{a}^{j} \equiv 1 \quad(\bmod {m}),其中满足条件的最小j叫a模m的指数,也叫阶(Multiplicative order)

数论_nameofcsdn的博客-CSDN博客

阶,也叫阶数、指数、秩,虽然代数里面秩的单词貌似是rank,但是数论这个秩是翻译成order的。

代码:

	class Order :public Phi, public NumberTheory, public Multi {
	public:
		//求a mod n的阶,-1表示不存在
		static int getOrder(int a, int n)
		{
			if (gcd(a, n) != 1)return -1;
			return getOrder(a, n, getPhi(n));
		}
	private:
		//求a mod n的阶,一定是upPie的约数
		static int getOrder(int a, int n, int upPie)
		{
			vector<int> v = getFacs(upPie);
			for (auto vi : v) {
				while (upPie % vi == 0 && multiMulti(a, upPie / vi, n) == 1)upPie /= vi;
			}
			return upPie;
		}
	};

下面讲一下求阶的思路。

首先,如果gcd是1,那么就有阶,否则,就没有阶。

然后,欧拉定理+阶的性质

欧拉定理:如果gcd(a,n)=1那么a^phi(n)≡1 (mod n)

阶的性质:如果a^k≡1 (mod n),那么order | k

所以,只需要先求出phi(n),然后传给getOrder(int a, int n, int upPie)就可以求出阶了。

也就是说,它的的功能是,已知a mod n的阶是upPie的约数,求阶。

(pie不是圆周率,而是乘积的意思)

之所以指出这个,是因为和求阶有关。

最后,求阶

可以遍历upPie的所有约数,从而判断哪一个是阶。

当然,其实不需要遍历所有的约数。

不管是按大小顺序枚举约数,还是根据解空间的结构定义序关系进行查找,都可以,只是后者的效率自然高一些。

本题的解空间是素数无限维空间的子空间的一部分。

素数无限维空间指的是以所有素因子为基,以积为运算,得到的无限维空间。

本质都是一样的,就是找满足a^k≡1 (mod n)的最小k (也就是阶的定义)

这里,因为k可以很大,所以需要函数multiMulti计算快速幂。

假设upPie=p1^a1 * p2^a2 *……* pl^al

那么枚举约数需要调用(a1+1)*(a2+1)*……*(al+1)次multiMulti函数

但是,根据序关系进行查找,只需要a1+a2+a3+……+al次,有了大幅度提高。

具体如何定义序关系,还是根据上面提到的阶的性质,值得细细品味。

二,OJ实战

HDU 1210 Eddy’s 洗牌问题

题目:

Eddy是个ACMer,他不仅喜欢做ACM题,而且对于纸牌也有一定的研究,他在无聊时研究发现,如果他有2N张牌,编号为1,2,3..n,n+1,..2n。这也是最初的牌的顺序。通过一次洗牌可以把牌的序列变为n+1,1,n+2,2,n+3,3,n+4,4..2n,n。那么可以证明,对于任意自然数N,都可以在经过M次洗牌后第一次重新得到初始的顺序。编程对于小于100000的自然数N,求出M的值。 
Input
每行一个整数N
Output
输出与之对应的M
Sample Input
20
1
Sample Output
20
2

编号为i的牌,m次洗牌之后编号为i*2^m (mod 2n+1)

所以这个题目的答案,其实就是2 mod 2n+1的阶

关于求阶,可以参考CSU – 1755 阶数 

不过这个题目可以偷个懒

代码:
 

#include<iostream>
using namespace std;

......

int main()
{
	int n;
	while (cin >> n)cout << GetOrder(2, n * 2 + 1) << endl;
	return 0;
}

力扣 1806. 还原排列的最少操作步数

给你一个偶数 n​​​​​​ ,已知存在一个长度为 n 的排列 perm ,其中 perm[i] == i​(下标 从 0 开始 计数)。

一步操作中,你将创建一个新数组 arr ,对于每个 i :

如果 i % 2 == 0 ,那么 arr[i] = perm[i / 2]
如果 i % 2 == 1 ,那么 arr[i] = perm[n / 2 + (i – 1) / 2]
然后将 arr​​ 赋值​​给 perm 。

要想使 perm 回到排列初始值,至少需要执行多少步操作?返回最小的 非零 操作步数。

示例 1:

输入:n = 2
输出:1
解释:最初,perm = [0,1]
第 1 步操作后,perm = [0,1]
所以,仅需执行 1 步操作
示例 2:

输入:n = 4
输出:2
解释:最初,perm = [0,1,2,3]
第 1 步操作后,perm = [0,2,1,3]
第 2 步操作后,perm = [0,1,2,3]
所以,仅需执行 2 步操作
示例 3:

输入:n = 6
输出:4
 

提示:

2 <= n <= 1000
n​​​​​​ 是一个偶数

class Solution {
public:
    int reinitializePermutation(int n) {
        if(n==2)return 1;
        return GetOrder(2, n  - 1);
    }
};

CSU 1755 阶数

题目:
Description

满足a^x≡1(mod n)的最小正整数x称为a模n的阶。

现给出两个正整数,求x。

Input

第一行输入k,表示有k组数据

之后k行每行两个数a,n(2<=a,n<=10^9)

Output

对于每组输入,用一行输出x的值,若不存在输出-1

Sample Input

2
2 3
2 4
Sample Output

2
-1

代码:

#include<iostream>
using namespace std;

int maxp(int n)
{
	int r = n;
	for (int i = 2; i*i <= n; i++)
	{
		if (n%i == 0)
		{
			r = i;
			while (n%i == 0)n /= i;
		}
	}
	if (n > 1)r = n;
	return r;
}


bool prime(int n)
{
	if (n == 2)return true;
	if (n % 2 == 0)return false;
	for (int i = 3; i*i <= n; i++)if (n%i == 0)return false;
	return true;
}


long long mi(int a, int k, int n)
{
	if (k == 0)return 1;
	long long  r = mi(a, k / 2, n);
	r = r*r%n;
	if (k % 2)r = r*a;
	return r%n;
}

int order(int a, int n, int upie)
{
	for (int i = 2; i*i <= upie; i++)
	{
		if (!prime(i))continue;
		while (upie%i == 0)
		{
			if (mi(a, upie / i, n) != 1)break;
			upie /= i;
		}
	}
	int r = maxp(upie);
	if (mi(a, upie / r, n) == 1)upie /= r;
	return upie;
}

int gcd(int m, int n)
{
	if (n == 0)return m;
	return gcd(n, m%n);
}

int phi(int n)
{
	int r = n;
	for (int i = 2; i*i <= n; i++)
	{
		if ( n%i == 0)
		{
			while (n%i == 0)n /= i;
			r = r / i*(i - 1);
		}
	}
	if (n > 1)r = r / n*(n - 1);
	return r;
}

int main()
{
	int k, a, n;
	scanf("%d", &k);
	while (k--)
	{
		scanf("%d%d", &a, &n);
		if (gcd(a, n) != 1)printf("-1\n");
		else printf("%d\n", order(a, n, phi(n)));
	}
	return 0;
}

POJ 3696 The Luckiest number

题目:

Description

Chinese people think of ‘8’ as the lucky digit. Bob also likes digit ‘8’. Moreover, Bob has his own lucky number L. Now he wants to construct his luckiest number which is the minimum among all positive integers that are a multiple of L and consist of only digit ‘8’.

Input

The input consists of multiple test cases. Each test case contains exactly one line containing L(1 ≤ L ≤ 2,000,000,000).

The last test case is followed by a line containing a zero.

Output

For each test case, print a line containing the test case number( beginning with 1) followed by a integer which is the length of Bob’s luckiest number. If Bob can’t construct his luckiest number, print a zero.

Sample Input

8
11
16
0
Sample Output

Case 1: 1
Case 2: 2
Case 3: 0

题意:全部由8组成的数成为幸运数,即8、88、888、8888……

对于输入的任何n,求出能被n整除的最小幸运数,如果不存在则输出0

首先,判断n是否为16的倍数,如果n是16的倍数,那么不存在,输出0(这个判断由函数f里面的第一行完成)

如果n不是16的倍数,那么可以去掉n里面的所有的2,变成奇数(这个操作由函数f里面的第二行递归完成)

即对于任何奇数m,输出m,2m,4m,8m,得到的答案都是一样的。

现在确定n是奇数,那么可以把所有的幸运数都除以8,变成1,11,111,1111……通项为(10^k-1)/9

现在问题转化为,对于任何奇数n,输出满足n|(10^k-1)/9的最小的k,即9*n|10^k-1,如果不存在k就输出0

如果n是5的倍数,那么k不存在,输出0,

如果n不是5的倍数,那么k一定存在,满足9*n|10^k-1的最小的k,在数论中叫10模9*n的阶

到这里,问题其实就已经解决了,直接求出阶就可以了。

但是我还用了一个技巧,这样就避免了long long的使用,因为9*n超过了int的范围

不仅仅是避免了long long的使用,主要是效率变高了。

当然了,如果要提高效率的话,还可以用筛法对素数打表,而不是用prime函数判断。

下面说明这个技巧:

第一步,求最小的J,使得n|(10^J-1),即求10模n的阶

第二步,求最小的k,使得9*n|(10^k-1)

因为n|(10^k-1)是成立的,所以k一定是J的倍数(这是阶的最基本的性质)

假设3^a||n,即3^(a+2)||9*n,那么满足9*n|(10^k-1)的最小的k,其实就是满足3^(a+2)|(10^k-1)且J|k的最小的k

假设3^c||(10^J-1),根据第一步的结果以及3^a||n,c>=a

关于符号||参见数论中的归纳法——对指数归纳 数论中的归纳法——对指数归纳_nameofcsdn的博客-CSDN博客

根据这篇文章中的定理三:对于正整数b,3ǂb,非负整数a,3^(a+2) || (10^b)^(3^a)-1

可以得到,满足3^(a+2)|(10^k-1)且J|k的最小的k,其实就是满足3^a|k且J|k的最小正整数k

根据定理三还可以得到,因为3^a|(10^J-1),所以3^a|J*9

所以满足3^a|k且J|k的最小正整数k只有3种情况:J、J*3、J*9

稍微判断一下即可。

代码:
 

#include<iostream>
using namespace std;

int maxp(int n)
{
	int r = n;
	for (int i = 2; i*i <= n; i++)
	{
		if (n%i == 0)
		{
			r = i;
			while (n%i == 0)n /= i;
		}
	}
	if (n > 1)r = n;
	return r;
}


bool prime(int n)
{
	if (n == 2)return true;
	if (n % 2 == 0)return false;
	for (int i = 3; i*i <= n; i++)if (n%i == 0)return false;
	return true;
}


long long mi(int a, int k, int n)
{
	if (k == 0)return 1;
	long long  r = mi(a, k / 2, n);
	r = r*r%n;
	if (k % 2)r = r*a;
	return r%n;
}

int order(int a, int n, int upie)
{
	for (int i = 2; i*i <= upie; i++)
	{
		if (!prime(i))continue;
		while (upie%i == 0)
		{
			if (mi(a, upie / i, n) != 1)break;
			upie /= i;
		}
	}
	int r = maxp(upie);
	if (mi(a, upie / r, n) == 1)upie /= r;
	return upie;
}

int gcd(int m, int n)
{
	if (n == 0)return m;
	return gcd(n, m%n);
}

int phi(int n)
{
	int r = n;
	for (int i = 2; i*i <= n; i++)
	{
		if (n%i == 0)
		{
			while (n%i == 0)n /= i;
			r = r / i*(i - 1);
		}
	}
	if (n > 1)r = r / n*(n - 1);
	return r;
}

long long f(int n)
{
	if (n % 16 == 0)return 0;
	if (n % 2 == 0)return f(n / 2);
	if (n % 5 == 0)return 0;
	long long k= order(10, n, phi(n));
	int m = 0;
	while (n % 3 == 0)
	{
		m++;
		n /= 3;
	}
	n = k;
	while (n % 3 == 0)
	{
		m--;
		n /= 3;
	}
	if (m == 2)return k * 9;
	if (m == 1)return k * 3;
	return k;
}

int main()
{
	int n, cas = 0;
	while (cin >> n)
	{
		if (n == 0)break;
		cout << "Case " << ++cas << ": " << f(n) << endl;
	}
	return 0;
}

这个代码直接从CSU – 1755 阶数里面的代码修改而来,加了一个函数f,main函数自然是改了,其他地方都没改。