前缀和、二分

前缀和

  Q:给出一个长度为n的数组A[],并进行q组询问,每次询问给出一个区间[l,r],需要求区间[l,r]的数组元素和。

  A:令前缀和数组sum[i]=A[1]+...+A[i]

sum[r]=A[1]+A[2]+...+A[l-1]+A[i]+...+A[r]

A[l]+...A[r]=sum[r]-sum[l-1]

每次询问O(1),总复杂度O(q)=1

  1. 给定一个长度为N的数列,A1,A2...AN,如果其中一段连续的子序列Ai,Ai+1...Aj(i<=j)之和是K的倍数,我们就称这个区间[i,j]是K倍区间。求出这个数列中共有多少个K倍区间。
    //朴素前缀和,复杂度O(n2)
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
       scanf("%d",&a[i]);
       sum[i]=sum[i-1]+a[i];//一边读数组,一边计算sum[i],没有额外的空间复杂度
    }
    for(int i=1;i<=n;i++){
        for(int j=i;j<=n;j++){
            if((sum[j]-sum[i-1])%k==0){
                ans++;
            }
        }
    }
    
    //竞赛中1s跑10^7位数的计算
    

    考虑优化:

  • 利用公式(a-b)%k=(a%k-b%k)%k

  • 把公式(sum[j]-sum[i-1])%k==0转换成sum[j]%k==sum[i-1]%k

  • 用add[i]记录有多少个sum数组中的元素取模后为i。则从add[i]里任意挑选两个都符合sum[j]%k==sum[i-1]%k,结果为C_{add[i]}^{2}\textrm{}

    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum[i]=(sum[i-1]+a[i])%k;
        add[sum[i]]++; 
    }
    for(int i=0;i<k;i++)
    {
        ans+=add[i]*(add[i]-1)/2;
    }
    printf("%lld",ans+add[0]);
  • 最后答案为add[0]+add[i]*add[i-1]/2(为什么要再加一个add[0]?因为add[0]本来就是sum[i]%k==0的,本身算K倍区间,所以要再加上)

2.给定一个NxM的矩阵A,请你统计有多少个子矩阵(最小1x1,最大NxM)满足子矩阵中所有数的和不超过给定的整数K。

//朴素二维前缀和
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
{
    scanf("%d",&a[i][j]);
    sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
}
int ans=0;
for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        for(int x=i;x<=n;x++)
            for(int y=j;y<=m;y++)
            {
                 if(sum[x][y]-sum[x-i][y]-sum[x][y-j]+sum[x-i][y-j]<=k)
                    ans++;   
            }

printf("%d\n",ans);

二分法

算法模板

int check(int n){} //根据题目要求检查n是否满足条件

//找最小的满足check的值

int bsearch(int l,int r)

{

        while(l<r)

        {

                int mid=(l+r+1)>> 1;//除2

                if(check(mid))

                       r=mid;

                else

                       l=mid+1; 

        }

        return l;

}

//找最大的满足check的值

int bsearch(int l,int r)

{

        while(l<r)

        {

                int mid=(l+r+1)>>1;

                if(check(mid))

                {

                        l=mid;

                }

                else

                        r=mid-1;

        }

        return l;

例题:

 #include<bits/stdc++.h>

int N,K;

int H[100000],W[100000];

int w_m=0; 

int main()

{

        for(int i=1;i<=N;i++)

                scanf("%d %d",&H[i],&W[i]);

        for(int i=1;i<=N;i++)

      {

         w_m=max(w_m,H[i]);

          w_m=max(w_m,W[i]);

      }

        int l=0,r=w_m;

        while(l<r){

        int m=(l+r+1)>>1;

        if(check(m)) l=m;

        else r=m-1;

        }

        printf("%d\n",l);

}

int check(a)

{

        int cnt=0;

        for(int i=1;i<=N;i++)

        {

                cnt+=(H[i]/a)*(W[i]/a);//如果a比边长还大的话,除完就为0了,就不加

                if(cnt>=K)

                        return 1;

        }

        return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值