这个周结束数论2专题,学习了好多知识点,先整理下题目,然后将知识点最后集中一下。
V题:给出n,m求(1!+2!+3!+...n!)%m,虽然题目很大(10^100),但是已知(a*b)%m=(a%m*b%m)%m,而且当n>m时,n!%m==0,所以计算量也就没那么可怕。注意n的输入用字符转换就行了。(水题)
H题:(水题)就是快速幂求几个数次方的和。
I题:给出两个数L,R,求L<=x<=R,区间内两素数距离最长,和最短的两对。
这是到好题,因为数据范围很大,不能够暴力达标预处理。 因为区间长度不超过10^6,所以先筛出,1到10^6之间的素数,然后利用线性筛素数的原理,确定题目中L和R的位置,利用一个10^6的数组做映射,筛出L到R之间所有的合数,同时将相应的位置映射回0到10^6的数组最后统计出区间内的素数,最后暴力找就行了,反正10^6之间素数也不是太多。
void solve()//映射,筛选处理
{
memset(notprime, false, sizeof(notprime));//记录映射回来的合数标记
if (L < 2) L = 2;
for (int i = 1; i <= cnt; i++)
{
if (prime[i] > R) break;
int p;
if (L % prime[i] == 0) p = L / prime[i];//确定给素数的倍数映射位置
else p = L / prime[i] + 1;
if (p == 1) p = 2;
while (p * prime[i] >= L && p * prime[i] <= R && (ll)prime[i] * prime[i] <= (ll)R)
{
notprime[p * prime[i] - L] = true;//映射处理
p++;
}
}
tot = 0;
for (int i = 0; i <= R - L; i++)
{
if (notprime[i] == false)
ans[++tot] = i + L;//将素数记录下来
}
}
这是道好题,把线性筛素数最大的发挥。。
F题:就是判素数,要利用随机算法判断 Miller_Rabin算法判素数 Pollard_rho算法大数因数分解。
粘下模版吧,虽然有误差,s足够大,判错几率很小。就是利用欧拉定理带入随机数来测试。
这是一位大佬的板子。
//****************************************************************
// Miller_Rabin 算法进行素数测试
//速度快,而且可以判断 <2^63的数
//****************************************************************
const int S=20;//随机算法判定次数,S越大,判错概率越小
//计算 (a*b)%c. a,b都是long long的数,直接相乘可能溢出的
// a,b,c <2^63
long long mult_mod(long long a,long long b,long long c)
{
a%=c;
b%=c;
long long ret=0;
while(b)
{
if(b&1){ret+=a;ret%=c;}
a<<=1;
if(a>=c)a%=c;
b>>=1;
}
return ret;
}
//计算 x^n %c
long long pow_mod(long long x,long long n,long long mod)//x^n%c
{
if(n==1)return x%mod;
x%=mod;
long long tmp=x;
long long ret=1;
while(n)
{
if(n&1) ret=mult_mod(ret,tmp,mod);
tmp=mult_mod(tmp,tmp,mod);
n>>=1;
}
return ret;
}
//以a为基,n-1=x*2^t a^(n-1)=1(mod n) 验证n是不是合数
//一定是合数返回true,不一定返回false
bool check(long long a,long long n,long long x,long long t)
{
long long ret=pow_mod(a,x,n);
long long last=ret;
for(int i=1;i<=t;i++)
{
ret=mult_mod(ret,ret,n);
if(ret==1&&last!=1&&last!=n-1) return true;//合数
last=ret;
}
if(ret!=1) return true;
return false;
}
// Miller_Rabin()算法素数判定
//是素数返回true.(可能是伪素数,但概率极小)
//合数返回false;
bool Miller_Rabin(long long n)
{
if(n<2)return false;
if(n==2)return true;
if((n&1)==0) return false;//偶数
long long x=n-1;
long long t=0;
while((x&1)==0){x>>=1;t++;}
for(int i=0;i<S;i++)
{
long long a=rand()%(n-1)+1;//rand()需要stdlib.h头文件
if(check(a,n,x,t))
return false;//合数
}
return true;
}
//************************************************
//pollard_rho 算法进行质因数分解
//************************************************
long long factor[100];//质因数分解结果(刚返回时是无序的)
int tol;//质因数的个数。数组小标从0开始
long long gcd(long long a,long long b)
{
if(a==0)return 1;
if(a<0) return gcd(-a,b);
while(b)
{
long long t=a%b;
a=b;
b=t;
}
return a;
}
long long Pollard_rho(long long x,long long c)
{
long long i=1,k=2;
long long x0=rand()%x;
long long y=x0;
while(1)
{
i++;
x0=(mult_mod(x0,x0,x)+c)%x;
long long d=gcd(y-x0,x);
if(d!=1&&d!=x) return d;
if(y==x0) return x;
if(i==k){y=x0;k+=k;}
}
}
//对n进行素因子分解
void findfac(long long n)
{
if(Miller_Rabin(n))//素数
{
factor[tol++]=n;
if(ans>n) ans=n;
return;
}
long long p=n;
while(p>=n)p=Pollard_rho(p,rand()%(n-1)+1);
findfac(p);
findfac(n/p);
}
Y题(Zombie's Treasure Chest):给出一个背包大小n,和两种宝石的体积s1,s2和价值v1,v2。求将宝石装入包中,价值最大。
刚开始没意识到,结果wa了几发才意识到,因为不一定是先装性价比高的。
其实是k1*s1+k2*s2<=n,k1*v1+k2*v2=ans,求最大的ans 。然后一个一个的暴力找。
if(v1*s2>=v2*s1)//此时性价比,一物品大于二物品
{
ll cnt=min(s1-1,N/s2);//满足s1*v2<=s2*v1,此时显然有物品2的数量不会超过s1,(否则这s1个物品2的空间放物品1的价值为s2*v1更优)
for(int i=0;i<=cnt;i++)
{
ll h=(N-(i*s2))/s1;
ans=max(ans,i*v2+h*v1);
}
}
else
{
ll cnt=min(s2-1,N/s1);//同理
for(int i=0;i<=cnt;i++)
{
ll h=(N-(i*s1))/s2;
ans=max(ans,i*v1+h*v2);
}
}
B题:给出若干个单词,然后输入单词,判断有四种情况:
1,有这个单词。输出正确。
2,一个字母错。缺一个字母。多一个字母。
3,完全错误。
其实就是暴力找。
int solve(char *str,char *ss)
{
int len1,len2;
len1=strlen(str);
len2=strlen(ss);
if(len1==len2)//判断是否只错一个
{
int cnt=0;
for(int i=0;i<len1;i++)
{
if(str[i]!=ss[i])
{
cnt++;
if(cnt>1) return 0;
}
}
return 1;
}
else if(len1>len2)//是否多一个字
{
int cnt=0;
for(int i=0;i<len1;i++)
{
if(str[i]==ss[cnt]) cnt++;
if(cnt==len2) return 1;
}
return 0;
}
else if(len1<len2)//是否少一个
{
int cnt=0;
for(int i=0;i<len2;i++)
{
if(str[cnt]==ss[i]) cnt++;
if(cnt==len1) return 1;
}
return 0;
}
}
N题(OO’s Sequence):求每一个[L, R]区间中 j!=i a[i]对任意a[j]取余为0 的i的个数的总和。
这个题刚开始我一直在考虑区间dp,后来发现可以换一个方向考虑,不一定从区间入手算,可以考虑是该点在多少区间可以被当做元素+1,即找出 i 这个位置向左向右可以最多延展到哪可以符合条件(就是区间内其他因子不是它的因子),然后代入演算出的公式(r[i]-x+1)*(x-l[i]+1),加和。
而计算左右延展最长到哪有个小技巧。先从左到右依次对元素进行标记vis[ i ],表示 i 这个值最近在哪出现,在这之前爆搜判断这个值因子最近处,这样只找1到sqrt(a[i]),就可以了。
void Left(int val,int t)
{
l[t]=max(vis[val],vis[1]);
for(int i=2;i*i<=val;i++)//这样就可以变成n*sqrt(n),就是提前做个标记的小操作。
{
if(val%i==0)
{
l[t]=max(l[t],vis[i]);
l[t]=max(l[t],vis[val/i]);
}
}
}
void Right(int val,int t)//在倒着来一遍。
{
r[t]=min(vis[val],vis[1]);
for(int i=2;i*i<=val;i++)
{
if(val%i==0)
{
r[t]=min(r[t],vis[i]);
r[t]=min(r[t],vis[val/i]);
}
}
}
马上总结下一篇数论知识点总结。