【差分专题】&【蓝桥杯备考训练】:差分矩阵图解公式推导、空调、棋盘、重新排序、差分模板、差分矩阵模板【已更新完成】

目录

写在前面(差分矩阵图解):

一维数组:

二维数组:

题目:

1、差分(模板)

2、差分矩阵(模板)

3、空调(USACO 2021 December Contest Bronze)

4、棋盘(第十四届蓝桥杯 省赛 Java A组/C组/研究生组 & Python C组)

5、重新排序(第十三届蓝桥杯 省赛 C++ C组 & JAVA 研究生组 & Python A/C组

有问题请留言


写在前面(差分矩阵图解):

为了方便本篇题目的推进,我们先把差分矩阵的公式推导一遍

一维数组:

首先,我们从一维数组说起,如何把一个数组a变成差分数组?

其实差分数组就是前缀和的逆运算

我们选择从后向前遍历:

我们这里只用一个数组就完成了差分矩阵的转化,注意要从后向前遍历,因为如果从前向后遍历,我们后面用到的a[i-1]已然改变

二维数组:

原理和一维数组相似,但是还要用到容斥原理

注意看这个图中,红色框中的区域是我们的目标区域,这个区域的原数组要在差分数组执行一系列操作后相当于在目标区域上都加上c。

 (x1,y1)和 (x2,y2)表示一个子矩阵的左上角坐标和右下角坐标

涂黄色的区域执行了b[x1][y1]+=c操作,涂绿色的区域执行了b[x2+1][y1]-=c操作,涂紫色的区域执行了  b[x1][y2+1]-=c操作,涂白色的区域执行了 b[x2+1][y2+1]+=c操作。

我们推导出如果要在原数组(x1,y1)~ (x2,y2)构成的区域上加上一个常数c,就相当于在差分矩阵做如下操作:
    b[x1][y1]+=c;
    b[x2+1][y1]-=c;
    b[x1][y2+1]-=c;
    b[x2+1][y2+1]+=c;

于是我们写出insert函数

insert函数

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;
}

这个函数同样也可以用来初始化差分数组,比如把a数组差分到b上,那就相当于在每个位置上加上常数c,即insert(i,j,i,j,c)

我们的差分数组再求前缀和就是原数组

题目:

1、差分(模板)

输入一个长度为 n 的整数序列。

接下来输入 m 个操作,每个操作包含三个整数 l,r,c表示将序列中 [l,r]之间的每个数加上 c。

请你输出进行完所有操作后的序列。

输入格式

第一行包含两个整数 n 和 m。

第二行包含 n 个整数,表示整数序列。

接下来 m 行,每行包含三个整数 l,r,c表示一个操作。

输出格式

共一行,包含 n 个整数,表示最终序列。

数据范围

1≤n,m≤100000
1≤l≤r≤n
−1000≤c≤1000
−1000≤整数序列中元素的值≤1000

输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2
**思路:

见开篇推导区域

代码:
#include<iostream>
using namespace std;
const int N=1000010;
int a[N],b[N];

void insert(int l,int r, int c)
{
    b[l]+=c;
    b[r+1]-=c;
}
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)insert(i,i,a[i]);
    while(m--)
    {
        int l,r,c;
        cin>>l>>r>>c;
        insert(l,r,c);
    }
    for(int i=1;i<=n;i++)
    {
        b[i]+=b[i-1];
    }
    for(int i=1;i<=n;i++)
    {
        cout<<b[i]<<" ";
    }
    return 0;
}

2、差分矩阵(模板)

输入一个 n行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c其中 (x1,y1)和 (x2,y2)表示一个子矩阵的左上角坐标和右下角坐标。

每个操作都要将选中的子矩阵中的每个元素的值加上 c。

请你将进行完所有操作后的矩阵输出。

输入格式

第一行包含整数 n,m,q。

接下来 n 行,每行包含 m 个整数,表示整数矩阵。

接下来 q 行,每行包含 55 个整数 x1,y1,x2,y2,c表示一个操作。

输出格式

共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。

数据范围

1≤n,m≤1000
1≤q≤100000
1≤x1≤x2≤n1
1≤y1≤y2≤m1
−1000≤c≤1000
−1000≤矩阵内元素的值≤1000

输入样例:
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1
输出样例:
2 3 4 1
4 3 4 1
2 2 2 2
**思路:

见开篇题解

代码:
#include<iostream>

using namespace std;

const int N=1010;

int a[N][N],b[N][N];

int n,q,m;

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()
{
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)cin>>a[i][j];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            insert(i,j,i,j,a[i][j]);
            //这个操作相当于在(i,j)到(i,j)这个区域加上a[i][j],不过对应的是差分操作
            //把a[i][j]差分到b[i][j]上
            //最后b[i][j]求前缀和
    while(q--)
    {
        int x1,y1,x2,y2,c;
        cin>>x1>>y1>>x2>>y2>>c;
        insert(x1,y1,x2,y2,c);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)cout<<b[i][j]<<" ";
        cout<<endl;
    }
    return 0;
}

3、空调(USACO 2021 December Contest Bronze)

Farmer John 的 N头奶牛对他们牛棚的室温非常挑剔。

有些奶牛喜欢温度低一些,而有些奶牛则喜欢温度高一些。

Farmer John 的牛棚包含一排 N 个牛栏,编号为 1…N,每个牛栏里有一头牛。

第 i头奶牛希望她的牛栏中的温度是 pi,而现在她的牛栏中的温度是 ti。

为了确保每头奶牛都感到舒适,Farmer John 安装了一个新的空调系统。

该系统进行控制的方式非常有趣,他可以向系统发送命令,告诉它将一组连续的牛栏内的温度升高或降低 11 个单位——例如「将牛栏 5…85…8 的温度升高 11 个单位」。

一组连续的牛栏最短可以仅包含一个牛栏。

请帮助 Farmer John 求出他需要向新的空调系统发送的命令的最小数量,使得每头奶牛的牛栏都处于其中的奶牛的理想温度。

输入格式

输入的第一行包含 N。

下一行包含 N 个非负整数 p1…pN,用空格分隔。

最后一行包含 N 个非负整数 t1…tN。

输出格式

输出一个整数,为 Farmer John 需要使用的最小指令数量。

数据范围

1≤N≤1e5
0≤pi,ti≤100000

输入样例:
5
1 5 3 3 4
1 2 2 2 1
输出样例:
5
样例解释

一组最优的 Farmer John 可以使用的指令如下:

初始温度     :1 2 2 2 1
升高牛棚 2..5:1 3 3 3 2
升高牛棚 2..5:1 4 4 4 3
升高牛棚 2..5:1 5 5 5 4
降低牛棚 3..4:1 5 4 4 4
降低牛棚 3..4:1 5 3 3 4
**思路:

我们要让两个数组的差值归零,就是让两个数组的差值的差分数组归零

让差分数组全部归零的操作,肯定是差分数组中 max(最大正数和,abs(最大负数和))

注意我们为了方便比较,在计算负数和的时候直接取反了

代码:
#include<bits/stdc++.h>

using namespace std;

const int N=1e5+3;

int n;

int a[N],b[N],c[N];

int main()
{
    cin>>n;

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

    for(int i=1;i<=n;i++)
    {
        scanf("%d",&b[i]);
        a[i]=a[i]-b[i];
    }

    for(int i=n;i>0;i--)a[i]-=a[i-1];
    //注意差分要倒着求,因为正着求的时候a【i-1】已经改变

    //我们要让两个数组的差值归零,就是让两个数组的差值的差分数组归零
    //让差分数组全部归零的操作,肯定是差分数组中max(最大正数和,abs(最大负数和))

    int pos=0,neg=0;

    for(int i=1;i<=n;i++)
    {
        if(a[i]>0)pos+=a[i];
        if(a[i]<0)neg-=a[i];//这里直接取相反的值,最后直接max比大小
    }

    cout<<max(pos,neg);

    return 0;
}

4、棋盘(第十四届蓝桥杯 省赛 Java A组/C组/研究生组 & Python C组)

小蓝拥有 n×n 大小的棋盘,一开始棋盘上全都是白子。

小蓝进行了 m 次操作,每次操作会将棋盘上某个范围内的所有棋子的颜色取反(也就是白色棋子变为黑色,黑色棋子变为白色)。

请输出所有操作做完后棋盘上每个棋子的颜色。

输入格式

输入的第一行包含两个整数 n,m用一个空格分隔,表示棋盘大小与操作数。

接下来 m 行每行包含四个整数 x1,y1,x2,y2,相邻整数之间使用一个空格分隔,表示将在 x1 至 x2 行和 y1至 y2 列中的棋子颜色取反。

输出格式

输出 n行,每行 n个0 或 1表示该位置棋子的颜色。

如果是白色则输出 0,否则输出 1。

数据范围

对于 30%的评测用例,1≤n,m≤500
对于所有评测用例,1≤n,m≤2000,1≤x1≤x2≤n1,1≤y1≤y2≤n1。

输入样例:
3 3
1 1 2 2
2 2 3 3
1 1 3 3
输出样例:
001
010
100
**思路:

一开始全是0,表示白色棋子,我们要想知道在多次取反之后棋子的颜色状态,只需要在每次取反操作的时候在原棋盘上加上1,最后%2即可,%2结果为0则是白色,不为0则为黑色

为了增快操作速度,我们选择差分矩阵进行操作:把原棋盘变成一个差分棋盘进行操作,之后再求前缀和进行还原

代码:
//每次加上1,最后处理询问的时候%2,奇数就是黑,偶数就是白(不管怎么加都是这样,因为一开始0都是白色)
#include<bits/stdc++.h>

using namespace std;

int n,m;

const int N=2003;

int a[N][N],b[N][N];

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()
{
	cin>>n>>m;
	
	//for(int i=1;i<=n;i++) 
		//for(int j=1;i<=n;j++)
		//{
			//scanf("%d",&a[i][j]);
			//insert(i,j,i,j,a[i][j]);
	//	}//将a差分进b
		
	//询问
	for(int i=1;i<=m;i++)
	{
		int x1,y1,x2,y2;
		cin>>x1>>y1>>x2>>y2;
		insert(x1,y1,x2,y2,1);
	}	
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
			if(!(b[i][j]%2))cout<<0;
			else cout<<1;	
		}
		cout<<endl;
	}

	return 0;
} 

5、重新排序(第十三届蓝桥杯 省赛 C++ C组 & JAVA 研究生组 & Python A/C组)

输入格式

输出格式

输出一行包含一个整数表示答案。

数据范围

输入样例:
5
1 2 3 4 5
2
1 3
2 5
输出样例:
4
样例解释

原来的和为 6+14=206+14=20,重新排列为 (1,4,5,2,3)(1,4,5,2,3) 后和为 10+14=2410+14=24,增加了 44。

**思路:

我们只需要知道哪个位置被选中的次数最多,给他分配最大的数即可,第二次数多的给他分配第二大的数字,以此类推

首先题目中给的 l 和 r 读入b数组,于是b数组就是一个天然的差分数组,求前缀和即可求出每个位置被选中的次数

然后我们求原来的总和,b的前缀和数组每个位置乘原数组对应的位置相加即可

然后排序b的前缀和数组,再把原数组排序,对应的位置相乘即可求出重新排序后的最大总和值

最后最大总和值减去原来的总和就是答案

代码:
//用差分处理每个位置被询问的次数
#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

int n,m;

const int N=1e5+3;

int a[N],b[N];

int main()
{
    cin>>n;
    //cout<<n<<m<<endl;
    for(int i=1;i<=n;i++)
    {
    	scanf("%d",&a[i]);
    }
    cin>>m;
    for(int i=0;i<m;i++)
    {
    	int l,r;
    	cin>>l>>r;
    	//cout<<l<<" "<<r<<endl;
    	b[l]++;
    	b[r+1]--;
    	
    	//区间两端,经过前缀和计算后就是 询问次数 
    }
    
    LL sum1=0,sum2=0;//sum1是原来的,sum是变大的 
    
    for(int i=1;i<=n;i++)//区间范围是小于等于n的,所以枚举到n即可(一开始想枚举到N) 
    {
        //cout<<b[i]<<endl;
    	b[i]=b[i]+b[i-1]; 
    	//cout<<b[i]<<endl;
    	sum1+=(LL)a[i]*b[i];
    }
    
    sort(a+1,a+n+1);sort(b+1,b+n+1);
    
    for(int i=1;i<=n;i++)
    {
    	sum2+=(LL)a[i]*b[i];
    }
    
    cout<<sum2-sum1;
    
    return 0;
}

有问题请留言

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值