数学基础和技巧学习指南

前置芝士

众数

对数

\(log_1(x)\)是没有意义的,会报错。

生成[l,r]的log2数组

以 2 为底的对数实际上是在求一个数在二进制下的最高有效位的位置,这个位置从 0 开始计数,也就是说,如果一个数有 n 位二进制表示,那么它的最高有效位的位置就是 n-1。

for(int i=1;i<=n;i++)
lg[i]=lg[i-1]+(1<<(lg[i-1]+1)==i)
#此时 lg[i] 就等于它的二进制表示中最高有效位的位置
# i&(i-1) 表示将 i 的最低有效位的 1 置为 0
lg=[0]*11
lg[0]=-1
for i in range(1,11):
    lg[i]=lg[i-1]+(i&(i-1)==0)

正整数

[公式:向上取整]

\[y=\frac{x+5}{6} \]

求x的位数

//1
(int)log10(num)+1  //[log10(x)+1]取整
//2
int f(long long x){
  int res=0;
  while(x){
    res++;
    x/=10;
  }
  return res;
}

求x的各个位的和

int s(int x){
    int sum=0;
    while(x){
    sum+=x%10;
    x/=10;
    }
    return sum;
}

求1→n中的m的倍数的和

cnt=3+6+9+....+n//3*3

(1+2+3+....+n//3)*3

def s(m:int)->int:
    return (1+n//m)*(n//m)//2*m

完全平方数

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。

等差数列

尼科彻斯定理

连续的奇数就是一个公差为2的等差数列

\[S(n)=a1*n+n*(n-1)/2*d \]

根据题意说的尼科彻斯定理:任何一个整数m的立方都可以写成m个连续奇数之和;

\(m*m*m=x*m+m*m-m\),即可算出 x=m*m-m+1。

求和公式

\[S_n=\frac{n\left(a_{1}+a_{n}\right)}{2} \]

\[S_{n}=n \times a_{1}+\frac{n \times(n-1)}{2} \times d \]

可以用中位数求和,S[l,r]。

\[S_n=(r-l+1)\times(l+r)\div2 \]

等比数列

求和公式

\[sum=\frac{p^n-1}{p-1} \]

质数

性质

两个连续的自然数一定是互质数。如:4和5、13和14是互质数。

相邻的两个奇数一定是互质数。如:5和7、75和77是互质数。

两个数中的较大一个是质数,这两个数一定是互质数。如:3和19、16和97是互质数。

两个数中的较小一个是质数,而较大数是合数且不是较小数的倍数,这两个数一定是互质数。如:2和15、7和54是互质数。

较大数比较小数的2倍多1或少1,这两个数一定是互质数。如:13和27、13和25是互质数。

两个数分别除以它们的最大公约数,所得的商一定互质。

两个数的最小公倍数分别除以这两个数,所得的商一定互质。

两个互质的数a和b最小不能表示的数就是(a-1)(b-1)-1,也就是说两个互质的数a,b可以表示(a-1)(b-1)之后的所有数字。

中间是偶数的连续三个自然数互质。

因子

阶乘

10!=3628800=3.6*10^6

康托展开

康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。

康托展开可以求解一个排列的序号,比如:12345 序号为 1 ,12354序号为2,按字典序增加编号递增,依次类推。
康托逆展开可以求解一个序号它对应的排列是什么。

\(X=a_n(n-1)!+a_{n-1}(n-2)!+\cdots+a_1\cdot0!\)

\(a_i\) 的意思是从右往左数第 i 位这个数是这个数前未出现的数,第\(a_i\)大。计算str[i]是第几大的数,或者说在此之后计算有几个比他小的数

单个质数判断

[6倍高效判断素数法]

除了2和3外,其余素数都与6的倍数相邻,这些素数都满足6n±1。

这是个trivial的素数分布特征,因模6的余数中只有1和5与6互素,故从5开始的素数必与6的倍数相邻。

//从5开始,如果能整除6n+-1,肯定是合数
bool isprime(int num){
    if(num==2||num==3){return true;}
    if(num%6!=1&&num%6!=5){return false;}
    int len=(int)sqrt(num);
    for(int i=5;i<=len;i+=6){
        if(num%i==0||num%(i+2)==0)
            return false;
    }
    return true;
}

区间[1,n]的质数判断

(1)

vector<int> get_primes(int n) {
	vector<int> res(n + 1, 1);
	res[0] = res[1] = 0;
	int len = sqrt(n);
	for (int i = 2; i <= len; i++) {
		if (res[i]) {
			for (int j = i * i; j <= n; j += i) {
				res[j] = 0;
			}
		}
		return res;
	}

(2)线性筛:O(n)

//return:范围[1,n]的素数个数
const int N=1e8+10;
int pr[N],cnt;
bool vis[N];
int init(int n){
    int res=0;
	for(int i = 2; i<=n; i++){
		if(!vis[i]) pr[cnt++] = i,res++;
		for(int j = 0; j <cnt&&pr[j]*i<=n; j++){
			vis[pr[j] * i] = true;
			if(i % pr[j] == 0) break;//确保每个数被自己的最小质数筛掉
		}
	}
    return res;
}

区间[l,r]的质数判断

const int N=50010;
int l,r,cnt;
int pr[N],ispr[N];
void init(){
    ispr[1]=1;
    for(int i=2;i<N;i++){
        if(!ispr[i]){
            pr[++cnt]=i;
        }
        for(int j=1;j<=cnt&&i*pr[j]<N;j++){
            ispr[i*pr[j]]=1;
            if(i%pr[j]==0) break;//确保每个数被自己的最小质数筛掉
        }
    }
}
int ans[1000010],res=0;
void solve(){
    cin>>l>>r;
    init();
    for(int i=1;i<cnt&&pr[i]<=r;i++){
        for(ll j=max(2,(l+pr[i]-1)/pr[i]);j*pr[i]<=(ll)r;j++){
            ans[j*pr[i]-l]=1;
        }
    }
    if(l<=1) ans[1-l]=1;
    for(int i=0;i<=r-l;i++){
        if(!ans[i]) res++;
    }
    cout<<res;
}

求n的因子数

时间复杂度:\(O(\sqrt{n})\)

//获取n的不重复因子数数组
vector<int> findFactors(ll n){
    vector<int> res;
    for(int i=1;i<=n/i;i++){
        if(n%i==0){
            if(i==n/i){
                res.push_back(i);
            }else{
                res.push_back(i);
                res.push_back(n/i);
            }
        }
    }
    return res;
}

求n的质因子及个数

int a[100010],idx;
int b[100010];
void init(int n){
	for(int i=2;i<=n/i;i++){
		if(n%i==0){
			a[idx]=i;
			while(n%i==0){
				b[idx]+=1;
				n/=i;
			}
			idx+=1;
		}
	}
	if(n>1) a[idx]=n,b[idx++]+=1;
}

求区间[1,n]的因子数

时间复杂度:O(nlogn)

所以可以遍历1~n,将这n个数做为约数,去求他们对应的倍数的值。

const int N=1e6+10;
int a[N];//存放每个数的约数个数
void solve(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n/i;j++){
			a[i*j]++;
		}
	}
}

求阶乘n!中每个质因子出现次数

int prime[1000],cnt=0,rat[1000];
bool p[1000];
void findprime(int n)//欧拉筛来找素数
{
    for (int i = 2; i <= n; i++)
    {
        if (!p[i])
            prime[cnt++] = i;
        for (int j = 0; j < cnt; j++)
        {
            if (i*prime[j] > n)break;
            p[i*prime[j]] = true;
            if (i%prime[j] == 0)break;
        }
    }
}
int rate(int n, int p)//分解质因数,求得每个质因数在n!中的出现个数
{
    int res = 0;
    while (n)
    {
        res += n / p;
        n /= p;
    }
    return res;
}
void solve(){
    int n, m;
    cin>>n>>m;
    findprime(n);
    for (int i = 0; i < cnt; i++)
        rat[i] = rate(n, prime[i]) - rate(m, prime[i]) - rate(n - m, prime[i]);//计算每个质因数的幂的次数
}

\(a^b\)的所有因子和

[solved]

既然要求因子和,那我们必然要先分解质因数

根据整数的唯一分解定理,整数a进行质因数分解对应的式子唯一,有:

\[a=p_1^{k_1}*p_2^{k_2}*p_3^{k_3}*\ldots*p_n^{k_n} \]

\(a^b\)的质因子分解:

\[a^b=p_1^{k_1*b}*p_2^{k_2*b}*p_3^{k_3*b}*\ldots*p_n^{k_n*b} \]

因子和:

\[ans=(1+p_1^1+p_1^2+p_1^3+\ldots+p_1^{k_1*b})*(1+p_2^1+p_2^2+p_2^3+\ldots+p_1^{k_2*b})*\ldots*(1+p_n^1+p_n^2+p_n^3+\ldots+p_n^{k_n*b}) \]

【细节】

(x-1)=== k*mod:(x-1)不存在逆元,我们需要进行特判。

\[\begin{aligned}(1+p_i^1+p_i^2+\cdots+p_i^{c_i})\%P\end{aligned}=(1+1+1+⋯+1)=c i+1 \]

ll a, b, f[10010][2], cnt = 0;
const ll mod = 9901;
ll qmi(ll a, ll b) { //快速幂
	ll res = 1;
	while (b) {
		if (b & 1) {
			res = res * a % mod;
		}
		a = a * a % mod;
		b >>= 1;
	}
	return res % mod;
}
ll sum(ll x, ll y) {
	ll res = 0;
	y *= b;
	cout<<x<<" "<<y<<endl;
	if (x % mod == 1) {
		res = (y + 1) % mod; //当逆元不存在时
	} else {
		res = (qmi(x % mod, y+1) - 1) % mod * qmi((x - 1) % mod, mod - 2) % mod; //当逆元存在时
	}

	return res % mod;
}
void solve() {
	cin >> a >> b;
	if (a == 0) { //特判,0的因数和就是0
		cout << 0 << endl;
		return;
	}
	for (int i = 2; i * i <= a; i++) { //分解质因数
		if (a % i == 0) {
			cnt++;
			f[cnt][0] = i; //记录质因数
			f[cnt][1] = 1; //记录幂次
			a = a / i;
			while (a % i == 0) {
				f[cnt][1]++;//记录幂次
				a = a / i;
			}
		}
	}
	if (a > 1) { //可能a仍为因子
		cnt++;
		f[cnt][0] = a;
		f[cnt][1] = 1;
	}
	ll res = 1;
	for (int i = 1; i <= cnt; i++) {
		res = res * sum(f[i][0], f[i][1]) % mod;
	}
	cout << (res+mod)%mod<< endl;
}

细胞分裂

[problem pesection]

博士要培养细菌平均装到 M 个试管中去做实验。M 很大,一般的数据类型装不下它。碰巧的是,M 正好可以分成 m1m2 的形式。
一共有 n 种细菌,对于每个第 i 种细菌,每天都可以分裂成 Si 个细菌。给出 m1 、m2 、n 和 对应的 Si。Si 什么时候可以变成 M 的倍数?

[solved]

质因数分解:任意的大于1 的整数都一定可以分解成质数幂乘积的形式
数学表达式为:\(n = 2^x1^ * 3^x2^ * 5^x3^ * 7 ^x4^ ……\)(x1,x2,x3…会根据 n 的不同而改变)

【样例分析】

2
24 1
30 12

\(M = 2 ^3 * 3 ^1\)

-------- 第一天 第二天 第三天 第四天
一号 \(2^1 * 3^1 * 5^1\) \(2 ^2 * 3 ^2 * 5 ^2\) \(2 ^3 * 3 ^3 * 5 ^3\) \(2 ^4 * 3 ^4 * 5 ^4\)
二号 \(2^2 * 3^1\) \(2^4 * 3^2\) \(2^6 * 3^3\) \(2^8 * 3 ^4\)

一号细菌在第三天的时候可以整除 M
二号细菌在第二天的时候可以整除 M
答案就是 2 了

step1:筛选出来所有的质数
step2:找 M 的质因子,同时遍历待选细菌,如果细菌不能被 M 的质因数整除这个细菌就不会增值成 M 的倍数
step3:遍历待选细菌,选出最佳答案。如果这时候没有待选细菌,就输出 -1

typedef struct Node{
 int num;	// 质因数
 int sum;	// 次幂
}node;

int pr[5000];
bool flag[30005];
int num = 0;
// 任意的大于1 的整数都一定可以分解成质数幂乘积的形式
void Init(){            // 筛选质数 
	for(int i = 2; i < 30000; i++){
		if(!flag[i]) pr[num++] = i;
		for(int j = 0; j < num; j++){
			if(pr[j] * i > 30005) break;
			flag[pr[j] * i] = true;
			if(i % pr[j] == 0) break;
		}
	}
}
void solve(){
	Init();
	ll n;
 	cin >> n;
 	ll m1, m2;
	cin >> m1 >> m2;
 	queue<ll>v;           //存待选的细菌 
 	ll b;
 	for(int i = 1; i <= n; i++){
  	cin >> b;
  	v.push(b);
 	}
	vector<node>v1;      // 存 m1^m2 的质分解的结果 
 	for(int i = 0; m1 != 1; i++){
  		if(m1 % pr[i] == 0){        //如果 pr[i] 是 m1^m2 的质因子就存起来 
  			node a = {pr[i], 0};
   			while(m1 % pr[i] == 0){  // 算一下是多少次幂 
   				m1 /= pr[i];
    				a.sum++;
   			}
   			a.sum *= m2;             // 别忘了 m2 
   			v1.push_back(a);
   			int len = v.size();
   			for(int j = 1; j <= len; j++){    // 顺便判断待选细菌符合不符合 
   				int c = v.front();	//从队列中拿出来
    				v.pop();
    				if(c % pr[i] == 0) v.push(c);  //符合就再放回去
   			}
   		}
   	}

	if(v.empty()) cout << -1 << endl;        // 如果已经没有待选的答案,就输出 -1  
	else{
  		ll res = 0x3f3f3f3f;
  		int len = v.size();
  		for(int i = 1; i <= len; i++){       // 寻找最佳答案 
   			int c = v.front();
   			ll temp = 0;
   			for(int j = 0; j < v1.size(); j++){   // 遍历 m1^m2 的质因子
    				ll sum = 0;
    				while(c % v1[j].num == 0){
     					sum++;
     					c /= v1[j].num;
    				}
    				temp = max(temp, (v1[j].sum + sum - 1) / sum);
   			}
   			res = min(temp, res);
   			v.pop();
   		}
  	cout << res << endl;
  	}
}

查看字符串字典序的排名

朴素法

【指定排位为1的字符串】

【c++】

const int N = 17;
ll jc[N+10]{1, 1};//阶乘
string s1; // 起始串
ll rk1;    // 起始串排名

ll tak(string &s)
{
    ll ans = 1;
    int sz = s.size();
    for (int i = 0; i < sz; i++)
    {
        int t = 0;
        for (int j = i + 1; j < N; j++)
        {
            if (s[j] < s[i])//判断后面比当前小的数目,可以判断这个位的可选项
                t++;
        }
        ans += jc[sz - 1 - i] * t;
    }
    return ans;
}

void init()
{
    for (int i = 1; i <= N; i++)
    {
        jc[i] = jc[i - 1] * i;
    }
    rk1 = tak(s1);
}

树状数组优化:O(nlogn)

【指定排位为1的字符串】

【c++】

//c++
/*N=1000000 o(nlogn)*/
const int N = 17;
ll jc[N+10]{1, 1};//阶乘
string s1; // 起始串,需要输入
ll rk1;    // 起始串排名
ll ft[28];
int lowbit(int x){
    return x&-x;
}

int query(int x){
    int res=0;
    for(int i=x;i>=1;i-=lowbit(i)) res+=ft[i];
    return res;
}

void update(int x,int n,ll val){
    for(int i=x;i<=n;i+=lowbit(i)) {ft[i]+=val;}
}

ll tak(string &s)//排名
{
    ll ans = 1;
    int sz = s.size();
    for(int i=0;i<sz;i++){update(s[i]-'a'+1,sz,1);}
    for (int i = 0; i < sz; i++)
    {
        ans += jc[sz - 1 - i] * query(s[i]-'a');
        update(s[i]-'a'+1,sz,-1);
    }
    return ans;
}

void init()//初始化
{
    for (int i = 1; i <= N; i++)
    {
        jc[i] = jc[i - 1] * i;
    }
    rk1 = tak(s1);
}

void solve(){
    s1="aejcldbhpiogfqnkr";//起始串
    init();
    string s2="ncfjboqiealhkrpgd";
    ll rk2=tak(s2);
}

查看数字全排列排名

[c++]

//c++
/*N=1000000 o(nlogn),mod=998244353;*/
const int N = 100010;
const int mod=998244353;
ll jc[N+10]{1, 1};//阶乘
ll ft[N+10];//树状树状
int a[N+10],n;//全排列数组
int lowbit(int x){
    return x&-x;
}

int query(int x){
    int res=0;
    for(int i=x;i>=1;i-=lowbit(i)) res+=ft[i];
    return res;
}

void update(int x,ll val){
    for(int i=x;i<=n;i+=lowbit(i)) {ft[i]+=val;}
}

ll tak()//排名
{
    ll ans = 1;
    for(int i=0;i<n;i++){update(a[i],1);}
    for (int i = 0; i < n; i++)
    {
        ans = (ans+jc[n - 1 - i] * query(a[i]-1))%mod;
        update(a[i],-1);
    }
    return ans;
}

void init()//初始化
{
    for (int i = 1; i <= N; i++)
    {
        jc[i] = jc[i - 1] * i%mod;
    }
}

void solve(){
    init();
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    ll rk=tak();
    cout<<rk<<endl;
}

计算一元多项式的值

[朴素计算]

double F(double x)
{
 double f=0;
 for(int i=n;i>=0;i--)
 {
  double t=1;
  for(int j=1;j<=i;j++)
  t*=x;
  f+=a[i]*t;
 }
 return f;
 }

[秦九韶算法]

int n;//位数(n+1)
double a[15] //依次系数
double F(double x)
{
 double sum=0;
 for(int i=n;i>=0;i--)
 sum=sum*x+a[i];
 return sum; 
}

摩尔投票

摩尔投票是一种用来解决绝对众数问题的算法。在一个集合中,如果一个元素的出现次数比其他所有元素的出现次数之和还多,那么就称它为这个集合的 绝对众数 。等价地说,绝对众数的出现次数大于总元素数的 一半

摩尔投票的过程非常简单,让我们把找到绝对众数的过程想象成一次选举。我们维护一个m,表示当前的候选人,然后维护一个 cnt。对于每一张新的选票,如果它投给了当前的候选人,就把 cnt 加1,否则就把cnt 减1(也许你可以想象成,B的一个狂热支持者去把A的一个支持者揍了一顿,然后两个人都没法投票)。特别地,计票时如果cnt=0 ,我们可以认为目前谁都没有优势,所以新的选票投给谁,谁就成为新的候选人。

O(n) 时间复杂度、 O(1) 空间复杂度的

int m = 0, cnt = 0;
for (int i = 0; i < n; ++i)
{
    if (cnt == 0)
        m = A[i];
    if (m == A[i])
        cnt++;
    else
        cnt--;
}
// 最后需要验证答案是否符合要求

扩展摩尔投票

要选出 N 个候选人,并且要求每个人的得票都超过总票数的 1/(N+1)。

int m[N], cnt[N];
for (auto e : nums) {
    int i = find(m, m + N, e) - m;
    if (i != N) { // 如果当前票投给了候选人之一
        cnt[i]++;
        continue;
    }
    int j = find(cnt, cnt + N, 0) - cnt;
    if (j != N) { // 如果当前存在一个位置"虚位以待"
        m[j] = e;
        cnt[j] = 1;
        continue;
    }
    for (auto &c : cnt)
        c--;
}
// 最后需要验证答案是否符合要求

区间绝对众数

posted @ 2023-10-16 23:35  White_Sheep  阅读(7)  评论(0编辑  收藏  举报