洛谷题单 【算法2-1】前缀和与差分

P1115 最大子段和

思路

参考了题解代码~
(虽然这题放在差分这里但是我用差分法会过不去时间,就参考了题解里的dp)
f [ i ] f[i] f[i]为从i开始向前连续延申的最大子段和,最终的最大子段和就是 f [ i ] f[i] f[i]中最大的
在这里插入图片描述

实现

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int a[200010],p,ans[200010]={0};
    int sum=-9999999;
    cin>>p;
    for(int i=1;i<=p;i++)
    {
        cin>>a[i];
        ans[i]=max(ans[i-1]+a[i],a[i]);//DP
        sum=max(sum,ans[i]);
    }
    cout<<sum;
    return 0;
}

其他

来自洛谷题解区

这里是引用

P3397 地毯

思路

差分法的入门练习题,先差分,再求和

实现

#include <iostream>
#include <vector>
#define rep(i,j,k) for(int i=j;i<k;i++)
using namespace std;
int b[1010][1010];
void insert(int x1, int y1, int x2, int y2, int c)
{
    b[x1][y1] += c;
    b[x2 + 1][y1] -= c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y2 + 1] += c;
}
int main()
{
	int n; //列数 
	int m;
	cin>>n>>m;
	int x1,x2,y1,y2;
	rep(i,0,m)
	{
		scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
		insert(x1,y1,x2,y2,1);
	}
	
	rep(i,1,n+1)
	{
		rep(j,1,n+1)
		{
			b[i][j] += b[i-1][j]+b[i][j-1]-b[i-1][j-1];
			printf("%d ",b[i][j]);
		}
		cout<<endl;
	}
    return 0;
}

P3406 海底高铁

思路

  • 对于同一段铁路,我们要么一直买票,要么一直办卡。假设这条铁路一共走了n次,关键就是比较 n A nA nA C + n B C+nB C+nB哪个小。
  • 首先需要统计出每一小段路程需要走多少次,比如城市3–>城市1,就需要给第1、2段铁路加1,比如1–>4,就需要给1、2、3段加1。规律就是读入两个数分别命名为pre和cur,如果pre<cur,就给[pre,cur-1]这个区间里的铁路都加1,如果pre>cur,就给[cur,pre-1]这个区间里的铁路都加1。
  • 用差分来实现给一个区间里的所有数都加1。这道题是先差分insert,再累加,最后比较买票和办卡哪个花费少。

实现

#include <iostream>
#include <vector>
#include <algorithm>
#define rep(i,j,k) for(int i=j;i<k;i++)
using namespace std;
long long b[101000];
void insert(int l, int r, int c)
{
    b[l] += c;
    b[r + 1] -= c;
}
int main()
{
	int n,m;
	cin>>n>>m;
	int pre,cur;
	scanf("%d",&pre);
	rep(i,1,m)
	{
		scanf("%d",&cur);
		if(cur>pre)
		insert(pre,cur-1,1);
		else
		insert(cur,pre-1,1);
		pre = cur;
	}
	long long ans = 0;
	rep(i,1,n)
	{
		b[i]+=b[i-1];
//	cout<<b[i];
	ans+=b[i];
}
//cout<<ans;
ans = 0;
int a,bb,c;
rep(i,1,n)
{
	scanf("%d %d %d",&a,&bb,&c);
	ans+= min(a*b[i],bb*b[i]+c);
}
cout<<ans;
    return 0;
}

P1719 最大加权矩形

思路

前缀和的方法就是四层循环
优化的方法有:先压缩再一维dp (来自洛谷题解
对于矩形
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
先在竖直方向算出前缀和
0 -2 -7 0
9 0 -13 2
5 1 -17 3
4 9 -17 1
之后,i和j是选择的两行,i从0到n-1,j从i+1到n。每次先选择两行,然后 d p [ k ] dp[k] dp[k]表示在这两行之间到第k列为止的向前连续的最大子矩阵。(可以先做一下P1115最大子段和理解向前连续)

实现

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 330;

int n, a[N][N], res = -9999999, dp[N];

int main(){
    cin >> n;
    //前缀和(竖直方向)
    for (int i = 1 ; i <= n ; i ++ ){
        for (int j = 1 ; j <= n ; j ++ ){
            cin >> a[i][j];
            a[i][j] += a[i - 1][j];
        }
    }
    //降维变成一维dp
    for (int i = 0 ; i <= n - 1 ; i ++ ){
        for (int j = i + 1 ; j <= n ; j ++ ){
            for (int k = 1 ; k <= n ; k ++ ){
                dp[k] = max(a[j][k] - a[i][k], dp[k - 1] + a[j][k] - a[i][k]);
                res = max(res, dp[k]);
            }
        }
    }
    
    cout << res;
  
    return 0;
}

P2004 领地选择

思路

二维前缀和的模板题,需要注意边界

实现

#include <iostream>
#include <algorithm>
#include <vector>
#define rep(i,j,k) for(int i=j;i<k;i++)
using namespace std;
typedef long long ll;
int main(){
    int n,m,c;
    cin>>n>>m>>c;
    vector<vector<ll> > a(n+1,vector<ll>(m+1,0));
    rep(i,1,n+1)
    {
    	rep(j,1,m+1)
    	{
    		scanf("%lld",&a[i][j]);
    		a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
//    		cout<<a[i][j]<<" ";
		}
//		cout<<endl;
	}
	ll ans = a[1][1];
	ll area;
	int max_i,max_j;
    rep(i,1,n-c+2)
    {
    	rep(j,1,m-c+2)
    	{
    		area = a[i+c-1][j+c-1]+a[i-1][j-1]-a[i-1][j+c-1]-a[i+c-1][j-1];
    		if(ans<area)
    		{ans = area;
    		max_i = i;
    		max_j = j;
    		}
		}
	}
    cout<<max_i<<" "<<max_j;
    return 0;
}

P5638 【CSGRound2】光骓者的荣耀

思路

既然可以跳到第 i + k i+k i+k或第 i − k i-k ik个城市,那么要走的路尽可能少,一定是跳到 i + k i+k i+k个城市,并且这个 i i i满足:第 i i i个城市和第 i + k i+k i+k个城市之间的距离最长。题意其实就是,找出在一个数组中长度为k的连续子序列的和的最大值。一维前缀和,累加的同时找最大值即可。

实现

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

typedef long long ll;


int main()
{
	int n,k;
	ll max_ = 0;
	ll sum = 0;
	cin>>n>>k;
	vector<ll> a(n);
	a[0] = 0;
	for(int i=1;i<n;i++)
	{
		scanf("%d",&a[i]);
		a[i]+=a[i-1];
		//cout<<a[i]<<endl;
	} 
	for(int i=k;i<=n;i++)
	{
		max_ = max(max_,a[i]-a[i-k]);
	}
	cout<<a[n-1]-max_;
    return 0;
}

P2671 [NOIP2015 普及组] 求和

思路

待补充-

实现

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

typedef long long LL;
const int N = 100010, MOD = 10007;
int num[N], color[N];
LL s[N][2], cnt[N][2];
int n, m;

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> num[i];
    for (int i = 1; i <= n; i++)
    {
        cin >> color[i];
        s[color[i]][i % 2] = (s[color[i]][i % 2] + num[i]) % MOD;
        cnt[color[i]][i % 2]++;
    }
    LL res = 0;
    for (int i = 1; i <= n; i++)
        res = (res + i * ((cnt[color[i]][i % 2] - 2) * num[i] + s[color[i]][i % 2])) % MOD;
    cout << res << endl;
    return 0;
}

欢迎指正-

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前缀和差分是一类常用的算法,它们常常被用来优化一些区间操作的问题,如求区间和、区间最大值/最小值等等。下面我们将分别介绍前缀和差分的定义、用法和常见问题。 ## 前缀和 前缀和,顾名思义,就是把前面所有数的和都求出来,用一个数组存起来,以便之后的查询。 ### 定义 给定一个长度为 $n$ 的序列 $a$,令 $s_i = \sum_{j=1}^{i}a_j$,则 $s$ 称为序列 $a$ 的前缀和数组。 ### 用法 前缀和的主要作用是用 $O(1)$ 的时间复杂度求出一个区间 $[l,r]$ 的和,即 $s_r - s_{l-1}$。这是因为 $s_r$ 存储了序列从 $1$ 到 $r$ 的和,而 $s_{l-1}$ 存储了序列从 $1$ 到 $l-1$ 的和,因此区间 $[l,r]$ 的和可以通过两个前缀和相减计算得出。 前缀和的时间复杂度为 $O(n)$,因为需要遍历一遍序列求出前缀和数组。但是,如果有多个查询需要求区间和,那么使用前缀和可以将每次查询的时间复杂度降低到 $O(1)$。 ### 代码实现 下面是使用前缀和求区间和的代码实现: ```cpp vector<int> a; // 原序列 vector<int> s(a.size() + 1); // 前缀和数组 // 计算前缀和 for (int i = 1; i <= a.size(); i++) { s[i] = s[i - 1] + a[i - 1]; } // 查询区间 [l, r] 的和 int sum = s[r] - s[l - 1]; ``` ## 差分 差分前缀和相反,它主要用来对区间进行修改。我们可以利用差分数组进行区间修改,并最终得到修改后的序列。 ### 定义 给定一个长度为 $n$ 的序列 $a$,令 $d_i = a_i - a_{i-1}$($d_1 = a_1$),则 $d$ 称为序列 $a$ 的差分数组。 ### 用法 差分的主要作用是对区间进行修改。假设我们需要将区间 $[l,r]$ 的数加上 $k$,我们可以将差分数组的 $d_l$ 加上 $k$,将 $d_{r+1}$ 减去 $k$。这样,对差分数组求前缀和,就可以得到修改后的序列。 具体来说,我们可以按照以下步骤进行区间修改: 1. 对差分数组的 $d_l$ 加上 $k$; 2. 对差分数组的 $d_{r+1}$ 减去 $k$; 3. 对差分数组求前缀和,得到修改后的序列。 差分的时间复杂度为 $O(n)$,因为需要遍历一遍序列求出差分数组。但是,如果有多次区间修改需要进行,那么使用差分可以将每次修改的时间复杂度降低到 $O(1)$。 ### 代码实现 下面是使用差分进行区间修改的代码实现: ```cpp vector<int> a; // 原序列 vector<int> d(a.size() + 1); // 差分数组 // 计算差分数组 for (int i = 1; i < a.size(); i++) { d[i] = a[i] - a[i - 1]; } // 修改区间 [l, r],将数加上 k d[l] += k; d[r + 1] -= k; // 对差分数组求前缀和,得到修改后的序列 for (int i = 1; i < d.size(); i++) { a[i] = a[i - 1] + d[i]; } ``` ## 常见问题 ### 1. 差分数组的长度是多少? 差分数组的长度应该比原序列长度多 1,因为 $d_1 = a_1$。 ### 2. 什么情况下使用前缀和?什么情况下使用差分? 如果需要进行多次区间查询,那么使用前缀和可以将每次查询的时间复杂度降低到 $O(1)$;如果需要进行多次区间修改,那么使用差分可以将每次修改的时间复杂度降低到 $O(1)$。 ### 3. 前缀和差分的本质区别是什么? 前缀和差分都是用来优化区间操作的算法,它们的本质区别在于: - 前缀和是通过预处理前缀和数组来优化区间查询; - 差分是通过预处理差分数组来优化区间修改。 ### 4. 前缀和差分能否同时使用? 当然可以。如果需要同时进行区间查询和修改,我们可以先使用差分数组对区间进行修改,然后再对差分数组求前缀和,得到修改后的序列。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值