基本算法

1.位运算

快速幂,快速乘

int power(int x,int b,int p){ //x^b mod p 
    int ans = 1%p;
    while(b){  
        if(b&1){
           	ans = 1ll*x*ans%p;
		}
		a=1ll*a*a%p;
		b>>=1;
   }
   return ans;
}
int mul(int a,int b,int p){//a*b%p
	int ans = 0%p;
	while(b){
		if(b&1)ans=(ans+a)%p
		b>>=1;
		a=a+a;
		a%=p;
	}
	return ans;
}

成对变换(常用于反向边)

若x为奇数 x xor 1 =x - 1

若x为偶数 x xor 1 =x+1

此玩意常用于反向边的变化(将正向边存n,反向边存n+1 (n&1==0))

lowbit

#define lowbit(x) (x&-x)

意为取出x二进制下最后一位(加个log2就是第几位)(2^k)mod 37互不相等(k∈[0,35])

前缀和与差分

一维前缀和

s u m i = s u m i − 1 + a i sum_i=sum_{i-1}+a_i sumi=sumi1+ai

此时区间[l,r]的和为:

s u m r − s u m l − sum_r - sum_{l-} sumrsuml

二维前缀和

s u m [ i ] [ j ] = s u m [ i − 1 ] [ j ] + s u m [ i ] [ j − 1 ] + a [ i ] [ j ] − s u m [ i − 1 ] [ j − 1 ] sum[i][j]=sum[i-1][j]+sum[i][j-1]+a[i][j]-sum[i-1][j-1] sum[i][j]=sum[i1][j]+sum[i][j1]+a[i][j]sum[i1][j1]

那么对于
∑ i n ∑ j m a i , j = s u m n , m − s u m i − 1 , m − s u m n , j − 1 + s u m i − 1 , j − 1 \sum_i^n \sum_j^m a_{i,j} = sum_{n,m}-sum_{i-1,m}-sum_{n,j-1}+sum_{i-1,j-1} injmai,j=sumn,msumi1,msumn,j1+sumi1,j1

差分

对于数列 A A A
它的差分数列 B B B定义为:
B i = A i − A i − 1 B_i = A_i-A_{i-1} Bi=AiAi1
此时 B B B的前缀和就是 A A A

对于A的区间加减操作可以变为对B的单点修改

例如:将 A l A_l Al A r A_r Ar全部加上 k k k

即为 B l + k , B r + 1 − k B_l+k,B_{r+1}-k Bl+k,Br+1k

二分

整数二分

	//在单调序列a中查找第一个>=x的 
int find(int x){
   int l=1,r=n; 
   while(l<r){
   	int mid = l+r>>1;
   	if(a[mid]>=x)r=mid;//若是严格大于就删掉等号 
    //这个判断可以换成check
   	else l=mid +1;
   }
   return a[l]
} 
//查找第一个<=x的
int findself(int x){
   int l=1,r=n;
   while(l<r){
   	int mid = l+r+1 >>1;
   	if (a[mid]<=x)l=mid;
   	else r=mid-1; 
   }
   return a[l];
} 

实数二分

/*实数二分*/
//有两种方式 
//方式1,设置精度(eps),以l+eps<r为条件。
//一般来说,需要保留k位小数时,eps取10^-(k+2)
double find(){
	double l=s,r=t;//最大和最小
	double eps=1;//10^0
	for(int i=1;i<=k+2;i++) eps/=10;
	while(l+eps<r){
		double mid =l+r>>1;
		if(check(mid))r=mid;
		else l=mid;
	}
	return l;
} 
// 方式2,在不容易确定精度时使用
//使用循环固定二分次数,次数要计算好,不要TLE,这种方法得到的精度一般比eps更高
double find1(){
	double l=s,r=t;
	for(int i=1;i<=100;i++){//二分一百次 
		double mid = l+r>>1;
		if(check(mid)) r=mid;
		else l=mid;
	}
	return l;
} 

排序

离散化

代码附上:

void solve(){
 sort(a+1,a+n+1);
 for(int i=1;i<=n;i++){
 	if(i==1||a[i]!=a[i-1]){
 		b[++m]=a[i];
 	}
 }
}
int query(int x){//查找x映射的谁 
 return lower_bound(b+1,b+m+1,x)-b; 
}

中位数

性质1:在序列 A A A中,记A的中位数为 x x x,则 x x x使得

∑ i = 1 n ∣ a i − x ∣ \sum_{i=1}^n |a_i-x| i=1naix
最小
在有2n项的时候,x在排名为n~n+1之间都可以,

在有2n-1项时,x是排名为n的数

动态维护中位数:
对顶堆算法:

开一个大根堆和一个小根堆:

设有M个数,则将排名1~M/2的存入大根堆,M/2+1存入小根堆

如果有一个堆元素过多,就取出堆顶放入另一个堆,这样小根堆的堆顶就是中位数

第K大数 O ( n ) O(n) O(n)算法
类似快排思想:

找一个基准数,把比它大的放在左边,比它小的放在右边。如果最终该数数所在 位置刚好是第k个,那么它就是答案。否则只需要在该数组的左半部分找,或者右半部分,此时k减去左半部分的数量。
逆序对
归并求逆序对


//归并排序及求逆序对
#include<bits/stdc++.h>
using namespace std;
#define N 1000005
int a[N] ,b[N];//b为辅助数组
long long cnt;
void merge_sort(int l , int r)
{
    if(r-l > 0)//如果整个区间中元素个数大于1,则继续分割
    {
        int mid = (l+r) / 2 ;
        int i = l; //辅助数组的下标
        int p = l , q = mid+1;
        merge_sort(l , mid);
        merge_sort(mid+1 , r);
        //printf("%d-%d  %d-%d\n",p,mid ,q ,r);
        while(p<=mid || q<=r)//左右两部分只要有一部分不为空
        {
            if(q>r || (p<=mid && a[p]<=a[q]))//从左半数组复制到辅助数组
                b[i++] = a[p++];
            else
            {
                b[i++] = a[q++];
                cnt += mid -p +1;//将逆序对的个数累加起来
            }
        }
        for(i = l ; i <= r; i++)//将b中排好序的元素复制到a中
            a[i] = b[i];
    }
}
int main()
{
    int n;
    while(cin >> n)
    {
        for(int i = 1 ; i <= n; i ++)
            cin >> a[i];
        cnt = 0;
        merge_sort(1 , n);
        for(int i = 1; i <= n; i++)
            cout << a[i] << " ";
        cout << endl;
        cout << "逆序对有:" << cnt <<endl;
    }
    return 0;

代码来源


倍增

基本思想

将可行答案成倍增长,得到一个 O ( l o g 2 N ) O(log_{2}N) O(log2N)的算法

代码长这样

int solve(){
 int ans = 0,p=1;
 while(p){
 	if(check(ans+p))ans+=p,p*=2;
 	else {
 		p/=2;
 	}
 }
}

ST表
ST表是处理RMQ问题的利器,在 O ( N l o g 2 N ) O(Nlog_2N) O(Nlog2N)的时间预处理之后可以 O ( 1 ) O(1) O(1)回答任意一个区间最值

具体思想

F i , j 表示子区间 [ i , i + 2 j − 1 ] 的最大值 F_{i,j}表示子区间[i,i+2^j-1]的最大值 Fi,j表示子区间[i,i+2j1]的最大值

边界: ∀ F i , 0 = A i \forall F_{i,0}=A_i Fi,0=Ai

有公式
F i , j = m a x ( F i , j − 1 , F i + 2 j , j − 1 ) F_{i,j}=max(F_{i,j-1},F_{i+2^j,j-1}) Fi,j=max(Fi,j1,Fi+2j,j1)
故代码为:

void ST(){
   for(int i=1;i<=n;i++)f[i][0]=a[i];
   int t = log(n)/log(2)+1;
   for(int i=1;i<t;i++){
   	for(int j=1;j<=n-(1<<i)+1;j++){
   		f[j][i]=max(f[j][i-1],f[j+(1<<(i-1))][i-1]);
   	}
   }
}
int ST_query(int l,int r){
   int k= log(r-l+1)/log(2);
   return max(f[l][k],f[r-(1<<k)+1][k]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值