[AcWing蓝桥杯]之二分与前缀和(C++题解)

目录

二分

 数的范围(经典模板)

数的三次方根

机器人的跳跃问题

四平方和

分巧克力

前缀和

前缀和(经典模板)

子矩阵的和(经典模板)

激光炸弹

 K倍区间


二分


 数的范围

89. 数的范围 - AcWing题库

模板:判断需要的是二段性的左右

是第一段的末尾还是第二段的首段

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
    int n,q;
    scanf("%d%d",&n,&q);
    
    int nums[n];
    for(int i=0;i<n;i++)
        scanf("%d",&nums[i]);
    
    while(q--)
    {
        int target=0;
        scanf("%d",&target);
        int left=0;
        int right=n-1;
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]>=target) right=mid;
            else left=mid+1;
        }
        if(nums[left]!=target) cout<<"-1 -1"<<endl;
        int j=left;
        left=0;
        right=n-1;
        while(left<right)
        {
            int mid=left+1+(right-left)/2;
            if(nums[mid]<=target) left=mid;
            else right=mid-1;
        }
        //if(nums[right]!=target) cout<<"-1 -1"<<endl;
        int k=right;
        if(nums[right]==target)
            cout<<j<<" "<<k<<endl;
    }
    
    return 0;
}

优化:第一次先求出第一次出现的位置,那么就已这个位置作为寻找这个数出现的末尾位置的left,从而缩短时间

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, m;
int q[N];

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);

    for (int i = 0; i < m; i ++ )
    {
        int x;
        scanf("%d", &x);
        // 二分x的左端点
        int l = 0, r = n - 1;   // 确定区间范围
        while (l < r)
        {
            int mid = l + r >> 1;
            if (q[mid] >= x) r = mid;
            else l = mid + 1;
        }

        if (q[r] == x)
        {
            cout << r << ' ';

            // 二分x的右端点
            r = n - 1;  // 右端点一定在[左端点, n - 1] 之间
            while (l < r)
            {
                int mid = l + r + 1 >> 1;   // 因为写的是l = mid,所以需要补上1
                if (q[mid] <= x) l = mid;
                else r = mid - 1;
            }
            cout << r << endl;
        }
        else cout << "-1 -1" << endl;
    }

    return 0;
}

数的三次方根

790. 数的三次方根 - AcWing题库

核心:由于是保留到底6位小数,所以我们要精确到第6位之后,浮点数判0的方式:小于一个非常小的数即可

#include<iostream>
#include<stdio.h>
using namespace std;
int main()
{
    double x;
    cin >> x;
    double left = -10000;
    double right = 10000;
    while (right - left > 1e-8)
    {
        double mid = (right + left) / 2;
        if (mid * mid * mid > x) right = mid;
        else left = mid;
    }
    printf("%.6lf", left);

    return 0;
}

机器人的跳跃问题

730. 机器人跳跃问题 - AcWing题库

核心:可推知不管是上升还是下降:对能量的消耗都是E=2*E-h[i]

也有,当能量大于这个h数组的最大值时,那么一定可以使条件符合,那么要寻找的就是满足题意的第一个数,也就是二段性中的第二段的首段

check满足条件,那么就继续压缩right,因为要找到的是二段性中的第二段的首段

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 100010;
int h[N];
int n;

bool check(int mid)
{
    for (int i = 1; i <= n; i++)
    {
        mid = mid * 2 - h[i];
        if (mid > 1e5) return true;
        if (mid < 0) return false;
    }
    return true;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &h[i]);

    int left = 0;
    int right = 1e5;
    while (left < right)
    {
        int mid = left+(right-left)/2;
        if (check(mid)) right = mid;
        else left = mid + 1;
    }
    cout << left << endl;

    return 0;
}

四平方和

1221. 四平方和 - AcWing题库

核心:字典序最小,巧妙代换

暴力方法:

#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 2500010;

int n;

int main()
{
    cin >> n;
    for (int a = 0; a * a <= n; a ++ )
        for (int b = a; a * a + b * b <= n; b ++ )
            for (int c = b; a * a + b * b + c * c <= n; c ++ )
            {
                int t = n - a * a - b * b - c * c;
                int d = sqrt(t);
                if (d * d == t)
                {
                    printf("%d %d %d %d\n", a, b, c, d);
                    return 0;
                }
            }
}

二分:

核心:对pow(c,2)+pow(d,2)进行排序以及c和d,从而使这两个确定了最小字典序,

接着再遍历a,b确定这两个的最小字典序

#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 2500010;

struct Sum
{
    int s, c, d;
    bool operator< (const Sum &t)const
    {
        if (s != t.s) return s < t.s;
        if (c != t.c) return c < t.c;
        return d < t.d;
    }
}sum[N];

int n, m;

int main()
{
    cin >> n;

    for (int c = 0; c * c <= n; c ++ )
        for (int d = c; c * c + d * d <= n; d ++ )
            sum[m ++ ] = {c * c + d * d, c, d};

    sort(sum, sum + m);

    for (int a = 0; a * a <= n; a ++ )
        for (int b = 0; a * a + b * b <= n; b ++ )
        {
            int t = n - a * a - b * b;
            int l = 0, r = m - 1;
            while (l < r)
            {
                int mid = l + r >> 1;
                if (sum[mid].s >= t) r = mid;//也就是sum这个pow(c,2)+pow(d,2)太大了,需要right左移换个小的
                else l = mid + 1;
            }
            if (sum[l].s == t)
            {
                printf("%d %d %d %d\n", a, b, sum[l].c, sum[l].d);
                return 0;
            }
        }

    return 0;
}

哈希:

核心:key存pow(c,2)+pow(d,2),同样地遍历a,b,确定这两个的最小字典序,接着利用map的快速查询

#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 2500010;

int n, m;
unordered_map<int, PII> S;

int main()
{
    cin >> n;

    for (int c = 0; c * c <= n; c ++ )
        for (int d = c; c * c + d * d <= n; d ++ )
        {
            int t = c * c + d * d;
            if (S.count(t) == 0) S[t] = {c, d};
        }

    for (int a = 0; a * a <= n; a ++ )
        for (int b = 0; a * a + b * b <= n; b ++ )
        {
            int t = n - a * a - b * b;
            if (S.count(t))
            {
                printf("%d %d %d %d\n", a, b, S[t].x, S[t].y);
                return 0;
            }
        }

    return 0;
}

分巧克力

1227. 分巧克力 - AcWing题库

核心:理解        res+=(h[i]/mid)*(w[i]/mid);

mid此时是作为正方形的边长,也就说整个巧克力的面积除以枚举的正方形面积,同样的二段性:因为边长越长,分到的巧克力的个数越少,所以选取的是第一段的末尾:即刚好满足

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100010;
int h[N],w[N];
int n,k;

bool check(int mid)
{
    int res=0;
    for(int i=0;i<n;i++)
    {
        res+=(h[i]/mid)*(w[i]/mid);
        if(res>=k) return true;
    }
    return false;
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++) scanf("%d%d",&h[i],&w[i]);
    
    int left=1;
    int right=1e5;
    while(left<right)
    {
        int mid=left+right+1>>1;
        if(check(mid)) left=mid;
        else right=mid-1;
    }
    cout<<right<<endl;
}

前缀和


前缀和(经典模板)

活动 - AcWing

因为开的数组足够大,为了方便从1开始对应

要求[left,right]区间的和的公式为:sum[right]-sum[left-1]         (left>=1)

#include<iostream>
#include<cstdio>
using namespace std;

const int N=100010;
int nums[N];
int sum[N];

int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&nums[i]);
        sum[i]=sum[i-1]+nums[i];
    }
    
    while(m--)
    {
        int left,right;
        cin>>left>>right;
        printf("%d\n",sum[right]-sum[left-1]);
    }
    
    return 0;
}

子矩阵的和(经典模板)

活动 - AcWing

核心:注意画图理解构造和求区间和公式的不同

#include<iostream>
using namespace std;

const int N = 1010;
int n, m, q;
int a[N][N];
int s[N][N];

int main()
{
	scanf("%d%d%d", &n, &m, &q);
	
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			scanf("%d", &a[i][j]);
			s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
		}
	}

	while (q--)
	{
		int x1, y1, x2, y2;
		cin >> x1 >> y1 >> x2 >> y2;
		printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
	}

	return 0;
}

激光炸弹

99. 激光炸弹 - AcWing题库

核心:因为这题数据比较大,所以为了节省空间,只开一个前缀和s数组,而不多开一个存权重w的数组,因此前缀和的构造公式需要发生小小的改变

此外:本题,没有限制地图的空间,只是给出了数据的范围,所以需要从给出的R的范围,和输入的x,y坐标中寻找最大的作为边界

思想:依次枚举每个R*R的矩阵,用前缀和数组快速地得出其区间和,记录最大值即可

#include<iostream>
#include<algorithm>
using namespace std;
const int N=5010;
int n,m;
int s[N][N];

int main()
{
    int cnt,R;
    cin>>cnt>>R;
    R=min(5001,R);
    
    n=m=R;
    while(cnt--)
    {
        int x,y,w;
        cin>>x>>y>>w;
        x++,y++;//为了对应
        n=max(n,x),m=max(m,y);
        s[x][y]+=w;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
    int res=0;
    for(int i=R;i<=n;i++)
        for(int j=R;j<=m;j++)
            res=max(res,s[i][j]-s[i-R][j]-s[i][j-R]+s[i-R][j-R]);
    cout<<res<<endl;
    
    return 0;
}

 K倍区间

1230. K倍区间 - AcWing题库

核心:从暴力->前缀和优化求和步骤->前缀和同余定理

思想:依次枚举右端点,并求出左端点和右端点之间的前缀和的余,如果有多少个余数相等,那么就将该个数加到res中即可        关于对cnt[0]=1的解释在注释中

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 100010;

/*没讲清楚,为什么前缀和模为1的时候会影响模为0的值,我认为是这样:总共出现了3次模为1的情况,
而每两次模为1组合起来可以模0,比如1,2加起来模为1,1,2,3,4,5加起来模为1,那么这两种情况组合起
来(区间做减)是3,4,5就是模为0的情况。所以一共出现了3次模为1的情况,那么两两组合的情况一共有三种,再
加上本来模为0的情况有3次,一共就6次模为0的情况。“k倍区间就加上cnt[sum[i]]”只是实现了计算模不为0的时候
的情况两两组合的组合数量*/
//答案等于前缀中出现过的和s[i]余k相等的计数之和,完成答案的计算之后需要将计数数组更新,加上自己
int n, k;
ll s[N], cnt[N];

int main()
{
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; i++)
	{
		scanf("%lld", &s[i]);
		s[i] += s[i - 1];
	}
	ll res = 0;
	cnt[0] = 1;

	for (int i = 1; i <= n; i++)
	{
		res += cnt[s[i] % k];//已经构造好了:
		cnt[s[i] % k]++;
	}
	cout << res << endl;

	return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值