Note2

目录

    • 分治
      • · 归并排序(+求逆序对)
      • · 最近点对问题(模板)
      • · cdq分治
        • 三维偏序问题
        • 动态逆序对
    • 一些实用的东西
    • 离散化
    • 数论
      • · gcd+lcm
      • · 快速积
      • · 快速幂
      • · 分解质因数(快速求一个数的因子个数)
      • · 等比数列求和
      • · 有重复数的排列
      • · [n/1]+[n/2]+[n/3]+…+[n/k]模板 [ ]整除
      • · 欧拉筛法
      • · Miller_Rabin随机数测试算法(判断大数是不是素数)
      • · 威尔逊定理
      • · 扩展欧几里得 (求ax+by=d的整数解)
      • · 逆元
      • · 组合数 C n m C^m_{n} Cnm
      • · 同余方程
      • · 快速傅里叶变换(FFT)
      • · 类欧几里得( ∑ [(ax+b)/c] )

分治

· 归并排序(+求逆序对)

void memsort(int s,int t){
  int m,i,j,k;
  if(s==t)return;
  m=(s+t)/2;
  memsort(s,m);
  memsort(m+1,t);
  i=s;j=m+1;k=s;
  while(i<=m&&j<=t){
    if(a[i]>a[j])
      r[k++]=a[i++];     //ans=ans+t-j+1;(逆序对)
    else r[k++]=a[j++];
  }
  while(i<=m)r[k++]=a[i++];
  while(j<=t)r[k++]=a[j++];
  for(int i=s;i<=t;i++)a[i]=r[i];
}

· 最近点对问题(模板)

题目传送门

	作法: 按x坐标排序,不断往下二分。
			 合并:d=min(dl,dr);[mid.x-d,mid.x+d]内所有temp点并求两两之间最小距离dt
			 	  d=min(d,dt);
代码如下
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int INF=2<<20;
#define LL long long
#define M 201000

int n,temp[M];
struct point{
	double x,y;
}s[M];

inline int cmpx(point a,point b){return a.x<b.x||(a.x==b.x&&a.y<=b.y);}
inline int cmpy(int a,int b){
	return s[a].y<s[b].y||(s[a].y==s[b].y&&s[a].x<=s[b].x);
}
inline double min_(double a,double b){return a<b?a:b;}

inline double dist(int i,int j){
	double x=(s[i].x-s[j].x)*(s[i].x-s[j].x);
	double y=(s[i].y-s[j].y)*(s[i].y-s[j].y);
	return sqrt(x+y);
}
double deal(int l,int r){
	double d=INF;
	if(l==r)return d;
	if(l+1==r)return dist(l,r);
	int mid=(l+r)>>1;
	d=min_(deal(l,mid),deal(mid+1,r));
	int k=0;
	for(int i=l;i<=r;i++) //找两个区间中间[mid-d,mid+d]之间的所有点
		if(abs(s[mid].x-s[i].x)<d)temp[++k]=i;
	sort(temp+1,temp+1+k,cmpy);
	double dt;
	for(int i=1;i<=k;i++)//合并中间有必要合并的点的距离,更新最小值
		for(int j=i+1;j<=k&&s[temp[j]].y-s[temp[i]].y<d;j++){
			dt=dist(temp[i],temp[j]);
			if(d>dt)d=dt;
		}
	return d;
}
	
	
int main(){
//	freopen("testdata.in","r",stdin);
	while(cin>>n,n!=0){
		for(int i=1;i<=n;i++)scanf("%lf%lf",&s[i].x,&s[i].y);
		sort(s+1,s+1+n,cmpx);
		printf("%.2lf\n",deal(1,n)/2);
	}
//	fclose(stdin);	
}

· cdq分治

三维偏序问题

引例:二维偏序
三维偏序模板题:陌上花开
思路:

对第一维排序,对第二维归并排序,第三维用树状数组维护
[l,mid]放树状数组里,[mid+1,r]计算前面对后面的贡献
!要预处理相同的三元组后面对前面的贡献(因为cdq分治的时候后面的三元组不会对前面的三元组产生贡献)

//洛谷P3810陌上花开
#include
using namespace std;
const int N=100007,M=200007;
struct node{
	int x,y,z,v;	//v是小于当前的三元组个数 
	bool operator <(const node &o)const{
		return x<o.x||(x==o.x&&y<o.y)||(x==o.x&&y==o.y&&z<o.z);
	}
}a[N],t[N];
int n,m,c[M],ans[N];
int lowbit(int x){return x&(-x);}
void updata(int k,int val){
	while(k<=m){
		c[k]+=val;
		k+=lowbit(k);
	}
}
int getsum(int k){
	int sum=0;
	while(k){
		sum+=c[k];
		k-=lowbit(k);
	}
	return sum;
}
void cdq(int l,int r){//类似于归并排序 
	int mid=(l+r)>>1;
	if(l==r)return;
	cdq(l,mid);cdq(mid+1,r);
	int i=l,j=mid+1,k=l;
	while(i<=mid&&j<=r){
		if(a[i].y<=a[j].y)updata(a[i].z,1),t[k++]=a[i++];
		else a[j].v+=getsum(a[j].z),t[k++]=a[j++];
	}
	while(i<=mid)updata(a[i].z,1),t[k++]=a[i++];
	while(j<=r)a[j].v+=getsum(a[j].z),t[k++]=a[j++];
	for(int i=l;i<=mid;i++)updata(a[i].z,-1);//清空树状数组 
	for(int i=l;i<=r;i++)a[i]=t[i];
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
	sort(a+1,a+1+n);
	//三元组可能有重复,所以预先算好相等的三元组后面对前面的贡献 
	int cnt=1;
	node temp=a[n];
	for(int i=n-1;i;i--){
		if(temp.x==a[i].x&&temp.y==a[i].y&&temp.z==a[i].z)
			a[i].v+=cnt++;
		else{
			temp=a[i];
			cnt=1;
		}
	}
 
	cdq(1,n);
	for(int i=1;i<=n;i++)ans[a[i].v]++;
	for(int i=0;i<n;i++)printf("%d\n",ans[i]);
}

动态逆序对

CQOI2011动态逆序对
Note2_第1张图片

ans[i]表示在i时刻恰好删除的有多少对逆序对
time[i]val[j] pos[i] time[i]>time[j] val[i]>val[j] pos[i]

#include
using namespace std;
typedef long long LL;
const int N=100007;
struct node{
	int val,time,pos;	//time是删除的时间
}q[N],t[N];
LL ans[N],s;
int a[N],c1[N],c2[N],n,m,pos[N],del[N],t1[N],t2[N];
//c1,c2是两个树状数组,t1,t2是清空树状数组用的
int lowbit(int x){return x&(-x);}
int updata(int k,int val,int c[]){for(;k<=n;k+=lowbit(k))c[k]+=val;}
int getsum(int k,int c[]){int sum=0;for(;k;k-=lowbit(k))sum+=c[k];return sum;}
void cdq(int l,int r){
	if(l==r)return;
	int mid=(l+r)>>1;
	cdq(l,mid);cdq(mid+1,r);
	int i=l,j=mid+1,k=l,k1=0,k2=0;
	while(i<=mid&&j<=r){
		if(q[i].time>q[j].time){
			updata(q[i].val,1,c1);
			ans[q[i].time]+=getsum(q[i].val-1,c2);
			t1[++k1]=q[i].val;t[k++]=q[i++];
		}else{
			updata(q[j].val,1,c2);
			ans[q[j].time]+=getsum(n,c1)-getsum(q[j].val,c1);
			t2[++k2]=q[j].val;t[k++]=q[j++];
		}
	}
	while(i<=mid){
		updata(q[i].val,1,c1);
		ans[q[i].time]+=getsum(q[i].val-1,c2);
		t1[++k1]=q[i].val;t[k++]=q[i++];
	}
	while(j<=r){
		updata(q[j].val,1,c2);
		ans[q[j].time]+=getsum(n,c1)-getsum(q[j].val,c1);
		t2[++k2]=q[j].val;t[k++]=q[j++];
	}
	for(int i=1;i<=k1;i++)updata(t1[i],-1,c1);
	for(int i=1;i<=k2;i++)updata(t2[i],-1,c2);
	for(int i=l;i<=r;i++)q[i]=t[i];
}		
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),pos[a[i]]=i;
	for(int i=1,x;i<=m;i++){
		scanf("%d",&x);
		del[pos[x]]=i;
	}
	int k=m;
	for(int i=1;i<=n;i++){
		if(!del[i])del[i]=++k;
		q[i]=(node){a[i],del[i],i};
	}
	cdq(1,n);
	for(int i=1;i<=n;i++)s+=ans[i];
	for(int i=1;i<=m;i++){
		printf("%lld\n",s);
		s-=ans[i];
	}
}

一些实用的东西

const int Inf=...  0x7fffffff2^31-10x3f3f3f3f10^8的较大数
数组初始化  
memset(a,0,sizeof(a));//int
	63较大,-64负较大,127很大 -127/128负很大,-10
}
next_permutation(a+1,a+n+1); //从小到大生成a数组的全排列(可以自定义优先级)
//生成到最后一个时返回0,使用前先排序

random_shuffle(a+1,a+1+n);//随机排列,打乱顺序

sort(a,a+6);                       //按从小到大排序 (从大到小的话找的东西相反)
int pos1=lower_bound(a,a+6,x)-num;    //返回数组中第一个大于或等于被查数的下标
int pos2=upper_bound(a,a+6,x)-num;    //返回数组中第一个大于被查数的下标


1. __gcd(x, y)
求两个数的最大公约数,如__gcd(6, 8) 就返回2。在 algorithm 库中。是不是很方便?

2. reverse(a + 1, a + n + 1)
将数组中的元素反转。a 是数组名,n是长度,跟 sort 的用法一样。值得一提的是,对于字符型数组也同样适用。也在 algorithm 库中。

3. unique(a + 1, a + n + 1)
去重函数。跟sort的用法一样。不过他返回的值是最后一个数的地址,所以要得到新的数组长度应该这么写: _n = unique(a + 1, a + n + 1) - a - 1.

6.fill(a + 1, a + n + 1, x)
将数组a中的每一个元素都赋成x,跟memset的区别是,memset是一个字节一个字节赋值,fill是一个元素一个元素赋值!(省掉一层for……)

离散化

对a[n]数组离散化,O(nlogn)

#include
using namespace std;
const int N=100007;
int n,a[N],A[N];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	//离散化
	for(int i=1;i<=n;i++)A[i]=a[i];
	sort(A+1,A+1+n);
	int size=unique(A+1,A+1+n)-A-1;
	for(int i=1;i<=n;i++)a[i]=lower_bound(A+1,A+1+size,a[i])-A;
	//
	
	for(int i=1;i<=n;i++)printf("%d ",a[i]);
	
}
	

数论

· gcd+lcm

	int gcd(int a, int b){
	    while (b)
        b ^= a ^= b ^= a %= b;
	    return a;
	}
	int lcm(int a,int b)
  		{return a*b/gcd(a,b);}
  		//algorithm库里有__gcd(a,b)函数可以直接用

· 快速积

LL qmul(LL a,LL b){
    LL re=0;
    while(b){
        if(b&1)re=(re+a)%MOD;
    	a=(a+a)%MOD;
        b>>=1;
    }
    return re;
}

· 快速幂

注意:当mod为质数时,对指数e取余是%(mod-1),然后取qpow(x,e+mod-1)为结果 [费马小定理]

LL qpow(LL x,LL e){
	LL re=1;
	while(e){
		if(e&1){
			re=re*x%MOD;
		}
		x=x*x%MOD;
		e>>=1; 
	} 
	return re%MOD;
}

· 分解质因数(快速求一个数的因子个数)

/* 分解质因数 */
void Solve(LL n)  {  
    p.clear();  
    for(LL i=2;i*i<=n;i++){  
        if(n%i==0){  
            p.push_back(i);  
            while(n%i==0) n/=i;  
        }  
    }  
    if(n>1)  
        p.push_back(n);  //这个不可以缺少
}

然后,n的因子个数等于(p[0]+1) * (p[1]+1) * (p[2]+1) * … * (p[k]+1)

· 等比数列求和

O(log2n)

 LL cal(LL p,LL n){  ///这里是递归求解等比数列模板 1 + p + p^2 +...+ p^n
    if(n==0) return 1;
    if(n&1){///(1+p+p^2+....+p^(n/2))*(1+p^(n/2+1));
         return (1+qpow(p,n/2+1))*cal(p,n/2)%MOD;
    }
    else { ///(1+p+p^2+....+p^(n/2-1))*(1+p^(n/2+1))+p^(n/2);
         return (qpow(p,n/2)+(1+qpow(p,n/2+1))*cal(p,n/2-1))%MOD;
    }
}

· 有重复数的排列

k个不同的数,每个数a1,a2,…,ak个,不同的排列数为

( a 1 + a 2 + . . . + a k ) ! a 1 ! ∗ a 2 ! ∗ . . . ∗ a k ! \frac{(a1+a2+...+ak)!}{a1!*a2!*...*ak!} a1!a2!...ak!(a1+a2+...+ak)!

· [n/1]+[n/2]+[n/3]+…+[n/k]模板 [ ]整除

LL solve(LL n,LL k){// [n/1]+[n/2]+[n/3]+...+[n/k]    []向下取整 
	LL re=0;
    for(LL l=1,r;l<=min(n,k);l=r+1){
        r=min(n/(n/l),k);
        re=(re+n/l%MOD*((r-l+1)%MOD))%MOD;
    }
    return re;
}

· 欧拉筛法

//O(n)   
#define N 200000 //范围    
int su[N],cnt,u[N];  //u[i]==0是素数,su[]是素数表 
void oula(){
    for(int i=2;i<N;i++){   
        if(!u[i])                 
            su[++cnt]=i;    
        for(int j=1;j<=cnt&&i*su[j]<N;j++){
                u[i*su[j]]=1;     
         	    if(i%su[j]==0)break;  //prime[j]是i的最小质因子
        }          
    }              
}

· Miller_Rabin随机数测试算法(判断大数是不是素数)

LL qmul(LL a,LL b,LL MOD){
    LL re=0;
    while(b){
        if(b&1)re=(re+a)%MOD;
    	a=(a+a)%MOD;
        b>>=1;
    }
    return re;
}
LL qpow(LL x,LL e,LL MOD){
	LL re=1;
	while(e){
		if(e&0x01){
			re=qmul(re,x,MOD);
		}
		x=qmul(x,x,MOD);
		e>>=1; 
	} 
	return re%MOD; 
} 
bool Miller_Rabin(LL x){//判断x是不是素数,是返回1
	if(x==2) return 1;
	if(!(x&1)||x==1) return 0; 
    bool pass;
    LL d=x-1,m;
    while(!(d&1)) d>>=1;LL tmp=d;
    for(int i=1;i<=10;++i){//随机数检验10次基本上能确定,longlong以内可以用2,3,5,7,11,13,17,19检验,出错率0%
    	d=tmp;pass=0;
		m=qpow(rand()%(x-2)+2,d,x);
       	if(m==1) continue;else 
		for(;d<x&&d>=0;m=qmul(m,m,x),d<<=1)
			if(m==x-1){pass=1;break;}
        if(!pass) return 0;
    }
    return 1;
}

· 威尔逊定理

p为素数当且仅当(p-1)! ≡ p-1 (mod p)

· 扩展欧几里得 (求ax+by=d的整数解)

步骤一:令c=gcd(a,b),如果d%c==0 有解,否则无解
步骤二:a,b,d同除c,得到a`x+b`y=d`
步骤三:用扩欧求a`x`+b`x`=1的解x`,y`,原解x=x`*d`,y=y`*d`
[此时的x,y满足(|x|+|y|)是最小正整数解,x或y都有可能是负数]
	若求x的最小正整数解 则x1=(x%b+b)%b, y1=(d-a*x1)/b
	若求y的最小正整数解 则y2=(y%a+a)%a, x2=(d-b*y2)/a
	若求(x+y)的最小正整数解 则解为min(x1+y1,x2+y2)
扩欧代码:
void ex_gcd(LL a,LL b,LL &x,LL &y){
	if(b==0){x=1;y=0;return;}
	ex_gcd(b,a%b,y,x);
	y-=(a/b)*x;
}

· 逆元

若mod为质数,a的逆元为qpow(a,p-2)

LL qpow(LL x,LL e){
	LL re=1;
	while(e){
		if(e&1){
			re=re*x%MOD;
		}
		x=x*x%MOD;
		e>>=1; 
	} 
	return re%MOD;
}

否则如下

void ex_gcd(LL a,LL b,LL &x,LL &y){
	if(b==0){x=1;y=0;return;}
	ex_gcd(b,a%b,y,x);
	y-=(a/b)*x;
}
LL inverse(LL a,LL b){//求a模b的逆元 [a,b互质才有解]
	LL x,y;
	ex_gcd(a,b,x,y);
	return x=(x%b+b)%b;
}

· 组合数 C n m C^m_{n} Cnm

LL fac[N];
void init(){fac[0]=fac[1]=1LL;for(LL i=1;i<N;i++)fac[i]=fac[i-1]*i%MOD;}
LL qpow(LL x,LL e){
	LL re=1;
	while(e){
		if(e&1){
			re=re*x%MOD;
		}
		x=x*x%MOD;
		e>>=1; 
	} 
	return re%MOD;
}
LL C(LL n,LL m){
	LL re,up,down;
	up=fac[n];
	down=fac[m]*fac[n-m]%MOD;
	re=up*qpow(down,MOD-2)%MOD;
	return re;
}

· 同余方程

ax≡b(mod m)   两边可以同+-*  但不能同/  
[若m%gcd(a,b)==0,则a,b,m可同除gcd(a,b)  ]

若m%gcd(a,b)==0 则上式恰好有gcd(a,b)个模m不同的解,否则无解
有解时,ax≡b(mod m)的解 等价于 ax+my=b的解,用扩欧求解

· 快速傅里叶变换(FFT)

详解传送门,看不懂@_@,慎点qwq
1.多项式乘法【FFT模板题】传送门
2.A*B problem升级版传送门(数据卡高精乘)

//多项式乘法(FFT)模板
#include
using namespace std;
typedef complex<double>Complex;
typedef vector<int>vec;
const double PI=acos(-1.);
const int N=3e6;
int la,lb;
vec w;

template<class T>inline void read(T &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch<'0'||ch>'9')  {f|=(ch=='-');ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x=f?-x:x;
    return;
}//快读

void FFT(Complex *P,int n,int op){
	for(int i=1,j=0;i<n-1;i++){
		for(int s=n;j^=s>>=1,~j&s;);
		if(i<j)swap(P[i],P[j]);
	}
	Complex unit_p0;
	for(int d=0;(1<<d)<n;d++){
		int m=1<<d,m2=m*2;
		double p0=PI/m*op;
		unit_p0=Complex(cos(p0),sin(p0));
		for(int i=0;i<n;i+=m2){
			Complex unit=1;
			for(int j=0;j<m;j++){
				Complex &P1=P[i+j+m],&P2=P[i+j];
				Complex t=unit*P1;//蝴蝶效应,优化
				P1=P2-t;
				P2=P2+t;
				unit=unit*unit_p0;
			}
		}
	}
}
Complex A[N],B[N];
vec operator *(const vec &u,const vec &v){
	int n=1,p=u.size(),q=v.size(),i;
	while(n<=p+q-2)n<<=1;
	for(i=0;i<n;++i)A[i]=i<p?u[i]:0;
	for(i=0;i<n;++i)B[i]=i<q?v[i]:0;
	FFT(A,n,1);
	FFT(B,n,1);
	for(i=0;i<n;++i)A[i]*=B[i];
	FFT(A,n,-1);
	vec w(p+q-1);
	for(i=0;i<w.size();++i)
		w[i]=(int)(A[i].real()/n+0.5);
	return w;
}
vec a,b;
int main(){
	cin>>la>>lb;int x;
	for(int i=0;i<=la;i++){read(x);a.push_back(x);}
	for(int i=0;i<=lb;i++){read(x);b.push_back(x);}
	w=a*b;
	for(int i=0;i<w.size();i++)printf("%d ",w[i]);
}
	

· 类欧几里得( ∑ [(ax+b)/c] )

例题链接:[洛谷 4132 算不出的不等式]

注意代码里这个 f(a,b,c,n) 中的 n 是开区间,也就是说计算的是在这里插入图片描述

LL f(LL a,LL b,LL c,LL n) {
    if(n<=0)return 0;
    return n*(n-1)/2*(a/c)+n*(b/c)+f(c,(a*n+b)%c,a%c,(a%c*n+b%c)/c);
}

你可能感兴趣的:(My,Notes)