前缀和+差分+ 树状数组

前缀和+差分的理论知识

前缀和相关题:

「USACO16JAN」子共七 Subsequences Summing to Sevens

定理:若两个数相减 mod k == 0,那么这两个数mod k 的余数相同。(看别人的题解得到的)

#include<bits/stdc++.h>
using namespace std;

const int N = 5e4;

int n;
int a[N + 1];//前n项和mod 7 
int bgin[7],tail[7];  //bgin存第1个mod为i的数据编号,tail存最后一个mod为i的数据编号 

int main()
{
  // 若两个数相减mod 7==0,那么这俩个数mod 7的余数一定相同 
	ios::sync_with_stdio(false);
	
	int res = 0;//存结果 
	cin >> n ;
	for(int i = 1; i <= n; i ++)
	{
		cin >> a[i];
		a[i] += a[i-1];
		a[i] %= 7;
	}
	for(int i = 0; i <= n; i ++)  //从前往后搜,且这里必须是i=0开始,不能从i=1开始,考虑数据1 3 4即明白 
		tail[a[i]] = i;
	for(int i = n; i >= 0; i --) //从后往前搜, 并且这里必须要包含0也即i>=0 
		bgin[a[i]] = i;
	
	for(int i = 0; i < 7; i ++)
		res = max(res, tail[i] - bgin[i]);

	cout << res << endl;
	return 0;
	
}

「USACO05JAN」Moo Volume S

公式推到:(假设数据有序)

第i个位置的牛与前i-1个牛聊天音量的总和:
x i − x i − 1 + x i − x i − 2 + ⋯ + x i − x 2 + x i − x 1 = ( i − 1 ) ⋅ ∑ k = 1 i − 1 x k x_i - x_{i-1} + x_i - x_{i-2} +\cdots+ x_i -x_2+x_i - x_1 = (i-1)\cdot\sum\limits_{k=1}^{i-1}x_k xixi1+xixi2++xix2+xix1=(i1)k=1i1xk
下式res结果要乘2 是因为只计算了 ( x i > x j ) 时 x i − x j (x_i>x_j)时x_i-x_j (xi>xj)xixj并没有计算 x j − x i x_j-x_i xjxi,且 ∣ x i − x j ∣ = ∣ x j − x i ∣ \lvert x_i-x_j\rvert=\lvert x_j-x_i\rvert xixj=xjxi

r e s = 2 ⋅ ∑ i = 1 n ( i − 1 ) ⋅ ∑ k = 1 i − 1 x k res =2\cdot \sum\limits_{i=1}^{n}(i-1)\cdot\sum\limits_{k=1}^{i-1}x_k res=2i=1n(i1)k=1i1xk

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 1;
typedef long long ll;

ll n;
ll a[N]; //存放数字 
ll sum[N]; //前n项和 
int main()
{
	ios::sync_with_stdio(false);
	
	cin >> n;
	ll res = 0;//结果 
	for(int i = 1; i <= n; i ++)
		cin >> a[i];
	//排序 
	sort(a+1,a+n+1);
	//求前n项和 
	for(int i = 1; i <= n; i ++)
		sum[i] = sum[i-1] + a[i];
	// 求最终结果	
	for(int i = 1; i <= n; i ++)
		res += (i - 1)*a[i] - sum[i-1];//此公式需要推到 
	cout << res * 2 << endl;
} 

二维前缀和:

洛谷 P1387 最大正方形

#include<bits/stdc++.h>
using namespace std;
const int N = 100 + 3; 
int n, m;

int matrix[N][N];

int sum[N][N]; //前缀和 

int main()
{
	ios::sync_with_stdio(false);
	int res = 0;
	cin >> n >> m;
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= m; j ++)
		{
			cin >> matrix[i][j];
			sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + matrix[i][j];
		}
	for(int len = 1; len <= min(n, m); len ++)//边长 
		for(int i = len; i <= n; i ++)
			for(int j = len; j <= m; j ++)
			{
				if((sum[i][j] - sum[i - len][j] - sum[i][j - len] + sum[i - len][j - len]) == (len * len))
					res = max(res, len);
			}
	cout << res << endl;
	
	return 0;
}

「HNOI2003」激光炸弹

以下都是AC代码,为了防止MLE进行了空间优化,也即sum即存储原始数据,也存储前缀和。

#include<bits/stdc++.h>
using namespace std;

const int N = 5e3 + 3;

int n, m;


int sum[N][N];//前缀和 

int main()
{
	ios::sync_with_stdio(false);
	int res = 0, mx = 0, my = 0;
	cin >> n >> m;
	for(int i = 0; i < n; i ++)
	{
		int x, y, v;
		cin >> x >> y >> v;
		sum[x + 1][y + 1] = v;
		mx = max({mx, x + 1, m});
		my = max({my, y + 1, m});//必须把m算进去,考虑此类情况:n=1 m= 2 ,第二行输入 0,0 ,1 
	}
	for(int i = 1; i <= mx; i ++)
		for(int j = 1; j <= my; j ++)
			sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + sum[i][j];
	for (int i = m; i <= mx; i ++)
		for(int j = m; j <= my; j ++)
			res = max(res, sum[i][j] - sum[i - m][j] - sum[i][j - m] + sum[i - m][j - m]);		
	cout << res << endl;
	return 0;
}
#include<bits/stdc++.h>
using namespace std;

const int N = 5000 + 11;

int n, m;

int sum[N][N];//前缀和 

int main()
{
	ios::sync_with_stdio(false);
	int res = 0, mx = 0, my = 0;
	cin >> n >> m;
	for(int i = 0; i < n; i ++)
	{
		int x, y, v;
		cin >> x >> y >> v;
		sum[x + 1][y + 1] = v;//这个是让坐标从1开始,而不是从0开始 
		mx = max(mx, x + 1);
		my = max(my, y + 1);
	}
	for(int i = 1; i <= min(mx + m, 5001); i ++) //这里i不能小于等于N,因为你数组就开N个, 当然如果这里为了省事,可直接i<=5001 下面一样 
		for(int j = 1; j <= min(my + m, 5001); j ++)
			sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + sum[i][j];//这里其实进行了空间的优化 
	for (int i = m; i <= min(mx + m, 5001); i ++)
		for(int j = m; j <= min(my + m, 5001); j ++)
			res = max(res, sum[i][j] - sum[i - m][j] - sum[i][j - m] + sum[i - m][j - m]);		
	cout << res << endl;
	return 0;
}


CPS-邻域均值

#include<bits/stdc++.h>
using namespace std;
const int N = 600 + 3;
int n, l, r, t;

int  sum[N][N];

int main()
{
	ios::sync_with_stdio(false);
	int res = 0;
	
	cin >> n >> l >> r >> t;
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= n; j ++)
		{
			cin >> sum[i][j];
			sum[i][j] = sum[i-1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + sum[i][j]; 
		}
	
	// 中心点为参考
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= n; j ++)
		{
			int loc_l = max(1, j - r);
			int loc_r = min(n, j + r);//不能直接把变量设为r,t,会与前面的变量名重复。 
			int loc_t = max(1, i - r);
			int loc_b = min(n, i + r);
			double counts = (loc_r - loc_l + 1) * (loc_b - loc_t + 1); //个数 
			if( ((sum[loc_b][loc_r] - sum[loc_b][loc_l - 1] - sum[loc_t - 1][loc_r] + sum[loc_t - 1][loc_l - 1]) / counts) <= t)
				res += 1;
		}
	cout << res << endl;
	return 0;
}

csp- 期末预测之最佳阈值

#include<bits/stdc++.h>
using namespace std;

const int M = 1e5 + 1;

int m;

struct Node
{
	int y,result;
	bool operator < (const struct Node n)//根据y排序 
	{
		if (y < n.y)
			return true;
		return false;
	}
}node[M];

int sum[M]; 

int main()
{
	
	ios::sync_with_stdio(false);
	
	int res = 0, maxTotal = 0;//res:最大阈值,maxTotal: 预测正确次数最大 
	cin >> m;
	
	for(int i = 1; i <= m; i ++)
		cin >> node[i].y >> node[i].result;

	sort(node + 1, node + m + 1);
	
	
	for(int i = 1; i <= m; i ++)
		sum[i] = sum[i-1] + node[i].result;
	
	for(int i = 1; i <= m; i ++)
	{
		if(node[i].y == node[i-1].y) //这一步是必须的,如果y连续出现相同的数,只需计算第一个,后面的不要再计算 ,因为下面的sum[i-1]会出错 
			continue;
		int total = (i - 1 - sum[i-1]) + (sum[m] - sum[i - 1]);//计算预测正确次数, +前部分是 小于y的预测正确的次数,+ 后半部分是 大于y预测正确的次数 
		
		//找最大的maxtotal和阈值 
		if(maxTotal <= total)
		{
			maxTotal = total;
			res = node[i].y;
		}
		
	}
	cout << res << endl;
	return 0;
}

差分相关题:

a i + 1 = b i + 1 − b i a_{i+1} = b_{i+1} - b_i ai+1=bi+1bi其中 a a a是差分矩阵, b b b是原矩阵,对差分矩阵求前缀和即是原矩阵。如果对原矩阵 [ l , r ] [l,r] [l,r]增加k,那么可以对差分矩阵 a [ l ] + = k , a [ r + 1 ] − = k a[l] += k, a[r + 1] -= k a[l]+=k,a[r+1]=k。可以减少时间复杂度。
∑ i = 1 n a i = ∑ i = 1 n ∑ j = 1 i b j = ∑ i = 1 n ( n − i + 1 ) b i = ( n + 1 ) ∑ i = 1 n b i − ∑ i = 1 n i ⋅ b i \sum\limits_{i=1}^n a_i = \sum\limits_{i=1}^n \sum\limits_{j=1}^i b_j = \sum\limits_{i=1}^n (n - i + 1) b_i = (n + 1)\sum\limits_{i =1}^n b_i-\sum\limits_{i = 1}^n i\cdot b_i i=1nai=i=1nj=1ibj=i=1n(ni+1)bi=(n+1)i=1nbii=1nibi
有关 ∑ i = 1 n ∑ j = 1 i b j = ∑ i = 1 n ( n − i + 1 ) b i \sum\limits_{i=1}^ n \sum\limits_{j=1}^ i b_j = \sum\limits_{i=1}^n (n - i + 1) b_i i=1nj=1ibj=i=1n(ni+1)bi 这一步的产生,考虑有n个 b 1 b_1 b1,n-1个 b 2 b_2 b2 ,以此类推有 n − i + 1 n-i+1 ni+1 b i b_i bi故得出结果。

csp-出行计划

q + k ≤ t i ≤ q + k + c i − 1 q + k \le t_i \le q +k+c_i -1 q+ktiq+k+ci1 t i + 1 − c i − k ≤ q ≤ t i − k t_i + 1- c_i - k \leq q \leq t_i - k ti+1cikqtik,也即做核酸的时间点要在 [ t i + 1 − c i − k , t i − k ] [t_i + 1- c_i - k , t_i - k] [ti+1cik,tik]内,对每个出行计划其范围内的时间点+1,时间点为整数,最终如果求q时刻满足出行的计划,直接根据下标q即可得。

#include<bits/stdc++.h>
using namespace std;

const int N =  2e5 + 3;

int n, m, k; 
int diff[N]; // 差分矩阵 ,差分矩阵的原矩阵表示在每个时间点,如果这个时间做核酸可以出行,则这里的值+1 
int q[N];
int main()
{
	ios::sync_with_stdio(false);
	
	cin >> n >> m >> k; 
	for(int i = 0; i < n; i ++)
	{
		int ti, ci;
		cin >> ti >> ci;
		diff[max(1,ti + 1 - ci - k)] ++;//这里要和 1进行比较,如果不加会出现错误。 
		diff[max(1,ti - k + 1)] --;
	}
	for(int i = 1; i < N; i ++)
		diff[i] = diff[i - 1] + diff[i];
	for(int i = 0; i < m; i ++)
		cin >> q[i];
	for(int i = 0; i < m; i ++)
		cout << diff[q[i]] << endl;
	return 0;
}

P3397 地毯此题是很经典的二维差分问题。有关二维差分参见此博客

#include<bits/stdc++.h>
using namespace std;

const int N = 1000 + 3;

int n, m;

int diff[N][N];

int main()
{
	ios::sync_with_stdio(false);
	
	cin >> n >> m;
	for(int i = 0; i < m; i ++)
	{
		int lx, ly, rx, ry;
		cin >> lx >> ly >> rx >> ry;
		diff[lx][ly] ++;
		diff[lx][ry + 1] --;
		diff[rx + 1][ly] --;
		diff[rx + 1][ry + 1] ++;
	} 
	//求前缀和
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= n; j ++)
		{
			diff[i][j] = diff[i-1][j] + diff[i][j - 1] - diff[i - 1][j - 1] + diff[i][j];//这里是根据差分求原矩阵
		}
	//输出
	for(int i = 1; i <= n; i ++)
	{
		for(int j = 1; j <= n; j ++)
		{
			cout << diff[i][j] << " ";
		}
		cout << endl;
	}
		
	return 0;
}

P4552 [Poetize6] IncDec Sequence

题解实在是不知道该怎么说,洛谷有相关题解,第一个写的很好。

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 100000 + 3;

LL n;

LL  diff[N];//a是原矩阵,diff是差分矩阵 ,必须是LL,int会报错,2^31,需要开LL 

int main()
{
	ios::sync_with_stdio(false);
	LL count_operator = 0, count_result = 0; 
	cin >> n;
	LL last = 0;
	for(int i = 1; i <= n; i ++)
	{
		LL t;
		cin >> t;
		diff[i] = t - last;
		last = t;
	}
	LL sum_p = 0,sum_n = 0;
	for(int i = 2; i <= n; i ++)
	{
		if(diff[i] < 0)
			sum_n += diff[i];
		else if(diff[i] > 0)
			sum_p += diff[i];
	}
	count_operator = abs(sum_p + sum_n) + min(sum_p, abs(sum_n));
	count_result = abs(sum_p + sum_n) + 1;
	
	cout << count_operator << endl;
	cout << count_result << endl; 
	
	return 0;
}

树状数组

#130. 树状数组 1 :单点修改,区间查询

/*
  单点修改,区间查询  2022.9.9
  计算机中,负数用补码表示 ,所以lowbit = x & -x 
*/ 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e6 + 3; 

ll t[N]; //存储的原数组的树状数组。

struct Node
{
	ll id, l, r;
}node[N];

ll n, q;

//单点修改,对下标为i的原数组增加x 
void add(ll x, ll k)
{
	for(;x <= n; x += x & -x)
		t[x] += k;
}
// 求下标x的原数组前缀和 
ll prefix(ll x) 
{
	ll ans = 0;
	for(;x >= 1; x -= x & -x)
		ans += t[x];
	return ans; 
}
// 求区间原数组[l,r]的区间和 
ll quary(ll l, ll r)
{
	return prefix(r) - prefix(l - 1);
}

int main()
{
	cin >> n >> q;
	for(ll i = 1; i <= n; i ++)
	{
		ll temp;
		cin >> temp;
		add(i, temp);
	}
	for(ll i = 0; i < q; i ++)
		cin >> node[i].id >> node[i].l >> node[i].r;
	for(ll i = 0; i < q; i ++)
	{
		if(node[i].id == 1)
			add(node[i].l, node[i].r);
		else if(node[i].id == 2)
			cout << quary(node[i].l, node[i].r) << endl;
	}
	return 0;
}	

写成模板

/*
  单点修改,区间查询  2022.9.9
  计算机中,负数用补码表示 ,所以lowbit = x & -x 
*/ 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;


//写成模板 
const ll N = 1e6 + 3; 
struct BIT
{
	ll t[N], n; //t[n]存储的原数组的树状数组,n是元素组的元素个数。
	//单点修改,对下标为i的原数组增加x 
	inline void add(ll x, ll k)
	{
		for(;x <= n; x += x & -x)
			t[x] += k;
	}
	// 求下标x的原数组前缀和 
	inline ll prefix(ll x) 
	{
		ll ans = 0;
		for(;x >= 1; x -= x & -x)
			ans += t[x];
		return ans; 
	}
	// 求区间原数组[l,r]的区间和 ,区间查询
	inline ll quary(ll l, ll r)
	{
		return prefix(r) - prefix(l - 1);
	}
}; 

struct BIT bit;
struct Node
{
	ll id, l, r;
}node[N];
ll q;
int main()
{
	ios::sync_with_stdio(false);	
	cin >> bit.n >> q;
	for(ll i = 1; i <= bit.n; i ++)
	{
		ll temp;
		cin >> temp;
		bit.add(i, temp);
	}
	for(ll i = 0; i < q; i ++)
		cin >> node[i].id >> node[i].l >> node[i].r;
	for(ll i = 0; i < q; i ++)
	{
		if(node[i].id == 1)
			bit.add(node[i].l, node[i].r);
		else if(node[i].id == 2)
			cout << bit.quary(node[i].l, node[i].r) << endl;
	}
	return 0;
}


#131. 树状数组 2 :区间修改,单点查询

/*
	time: 2022.9.9
	区间修改,单点查询 
*/

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const ll N = 1e6 + 3;
struct BIT
{
	ll n, t[N]; //t[N]存储的是原数组的差分数组的树状数组
	
	//单点修改 (这里的单点是指对差分数组中的一个元素修改,而非原数组) 
	inline void add(ll x,ll k)
	{
		for(; x<= n; x += x & -x)
			t[x] += k;
	}
	
	//区间修改,对原数组的[l,k]+k(指的是对原数组进行区间修改)
	inline void inter_add(ll l, ll r, ll k)
	{
		add(l, k);
		add(r + 1, -k);
	}
	
	//单点查询,(这里的单点查询,是对原数组求其某下标的值),其实是求差分数组的前缀和 
	inline ll ask(ll x)
	{
		ll ans = 0;
		for(;x >= 1; x -= x & -x)
			ans += t[x];
		return ans;
	}
};

struct BIT bit;

ll q;
struct Qu
{
	ll id,l,r,x;
}Q[N];

int main()
{
	cin >> bit.n >> q;
	ll last = 0; 
	for(ll i = 1; i <= bit.n; i ++)
	{
		ll temp;
		cin >> temp;
		bit.add(i, temp - last); // 在进行构造过程中,不能 bit.add(i, temp);,而应该用差分数组 
		last = temp;
	}
	
	for(ll i = 1; i <= q; i ++)
	{
		cin >> Q[i].id;
		if(Q[i].id == 1)
			cin >> Q[i].l >> Q[i].r >> Q[i].x;
		else if(Q[i].id == 2)
			cin >> Q[i].l;
	}
	
	for(ll i = 1; i <= q; i ++)
	{
		if(Q[i].id == 1)
			bit.inter_add(Q[i].l, Q[i].r, Q[i].x);
		else if(Q[i].id == 2)
		{
			ll res = bit.ask(Q[i].l);
			cout << res << endl;
		}
	}
	
	return 0;
}

#132. 树状数组 3 :区间修改,区间查询

/*
	time: 2022.9.9 
	区间修改,区间查询; 
*/

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const ll N = 1e6 + 3;
struct BIT
{
	ll t1[N], t2[N], n;//t1[N]是差分数组的树状数组,t2[N]是i*diff[i]的树状数组
	
	//t1单点修改(这里是对差分数组单点修改)
	inline void add1(ll x, ll k)
	{
		for(; x <= n; x += x & -x)
			t1[x] += k;
	} 
	//t2 单点修改(这里是对i*diffi单点修改) 
	inline void add2(ll x, ll k)
	{
		for(; x <= n; x += x & -x)
			t2[x] += k;
	} 
	
	//求 diff从1到x的前缀和 
	inline ll prefix1(ll x)
	{
		ll ans = 0;
		for(; x >= 1; x -= x & -x)
			ans += t1[x];
		return ans;
	}
	//求i* diff的从1到x前缀和 
	inline ll prefix2(ll x)
	{
		ll ans = 0;
		for(; x >= 1; x -= x & -x)
			ans += t2[x];
		return ans;
	}
	//区间查询,求原数组[l,r]的区间和 
	inline ll inter_sum(ll l, ll r)
	{
		return ((r+1)*prefix1(r) - prefix2(r)) - (l*prefix1(l - 1) - prefix2(l - 1));
	}
	
	//区间修改,对元素组[l,r]+k 
	inline void inter_add(ll l, ll r, ll k)
	{
		add1(l, k);
		add1(r + 1, -k);
		//同时需要对 i*diff这个数组进行修改
		add2(l, l * k);
		add2(r + 1, - (r + 1) * k); 
	}
};
 
struct BIT bit;

ll q;
struct Node
{
	ll id, l, r, x;
}Q[N];

int main()
{
	ios::sync_with_stdio(false);
	
	cin >> bit.n >> q;
	
	ll last = 0;
	
	for(ll i = 1;i <= bit.n; i ++)
	{
		ll temp;
		cin >> temp;
		bit.add1(i, temp - last);
		bit.add2(i, i * (temp - last));
		last = temp; 
	}
	for(ll i = 1; i <= q; i ++)
	{
		cin >> Q[i].id;
		if(Q[i].id == 1)
			cin >> Q[i].l >> Q[i].r >> Q[i].x;
		else if(Q[i].id == 2)
			cin >> Q[i].l >> Q[i].r;
	}
	
	for(ll i = 1; i <= q; i ++)
	{
		if(Q[i].id == 1)
			bit.inter_add(Q[i].l, Q[i].r, Q[i].x);
		else if(Q[i].id == 2)
			cout << bit.inter_sum(Q[i].l, Q[i].r) << endl;
	}
	return 0;
}

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值