一、按位异或的应用:
例题1:给定2n+1个数,其中有一个数只出现了一次,找出这个数。
由于两个相同的数字异或等于0,一个数异或0等于他自己。
于是从1开始异或到2n+1即可。剩下的数字就是那个出现一次的数。
例题2:给定2n+2个数,其中有两个数是单身,按从大到小顺序找到这两个数。内存限制只能开一个2n+2的数组。
贴一个代码:
#include <iostream>
using namespace std;
const int MAX=100000+10;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s*10)+ch-'0';ch=getchar();}
x=s*w;
}
int a[MAX];
int main()
{
int x;
int sum=0;int n;cin>>n;
for(int i=0;i<2*n;i++)
{
read(x);
a[i]=x;
sum^=a[i];
}
int lowbit= sum & (-sum);
int b=0,c=0;
for(int i=0;i<2*n;i++)
{
if((lowbit&a[i])==lowbit)
{
b^=a[i];
}else
{
c^=a[i];
}
}
if(b<c)
{
cout<<b<<" "<<c<<endl;
}else
{
cout<<c<<" "<<b<<endl;
}
return 0;
}
不难看出从1异或到2n+2得到的就是两个单身的数字的异或,叫这个数为sum吧。
可以随意找sum二进制的一位:只要它是1,那么就可以判断出来两个单身数字在这一位上必有一个是1,一个是0。然后根据这一位是1还是0在2n+2个数字里分成两份。这样问题就转化为在每一份里找到一个2n+1的数,根据例题1可以解决。
技巧:lowbit的应用,lowbit就是找到一个数二进制最小为1的位,比如说为n。lowbit数值上等于2的n次幂。比如说11111000,lowbit就是1000;
lowbit的求法 lowbit(a)=a &(-a);得到了lowbit,就可以通过按位与操作来判断数字的二进制第n位是不是1。
二、快速幂与矩阵快速幂:
P1226 【模板】快速幂 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
洛谷模板题.
快速幂的复杂度是O(logn),a的n次幂,如果n是奇数,那么a的n次幂等于a的n-1次幂。如果n是偶数,a的n次幂等于a方的n/2次幂。每一步都去取模。
#include <iostream>
using namespace std;
#define ll long long
ll quickpow(ll x,ll y,ll mod)
{
ll re=1;
while(y!=0)
{
if(y%2==1) re=re*x%mod;
x=x*x%mod,y=y/2;//2的y次方等于4的y/2次方.
}
return re;
}
int main()
{
ll x,y,mod;
cin>>x>>y>>mod;
//2^10 mod 9=7
cout<<x<<"^"<<y<<" mod "<<mod<<"="<<quickpow(x,y,mod)<<endl;
return 0;
}
矩阵快速幂:
即把一个复杂递推式换成矩阵的形式Ax=B,n次递推只需要求出矩阵x的n次幂再乘以A即可。
P1962 斐波那契数列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <iostream>
using namespace std;
#define ll long long
const ll mod=1e9+7;
struct matrix
{
ll sqr[2][2];
}a,f,x;
matrix operator*(matrix a,matrix b)
{
matrix c;
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
{
//第i行乘第j列
c.sqr[i][j]=(a.sqr[i][0]%mod*b.sqr[0][j]%mod)%mod+(a.sqr[i][1]%mod*b.sqr[1][j]%mod)%mod;
c.sqr[i][j]%=mod;
}
return c;
}
void quickpow(matrix &x,ll y)
{
matrix I;
I.sqr[0][0]=I.sqr[1][1]=1;
I.sqr[0][1]=I.sqr[1][0]=0;
while(y)
{
if(y%2==1) I=I*x;
x=x*x,y/=2;
}
x=I;
}
int main()
{
f.sqr[0][0]=2;f.sqr[0][1]=1;
x.sqr[0][1]=x.sqr[0][0]=x.sqr[1][0]=1;
ll n;cin>>n;
quickpow(x,n-1);
f=x*f;
cout<<f.sqr[0][1]%mod<<endl;
return 0;
}
矩阵快速幂和快速幂实现上的区别:矩阵快速幂的re需要设为单位矩阵I,矩阵乘法需要重载运算符*,即aij的数等于第i行*第j列每个相应的数相乘再相加。这样效率比单纯递推高。
三、素数相关:
线性筛:
P3383 【模板】线性筛素数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <iostream>
using namespace std;
const int MAX=1e7+10;
using namespace std;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s*10)+ch-'0';ch=getchar();}
x=s*w;
}
bool judge[MAX];//0是素数,1不是素数
int prime[MAX];int p=0;
int main()
{
int n;read(n);
for(int i=2;i<n;i++)
{
if(!judge[i]) prime[++p]=i;
for(int j=1;j<=p&&i*prime[j]<=n;j++)
{
judge[i*prime[j]]=1;
if(i%prime[j]==0) break;//相比于埃氏筛优化的地方
}
}
int t,x;
read(t);
for(int i=0;i<t;i++)
{
read(x);
printf("%d\n",prime[x]);
}
return 0;
}
线性筛就是遍历1到i,查询i之前的所有素数,这样i*n一定不是素数,如果遍历到i的时候i没有被前面的数标记,那就把i存到素数数组里。如果某个素数是i的约数,break;不重复计算。
欧拉函数求解:
给定一个范围 n,有 q 个询问,每次询问一个数的欧拉函数的值:
#include <iostream>
using namespace std;
const int MAX=1e8+10;
using namespace std;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s*10)+ch-'0';ch=getchar();}
x=s*w;
}
bool judge[MAX];//0是素数,1不是素数
int prime[MAX];int p=0;
int phi[MAX];//欧拉函数
int main()
{
phi[1]=1;
int n;read(n);
for(int i=2;i<n;i++)
{
if(!judge[i]) prime[++p]=i,phi[i]=i-1;
for(int j=1;j<=p&&i*prime[j]<=n;j++)
{
judge[i*prime[j]]=1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];//两个数同余的合数的欧拉函数:余数(质数)乘以素数
break;//相比于埃氏筛优化的地方
}else
{
phi[i*prime[j]]=phi[i]*phi[prime[j]];//互为素数的合数的欧拉函数:两素数欧拉函数之积
}
}
}
int t;read(t);int x;
for(int i=0;i<t;i++)
{
read(x);
printf("%d\n",phi[x]);
}
return 0;
}
欧拉函数phi(x)指的是从1到x里与x互质元素的个数。
欧拉函数是通过三条定理来推的
素数的欧拉函数是p-1(1和任何数都互素)- i为素数
nm互素,phi(nm)=phi(n)*phi(m)
m%n==0 phi(nm)==n*phi[m]
欧拉函数应用:
训练题
这个题求得是在一个n*n的正方形里,gcd(x,y)=1的数对个数。
在数值上等于2*欧拉函数的前缀和-1;
#include <iostream>
using namespace std;
const int MAX=1e8+10;
using namespace std;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s*10)+ch-'0';ch=getchar();}
x=s*w;
}
bool judge[MAX];//0是素数,1不是素数
int prime[MAX];int p=0;
int phi[MAX];//欧拉函数
int main()
{
phi[1]=1;
int n;read(n);
for(int i=2;i<=n;i++)
{
if(!judge[i]) prime[++p]=i,phi[i]=i-1;
for(int j=1;j<=p&&i*prime[j]<=n;j++)
{
judge[i*prime[j]]=1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];//两个数同余的合数的欧拉函数:余数(质数)乘以素数
break;//相比于埃氏筛优化的地方
}else
{
phi[i*prime[j]]=phi[i]*phi[prime[j]];//互为素数的合数的欧拉函数:两素数欧拉函数之积
}
}
}
int t;cin>>t;int x;
long long int ans=0;
for(int i=1;i<=n;i++)
{
//cout<<phi[i]<<endl;
ans+=phi[i];
}
cout<<2*ans-1<<endl;
return 0;
}
四、同余相关:
扩展欧几里得算法:
#include<iostream>
using namespace std;
#define ll long long
int t,c,d,x,y;
void exgcd(int a, int b, int &x, int &y)
{
if(a%b==0)
{
x=0;
y=1;
return;
}//先递推找到一个可以得到通解的一组数字。
exgcd(b,a%b,x,y);//递推,如果a取余b不等于0不会返回。
t=x;
x=y;
y=t-a/b*y;
} //最后得到的一组x,y才是解
int main()
{
ll c,d;
cin>>c>>d;
exgcd(c,d,x,y);//一定有解就不用管xy初始的值。
while(x<=0)
{
x+=d;
}//最小正整数解保证是正数
cout<<x<<endl;
return 0;
}
求关于 x 的同余方程 ax≡1(modb) 的最小正整数解x。
转化为ax+by等于1的ex欧几里得算法
结果是x的最小整数;y是个负数
乘法逆元求法:
P2613 【模板】有理数取余 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
逆元就是a/b(modp)=a*b'(modp) b'就是b的逆元,
#include <iostream>
using namespace std;
#define ll long long
const ll mod=1e9+7;
ll getint(){
char chr=getchar();ll x=0;
while(chr<'0'||chr>'9')chr=getchar();
while(chr>='0'&&chr<='9'){
x=((x<<3)+(x<<1)+(chr&15))%mod;//15的二进制位是00001111;位运算更快,实质上是把x*10;
chr=getchar();
}
return x;
}
ll quickpow(ll x,ll y,ll mod)
{
ll re=1;
while(y!=0)
{
if(y%2==1) re=re*x%mod;
x=x*x%mod,y=y/2;//2的y次方等于4的y/2次方.
}
return re;
}
int main()
{
ll a,b;
a=getint();b=getint();
if(b==0) cout<<"Angry!"<<endl;
else cout<<a*quickpow(b,mod-2,mod)%mod<<endl;
return 0;
}
求n/m modp
转化为求n*m的逆元modp;
求逆元的几种方法:
1.exgcd求ax=1(modb)的解,求出逆元
2.费马小定理,a的p-1次方与1同余。a的p-2次方就是逆元
3.递推公式