目录
2、伯特兰-切比雪夫(Bertrand-Chebyshev)定理
一、数学
1、逆元
以ax(mod p)=1为例
(一)费马小定理/欧拉定理(快速幂)
p是质数且a不能被p整除
#include<iostream>
#define int long long
using namespace std;
const int p=998244353;
int power(int a,int b,int p){
int ans=1;
while(b){
if(b&1){
ans=ans*a%p;
}
b>>=1;
a=a*a%p;
}
return ans;
}
signed main(){
int a;
cin>>a;
if(a%p==0){
cout<<-1<<'\n'; //a可以被p整除
}else{
cout<<power(a,p-2,p)<<'\n';//求a关于p的逆元
}
return 0;
}
2、组合数
(1)求组合数C(n,m)
方法一:阶乘+逆元+快速幂求组合数
数较大时对阶乘进行初始化,直接用阶乘计算组合数
#include<iostream>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
const int N=1000005;
const int mod=998244353;
int inv[N],fac[N];
int power(int a,int b){
int ans=1;
while(b){
if(b&1) ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
void init(){
fac[0]=inv[0]=1;
for(int i=1;i<N;i++){
fac[i]=fac[i-1]*i%mod;
}
inv[N-1]=power(fac[N-1],mod-2);
for(int i=N-2;i;i--){
inv[i]=inv[i+1]*(i+1)%mod;
}
}
int C(int n,int m){
if(n<m) return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main(){
IOS;
init();
int T; cin>>T;
while(T--){
}
return 0;
}
方法二:记忆化搜索
只适合求特定一个组合数,不适合用于初始化
int C(int n,int m){
if(n<m) return 0;
if(!m||m==n) return 1;
if(C_[n][m]) return C_[n][m];
return C_[n][m]=(C(n-1,m)+C(n-1,m-1))%mod;
}
方法三:递推公式
可用于较小数的初始化,较大的数时会爆栈
void C(int N){
for(int i=0;i<N;i++){
C[i][0]=C[i][i]=1;
}
for(int i=1;i<N;i++){
for(int j=1;j<i;j++){
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
}
}
(2)组合数求概率
共有n场比赛,每场比赛的胜率均为a/b,每次获胜得到k分,求总得分的期望值
,即
#include<iostream>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
const int N=1000005;
const int mod=998244353;
int inv[N],fac[N];
int power(int a,int b){
int ans=1;
while(b){
if(b&1) ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
void init(){
fac[0]=inv[0]=1;
for(int i=1;i<N;i++){
fac[i]=fac[i-1]*i%mod;
}
inv[N-1]=power(fac[N-1],mod-2);
for(int i=N-2;i;i--){
inv[i]=inv[i+1]*(i+1)%mod;
}
}
int C(int n,int m){
if(n<m) return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main(){
IOS;
init();
int T; cin>>T;
while(T--){
int n,a,b,k; cin>>n>>a>>b>>k;
int win=a,lose=power(b-a,n-1),invba=power(b-a,mod-2),invbn=power(power(b,n),mod-2),ans=0;
for(int i=1;i<=n;i++){
int pro=win*lose%mod*invbn%mod;
ans=(ans+pro*C(n,i)%mod*k%mod)%mod;
win=win*a%mod;
lose=lose*invba%mod;
k=(k+k)%mod;
}
cout<<ans<<'\n';
}
return 0;
}
3、高精度sqrt
(1)二分法
int safe_sqrt(int x){
int l=0,r=2*sqrtl(x),mid;
while(l<=r){
mid=(l+r)>>1;
if(mid*mid>=x){
r=mid-1;
}else{
l=mid+1;
}
}
return l;
}
(2)递加递减
int safe_sqrt(int x){
int ret=sqrtl(x);
while((ret+1)*(ret+1)<=a) ret++;
while(ret*ret>a) ret--;
return ret;
}
4、快速幂
int power(int a,int b,int mod){
int ans=1;
while(b){
if(b&1){
ans=ans*a%mod;
}
b>>=1;
a=a*a%mod;
}
return ans;
}
5、欧拉函数
方法一:埃氏筛
const int N=200005;
int phi[N];
void euler(int n){
for(int i=1;i<=n;i++){
phi[i]=i;
}
for(int i=2;i<=n;i++){
if(phi[i]=i){
for(int j=i;j<=n;j+=i){
phi[j]=phi[j]/i*(i-1);
}
}
}
}
方法二:欧拉筛
const int N=200005;
int phi[N],num[N];
bool isnp[N];
void euler(int n){
int ant=0;
phi[1]=1;
for(int i=2;i<=n;i++){
if(!isnp[i]){
num[++ant]=i;
phi[i]=i-1;
}
}
for(int j=1;j<=ant&&i*num[j]<=n;j++){
isnp[i*num[j]]=1;
if(!i%num[j]){
phi[i*num[j]]=phi[i]*num[j];
break;
}
phi[i*num[j]]=phi[i]*phi[num[j]];
}
}
6、 质数筛
方法一:欧拉筛
初始化1—N的素数
void euler()
{
for (int i = 2; i <= N; i++)
{
if (!isnp[i])
prim[++cnt] = i;
for (int j = 1; j <= cnt && i * prim[j] <= N; j++)
{
isnp[i * prim[j]] = 1;
if (i % prim[j] == 0)
break;
}
}
}
7、质数判断
bool prime(int n){
if(n==2||n==3){
return 1;
}
if(n%6!=1&&n%6!=5){
return 0;
}
for(int i=5;i*i<=n;i+=6){
if(n%i==0||n%(i+2)==0){
return 0;
}
}
return 1;
}
8、欧拉常数
求1+1/2+1/3+1/4+1/5+1/6+...1/n
#include<isotream>
#include<cmath>
#define int long long
using namespace std;
const double euler=0.57721566490153286060651209;
signed main(){
int n; cin>>n;
double ans=euler+log(n);
return 0;
}
9、线性基
形式一:数组
1、处理线性基
bool add(int x){
for(int i=62;i>=0;i--){
if(x>>i){
if(b[i]){
x^=b[i];
}else{
b[i]=x;
return 1;
}
}
}
return 0;
}
2、最大异或和
int maxx(){
int ans=0;
for(int i=62;i>=0;i--){
ans=max(ans,ans^b[i]);
}
return ans;
}
3、最小异或和
0或者最小的d[i]
形式二:容器
vector<int> b;
void add(int x){
for(auto i:b){
x=min(x,b^x);
}
for(auto &i:b){
i=min(i,i^x);
}
if(x){
b.push_back(x);
}
}
10.二分
// 寻找最小值
#include <iostream>
using namespace std;
int main()
{
int l = 1, r = 1e9, mid, ans;
while (l <= r)
{
mid = l + r >> 1;
if (check(mid))
{
ans = mid;
r = mid - 1;
}
else
{
l = mid + 1;
}
}
cout << ans;
}
// 寻找最大值
#include <iostream>
using namespace std;
int main()
{
int l = 1, r = 1e9, mid, ans;
while (l <= r)
{
mid = l + r >> 1;
if (check(mid))
{
ans = mid;
l = mid + 1;
}
else
{
r = mid - 1;
}
}
cout << ans;
}
二、数据结构
1、并查集
(1)普通并查集
优化一:路径压缩(会破坏树结构!!!
#include<iostream>
using namespace std;
const int N=200005;
int fa[N];
int n,m;
void init(){
for(int i=1;i<=n;i++){
fa[i]=i;
}
}
int find(int x){
return fa[x]==x?x:(fa[x]=find(fa[x]));
}
void merge(int x,int y){
fa[find(x)]=find(y);
}
int main(){
cin>>n>>m;
init();
for(int i=1;i<=m;i++){
int u,v; cin>>u>>v;
merge(u,v);
}
return 0;
}
优化二:启发式合并(不压缩路径,保持树结构!!!
形式1:按高合并
#include<iostream>
using namespace std;
const int N=200005;
int fa[N],ra[N];
int n,m;
void init(){
for(int i=1;i<=n;i++){
fa[i]=i;
ra[i]=1;
}
}
int find(int x){
return fa[x]==x?x:find(fa[x]);
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy) return;
if(ra[fx]>ra[fy]){
fa[fy]=fx;
}else{
fa[fx]=fy;
if(ra[fx]==ra[fy]) ra[fy]++;
}
}
int main(){
cin>>n>>m;
init();
for(int i=1;i<=m;i++){
int u,v; cin>>u>>v;
merge(u,v);
}
return 0;
}
形式2:按集合大小合并
#include<iostream>
using namespace std;
const int N=200005;
int fa[N],sz[N];
int n,m;
void init(){
for(int i=1;i<=n;i++){
fa[i]=i;
sz[i]=1;
}
}
int find(int x){
return fa[x]==x?x:(fa[x]=find(fa[x]));
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy) return;
if(sz[fx]>sz[fy]){
sz[fx]+=sz[fy];
fa[fy]=fx;
}else{
sz[fy]+=sz[fx];
fa[fx]=fy;
}
}
int main(){
cin>>n>>m;
init();
for(int i=1;i<=m;i++){
int u,v; cin>>u>>v;
merge(u,v);
}
return 0;
}
优化三: 压缩路径+启发式合并
#include<iostream>
using namespace std;
const int N=200005;
int fa[N],ra[N];
int n,m;
void init(){
for(int i=1;i<=n;i++){
fa[i]=i;
ra[i]=1;
}
}
int find(int x){
return fa[x]==x?x:(fa[x]=find(fa[x]));
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy) return;
if(ra[fx]>ra[fy]){
fa[fy]=fx;
}else{
fa[fx]=fy;
if(ra[fx]==ra[fy]) ra[fy]++;
}
}
int main(){
cin>>n>>m;
init();
for(int i=1;i<=m;i++){
int u,v; cin>>u>>v;
merge(u,v);
}
return 0;
}
(2)带权并查集
(3)种类并查集——维护循环对称关系
两种关系:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=200005;
int n,m;
int fa[N],ra[N];
struct Node{
int u;
int v;
int w;
}a[N];
bool cmp(Node x,Node y){
return x.w>y.w;
}
void init(){
for(int i=1;i<=2*n;i++){
fa[i]=i;
ra[i]=1;
}
}
int find(int x){
return fa[x]==x?x:(fa[x]=find(fa[x]));
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy) return;
if(ra[fx]>ra[fy]){
fa[fy]=fx;
}else{
fa[fx]=fy;
if(ra[fx]==ra[fy]) ra[fy]++;
}
}
int main(){
cin>>n>>m;
init();
for(int i=0;i<m;i++){
cin>>a[i].u>>a[i].v>>a[i].w;
}
sort(a,a+m,cmp);
for(int i=0;i<m;i++){
if(find(a[i].u)==find(a[i].v)){
cout<<a[i].w;
break;
}
merge(a[i].u,a[i].v+n);
merge(a[i].u+n,a[i].v);
if(i==m-1){
cout<<"0";
}
}
return 0;
}
三种关系:
#include<iostream>
using namespace std;
const int N=200005;
int fa[N],ra[N];
int n,m;
void init(){
for(int i=1;i<=n*3;i++){
fa[i]=i;
ra[i]=1;
}
}
int find(int x){
return fa[x]==x?x:(fa[x]=find(fa[x]));
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy) return;
if(ra[fx]>ra[fy]){
fa[fy]=fx;
}else{
fa[fx]=fy;
if(ra[fx]==ra[fy]) ra[fy]++;
}
}
int main(){
int ans=0;
cin>>n>>m;
init();
for(int i=0;i<m;i++){
int op,x,y; cin>>op>>x>>y;
if(x>n||y>n){
ans++;
}else if(op==1){
if(find(x)==find(y+n)||find(x)==find(y+2*n)){
ans++;
}else{
merge(x,y);
merge(x+n,y+n);
merge(x+2*n,y+2*n);
}
}else if(op==2){
if(find(x)==find(y)||find(x)==find(y+2*n)){
ans++;
}else{
merge(x,y+n);
merge(x+n,y+2*n);
merge(x+2*n,y);
}
}
}
cout<<ans<<'\n';
return 0;
}
(3)可撤销并查集(看不懂。。。
#include<iostream>
#include<vector>
using namespace std;
const int N=200005;
typedef pair<int,int> PII;
int fa[N],sz[N];
vector<PII>his_fa;
vector<PII>his_sz;
int n,m;
void init(){
for(int i=1;i<=n;i++){
fa[i]=i;
sz[i]=1;
}
}
int find(int x){
return fa[x]==x?x:(fa[x]=find(fa[x]));
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy) return;
if(sz[fx]>sz[fy]){
his_sz.push_back({sz[fx],sz[fx]});
sz[fx]+=sz[fy];
his_fa.push_back({fa[fy],fa[fy]});
fa[fy]=fx;
}else{
his_sz.push_back({sz[fy],sz[fy]});
sz[fy]+=sz[fx];
his_fa.push_back({fa[fx],fa[fx]});
fa[fx]=fy;
}
}
void roll(int h){
while(his_fa.size()>h){
his_fa.back().first=his_fa.back().second;
his_fa.pop_back();
his_sz.back().first=his_sz.back().second;
his_sz.pop_back();
}
}
int main(){
cin>>n>>m;
init();
for(int i=1;i<=m;i++){
int u,v; cin>>u>>v;
merge(u,v);
}
return 0;
}
2、启发式合并
小集合合并到大集合!!!
void merge(vector<int>&a,vector<int>&b){
if(a.size()>b.size()){
for(auto v:b) a.push_back(b);
}else{
for(auto v:a) b.push_back(a);
}
}
3、字符串哈希
Base Mod
131 1e9+7
233 1e9+7
31 666623333
形式一:单哈希
for(int j=0;j<m;j++){
a[i]=(a[i]*base+s[j]-'a'+1)%mod;
}
4、线段树
(1)单点修改,区间查询
#include<iostream>
#include<cstring>
using namespace std;
const int N=50005;
int a[N],sum[N<<2];
void push_up(int root){
int rt=root<<1;
sum[root]=sum[rt]+sum[rt|1];
}
void build(int root,int l,int r){
if(l==r){
sum[root]=a[l];
return;
}
int rt=root<<1,mid=l+r>>1;
build(rt,l,mid);
build(rt|1,mid+1,r);
push_up(root);
}
void update(int root,int l,int r,int pos,int val){
if(l==r){
sum[root]+=val;
return;
}
int rt=root<<1,mid=l+r>>1;
if(pos<=mid){
update(rt,l,mid,pos,val);
}else{
update(rt|1,mid+1,r,pos,val);
}
push_up(root);
}
int query(int root,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){
return sum[root];
}
push_down(root,l,r);
int rt=root<<1,mid=l+r>>1,ans=0;
if(ql<=mid){
ans+=query(rt,l,mid,ql,qr);
}
if(mid<qr){
ans+=query(rt|1,mid+1,r,ql,qr);
}
return ans;
}
int main(){
int T; cin>>T;
for(int i=1;i<=T;i++){
memset(sum,0,sizeof(sum));
memset(lazy,0,sizeof(lazy));
int n; cin>>n;
for(int j=1;j<=n;j++){
cin>>a[j];
}
build(1,1,n);
printf("Case %d:\n",i);
while(1){
string s; cin>>s;
if(s[0]=='E'){
break;
}
int l,r; cin>>l>>r;
if(s[0]=='A'){
update(1,1,n,l,l,r);
}else if(s[0]=='S'){
update(1,1,n,l,l,-r);
}else{
cout<<query(1,1,n,l,r)<<'\n';
}
}
}
return 0;
}
(2)区间修改,区间查询
#include<iostream>
#include<cstring>
using namespace std;
const int N=50005;
int a[N],sum[N<<2],lazy[N<<2];
void push_up(int root){
int rt=root<<1;
sum[root]=sum[rt]+sum[rt|1];
}
void push_down(int root,int l,int r){
if(lazy[root]){
int rt=root<<1,mid=l+r>>1;
lazy[rt]+=lazy[root];
lazy[rt|1]+=lazy[root];
sum[rt]+=lazy[root]*(mid-l+1);
sum[rt|1]+=lazy[root]*(r-mid);
lazy[root]=0;
}
}
void build(int root,int l,int r){
if(l==r){
sum[root]=a[l];
return;
}
int rt=root<<1,mid=l+r>>1;
build(rt,l,mid);
build(rt|1,mid+1,r);
push_up(root);
}
void update(int root,int l,int r,int ql,int qr,int val){
if(ql<=l&&r<=qr){
sum[root]+=val*(r-l+1);
lazy[root]+=val;
return;
}
int rt=root<<1,mid=l+r>>1;
push_down(root,l,r);
if(ql<=mid){
update(rt,l,mid,ql,qr,val);
}
if(mid<qr){
update(rt|1,mid+1,r,ql,qr,val);
}
push_up(root);
}
int query(int root,int l,int r,in