备战蓝桥杯(前缀和、差分篇)

Acwing 562.壁画

题目大意:
墙壁为一行, n n n个格子,每个时刻开始,先涂染料,再崩坏,涂过染料的格子不会崩坏,并且涂格子只能涂相邻未涂过的。

解题思路:
因为是先涂染料,所以最终涂过的格子数为 ( n + 1 ) / 2 (n+1)/2 (n+1)/2,然后求长度为 ( n + 1 ) / 2 (n+1)/2 (n+1)/2的连续长度最大值即可。
AC代码:时间复杂度为 O ( N ) O(N) O(N)

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[5001000];
void solve(){
    int n;
    string x;
    cin>>n;
    cin>>x;
    for(int i=1;i<=n;i++){
        a[i]=x[i-1]-'0';
        a[i]+=a[i-1];
    }
    int sum=0;
    int k=(n+1)/2;
    for(int i=k;i<=n;i++){
        sum=max(a[i]-a[i-k],sum);
    }
    cout<<sum<<endl;
    return ;
}
signed main(){
    int t;
    cin>>t;
    for(int i=1;i<=t;i++){
        cout<<"Case #"<<i<<": ";
        solve();
    }
    return 0;
}

AcWing 1230. K倍区间

题目大意:
给定一个长度为 N N N的数列, A 1 , A 2 , … A N A1,A2,…AN A1,A2,AN,如果其中一段连续的子序列 A i , A i + 1 , … A j Ai,Ai+1,…Aj Ai,Ai+1,Aj
之和是 K K K的倍数,我们就称这个区间 [ i , j ] [i,j] [i,j] K K K倍区间。你能求出数列中总共有多少个 K K K倍区间吗?

解题思路:
因为是求和为 K K K的倍数的区间,所以就相当于求 m o d K = = 0 modK==0 modK==0的区间个数,由公式 ( a [ i + j ] − a [ i ] ) m o d K = = 0 即等价于 a [ i + j ] m o d K = = a [ i ] m o d K (a[i+j]-a[i])modK==0即等价于a[i+j]modK==a[i]modK (a[i+j]a[i])modK==0即等价于a[i+j]modK==a[i]modK,那么我们只需在每个前缀和下找到它前面有多少个与它取模 K K K相等的数,累加即可。
AC代码:时间复杂度为 O ( N ) O(N) O(N)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int a[200100],dp[200100];
signed main(){
    int n,k,sum=0;
    cin>>n>>k;
    dp[0]=1;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        a[i]+=a[i-1];
        sum+=dp[a[i]%k];
        dp[a[i]%k]++;
    }
    cout<<sum<<endl;
    return 0;
}

AcWing 1236. 递增三元组

题目大意:
给定三个整数数组

A = [ A 1 , A 2 , … A N ] , B = [ B 1 , B 2 , … B N ] , C = [ C 1 , C 2 , … C N ] , A=[A1,A2,…AN],B=[B1,B2,…BN],C=[C1,C2,…CN], A=[A1,A2,AN],B=[B1,B2,BN],C=[C1,C2,CN],
请你统计有多少个三元组 ( i , j , k ) (i,j,k) (i,j,k)
满足:

1 ≤ i , j , k ≤ N A i < B j < C k 1≤i,j,k≤N Ai<Bj<Ck 1i,j,kNAi<Bj<Ck

解题思路:
方法一:前缀和
既然要排序实现快速的查找A中小于B[i]的数的个数,可以将数组A中所有元素出现的次数存入一个哈希表中,因为数组中元素的范围只有 n 5 n^5 n5, 可以开一个大的数组 c n t a cnta cnta 作为哈希表。

在枚举 B B B中元素时,我们需要快速查找找小于 B [ i ] B[i] B[i]的所有元素的总数,只需要在枚举之前先将求出表中各数的前缀和即可。
AC代码: O ( N ) O(N) O(N)

//前缀和
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
int A[N], B[N], C[N];
int cnta[N], cntc[N], sa[N], sc[N];

int main() {
    int n;
    scanf("%d", &n);
    //获取数i在A中有cntc[i]个,并对cnt求前缀和sa
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &A[i]);
        cnta[++A[i]]++;
    }
    sa[0] = cnta[0];
    for(int i = 1; i < N; ++i) sa[i] = sa[i-1]+cnta[i];
    //B只读取即可
    for(int i = 1; i <= n; ++i) scanf("%d", &B[i]), B[i]++;

    //获取数i在C中有cntc[i]个,并对cnt求前缀和sc
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &C[i]);
        cntc[++C[i]]++;
    }
    sc[0] = cntc[0];
    for(int i = 1; i < N; ++i) sc[i] = sc[i-1]+cntc[i]; 

    //遍历B求解
    LL ans = 0;
    for(int i = 1; i <= n; ++i) {
        int b = B[i];
        ans += (LL)sa[b-1] * (sc[N-1] - sc[b]);
    }
    cout<<ans<<endl;
    return 0;
}

方法二:二分法
既然是查找,那么可以考虑进行二分查找,查找前先通过排序预处理三个数组,排序时间复杂 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)枚举B的所有元素+查找A,C中的元素时间复杂度也是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)总的时间复杂度降为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n).
AC代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[3][200100];
signed main(){
    int n;
    cin>>n;
    for(int i=0;i<3;i++){
        for(int j=1;j<=n;j++){
            cin>>a[i][j];
        }
    }
    for(int i=0;i<3;i++){
        sort(a[i]+1,a[i]+1+n);
    }
    int sum=0;
    for(int i=1;i<=n;i++){
        int k=a[1][i];
        int l=lower_bound(a[0]+1,a[0]+n+1,k)-a[0]-1;
        int r=upper_bound(a[2]+1,a[2]+n+1,k)-a[2];
        if(l>0&&r<n+1){
            sum+=(n-r+1)*l;
        }
    }
    cout<<sum<<endl;
    return 0;
}

AcWing 99. 激光炸弹

题目大意:
地图上有 N N N个目标点,用整数 X i , Y i Xi,Yi Xi,Yi
表示目标在地图上的位置,每个目标都有一个价值 W i Wi Wi
注意:不同目标可能在同一位置。现在有一种新型的激光炸弹,可以摧毁一个包含 R × R R×R R×R个位置的正方形内的所有目标。
激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x , y x,y x,y轴平行。求一颗炸弹最多能炸掉地图上总价值为多少的目标。

解题思路:
固定区间的二维前缀和,扫一遍就可以了,但是要注意内存限制,只能开一个数组。
AC代码: O ( 5001 ∗ 5001 ) O(5001*5001) O(50015001)

#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int a[5010][5010];
signed main(){
    IOS
    int n,r,x,y,w;
    cin>>n>>r;
    for(int i=1;i<=n;i++){
        cin>>x>>y>>w;
        a[x+1][y+1]+=w;
    }
    r=min(r,5001);
    for(int i=1;i<=5001;i++){
        for(int j=1;j<=5001;j++){
            a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
        }
    }
    int maxx=0;
    for(int i=r;i<=5001;i++){
        for(int j=r;j<=5001;j++){
            maxx=max(maxx,a[i][j]-a[i-r][j]-a[i][j-r]+a[i-r][j-r]);
        }
    }
    cout<<maxx<<endl;
    return 0;
}

AcWing 4262. 空调

题目大意:
给你两个序列A和B,可以对B序列进行操作,操作类型为:将B序列中任意长度的一段加1或者减一,最终使两序列相等,求最小操作次数。

解题思路:
先求出两序列的差值数组,再分别对该数组大于零和小于零的绝对值分别累加,取最大值即可。

AC代码: O ( N ) O(N) O(N)

#include<bits/stdc++.h>
using namespace std;
#define int long long 
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl "\n";
int a[200100],b[200100]; 
void solve(){
	int n,sum=0,num=0; 
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
		b[i]=a[i]-b[i];
	}
	for(int i=1;i<=n;i++){
		sum+=max(b[i]-b[i-1],0ll);
		num-=min(b[i]-b[i-1],0ll);
	}
	cout<<max(sum,num)<<endl;
	return ;
}
signed main()
{
	IOS
	int t=1;
	//cin>>t;
	while(t--)
	solve();
    return 0;
}

AcWing 797. 差分

模板题,先对区间进行 m m m次加减,然后再输出操作后的序列
AC代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long 
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl "\n";
int a[200100],b[200100]; 
void solve(){
	int n,m,l,r,w; 
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	while(m--){
		cin>>l>>r>>w;
		b[l]+=w;
		b[r+1]-=w;
	}
	for(int i=1;i<=n;i++){
		b[i]+=b[i-1];
		cout<<a[i]+b[i]<<" ";
	}
	cout<<endl;
	return ;
}
signed main()
{
	IOS
	int t=1;
	//cin>>t;
	while(t--)
	solve();
    return 0;
}

AcWing 798. 差分矩阵

同样是模板题,二维差分,和上一题类似
AC代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long 
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl "\n";
int a[1500][1500],b[1500][1500]; 
void solve(){
	int n,m,q,x1,x2,y1,y2,w; 
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j]; 
		}
	}
	while(q--){
		cin>>x1>>y1>>x2>>y2>>w;
		b[x1][y1]+=w;
		b[x1][y2+1]-=w;
		b[x2+1][y1]-=w;
		b[x2+1][y2+1]+=w;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			b[i][j]+=b[i][j-1]+b[i-1][j]-b[i-1][j-1];
			cout<<a[i][j]+b[i][j]<<" ";
		}
		cout<<endl;
	}
	return ;
}
signed main()
{
	IOS
	int t=1;
	//cin>>t;
	while(t--)
	solve();
    return 0;
}

AcWing 5396. 棋盘

题目大意:
小蓝拥有 n × n n×n n×n大小的棋盘,一开始棋盘上全都是白子。小蓝进行了 m m m次操作,每次操作会将棋盘上某个范围内的所有棋子的颜色取反(也就是白色棋子变为黑色,黑色棋子变为白色)。输出所有操作做完后棋盘上每个棋子的颜色。

解题思路:
因为每次操作只会变黑或者变白,所以只需差分标记操作次数,最后前缀和一下,对于每个格子的操作数,是奇数则输出1,偶数则输出0.

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long 
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl "\n";
int a[2500][2500]; 
void solve(){
	int n,m,x1,x2,y1,y2,w=1; 
	cin>>n>>m;
	while(m--){
		cin>>x1>>y1>>x2>>y2;
		a[x1][y1]+=w;
		a[x1][y2+1]-=w;
		a[x2+1][y1]-=w;
		a[x2+1][y2+1]+=w;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1];
			if(a[i][j]%2){
				cout<<"1";
			}else{
				cout<<"0";
			}
		}
		cout<<endl;
	}
	return ;
}
signed main()
{
	IOS
	int t=1;
	//cin>>t;
	while(t--)
	solve();
    return 0;
}

AcWing 4655. 重新排序

题目大意:
给定一个数组 A A A 和一些查询 L i , R i Li,Ri Li,Ri,求数组中第 L i Li Li
至第 R i Ri Ri 个元素之和。小蓝觉得这个问题很无聊,于是他想重新排列一下数组,使得最终每个查询结果的和尽可能地大。小蓝想知道相比原数组,所有查询结果的总和最多可以增加多少?

解题思路:
先根据查询操作造差分数组,同时记录原操作的和,再对其求前缀和,然后分别对原数组和差分数组进行排序,用最大的数乘以最大的操作数,以此类推求和,最后用排过序的和减去原来的和即为所求。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long 
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define endl "\n";
int a[100100],b[100100],c[100100]; 
void solve(){
	int n,m,l,r,sum=0,num=0; 
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		c[i]=a[i]+c[i-1];
	}
	cin>>m;
	while(m--){
		cin>>l>>r;
		b[l]++;
		b[r+1]--;
		sum+=c[r]-c[l-1];
	}
	for(int i=1;i<=n;i++){
		b[i]+=b[i-1];
	}
	sort(b+1,b+1+n);
	sort(a+1,a+1+n);
	for(int i=n;i>0;i--){
		num+=b[i]*a[i];
	}
	cout<<num-sum<<endl;
	return ;
}
signed main()
{
	IOS
	int t=1;
	//cin>>t;
	while(t--)
	solve();
    return 0;
}
  • 22
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值