AcWing 算法基础课week 1 总结(万字长文)

发布时间 2023-11-21 15:14:19作者: 一只傲娇璇

AcWing 算法基础课week 1 总结

总结点 1:快速排序(分治思想)

题1:从小到大排序

主体思路:定义一个数x属于数组s,利用双指针,将数组分为大于等于x和小于等于x的两部分,然后递归处理。(具体步骤如下)

1.

1

如上图所示,我们定义一个数组s用来储存n个数据,然后定义两个指针i j,分别指向数组的左右两端,同时i指针逐个向右移动扫描数组,j指针同理向左。

2.

2

当i,j指针扫描的过程中,当s[i]>x时,指针i就停止移动,同理当s[j]<x时,指针j就停止移动,然后交换s[i]与s[j],那么就使得大于x的s[i]去到了右边的数组,小于x的s[j]去到了左边的数组。

while(i<j)
{
    do i++;while(s[i]<x);//i指针向右移动,当s[i]>x,移动停止,j同理
    do j--;while(s[j]>x);
    if(i<j)swap(s[i],s[j]);//如果i<j说明s[j]<x<s[i],就进行交换
}

3.

重复以上操作,直到i>=j为止。然后相同的方式利用递归处理左右两半边的数组,直到子数组长度等于1。

quick_sort(s,l,j);
quick_sort(s,j+1;r);

完整代码如下

int n;
int s[1000010];
void quick_sort(int s[], int l, int r)//将s[]数组的l-r区间内的数据排序
{
	if (l >= r)return;
    //以中点数据值作为分界值,避免边界问题。
    //注意:以s[l],s[r]作为分界值时需要考虑边界问题,所以直接取中点値
	int x = s[l + r >> 1], i = l - 1, j = r + 1;
	while (i < j)
	{
		do i++; while (s[i] < x);//指针移动直到遇到不符合条件的值
		do j--; while (s[j] > x);
		if (i < j)swap(s[i], s[j]);//交换两值
	}
	quick_sort(s, l, j);//递归处理左右两边
	quick_sort(s, j + 1, r);
}
int main()
{
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> s[i];
	}
	quick_sort(s, 0, n - 1);//将s[]数组的l-r区间内的数据排序
	for (int i = 0; i < n; i++)
	{
		cout << s[i] << ' ';
	}
	return 0;
}

题2:找到第k小的数(快速排序的拓展)

主体思路与快排相同,只是递归方式不同。

扫描方式与快排相同,我们直接说不同点(递归);

当我们利用双指针i,j扫描一个区间时,肯定会将一个区间分成两部分,而我们需要找到第k个数,这个数可能在前半部分或者后半部分,所以我们分情况递归。

1.如果k在前一部分,就只递归前一部分

2.如果在后一部分,就递归后面

3

int sl = j-l+1;//sl记录的是前一部分有多少个数
if(k<=sl) return quick_sort(s,l,j,k);
else return quick_sort(s,j+1,r,k-(j-l+1));
//注意我们这里输入的第四个变量是指所求数在某一区间的相对位置。
//如果在右边,因为左边部分已经有j-l+1个数了,所以我们应该找这一区间的第k-(j-l+1)个数

完整代码:

int n, k;
vector<int> s;
int quick_sort(vector<int> s, int l, int r, int k)
{
	if (l >= r)return s[l];//如果区间长度为1,返回0
	int i = l - 1, j = r + 1, x = s[l + r >> 1];
	while (i < j)
	{
		do i++; while (s[i] < x);
		do j--; while (s[j] > x);
		if (i < j)swap(s[i], s[j]);
	}
	int sl = j - l + 1;//计算左边部分有多少个数
	if (k <= sl)return quick_sort(s, l, j, k);//递归左边
	else return quick_sort(s, j + 1, r, k - (j - l + 1));//递归右边
}
int main()
{
	cin >> n >> k;
	for (int i = 0; i < n; i++)
	{
		int x;
		cin >> x;
		s.push_back(x);
	}
	cout << quick_sort(s, 0, n - 1, k) << endl;
	return 0;
}

总结点2:归并排序(分治思想)

题1:排序

主体思路:将数组均分为两半部分,分别有序化,然后合并两个数组

1.

将数组均分为两半部分,利用递归分别有序化。

int mid = l+r>>1;
merge_sort(s,l,mid);
merge_sort(s,mid+1,r);

2.

合并两个数组。利用两个指针i,j分别指向两个数组的起始位置,如果s[i]<s[j]就将s[i]放入新数组,反之放入s[j]。

4

int temp[1000010];//新数组
int k = 0,i = l,j = mid+1;//k负责控制新数组的下标
while(i<=mid&&j<=r)
{
	if(s[i]<s[j]) temp[k++] = s[i++];
	else temp[k++] = s[j++];
}

3.

因为我们上面使用的循环条件时while(i<=mid&&j<=r),所以当一个数组中的全部元素全部放入新数组时,另一个数组可能会还有剩余的数据,所以我们需要将这些数据也放入新数组中。

while(i<=mid) temp[k++] = s[i++];
while(l<=r) temp[k++] = s[j++];

4.

最后需要将新数组中的数据储存回原数组中,因为递归的过程中需要再次用到原数组,所以必须储存回去。

for(int i = l,j = 0;i<=r;i++,j++) s[i] = temp[j];//i控制原数组,j控制临时数组

完整代码如下:

int n;
int s[1000010];
void a(int s[], int l, int r)
{
	if (l >= r)return;
	int temp[1000010];
	int mid = l + r >> 1;
	a(s, l, mid);
	a(s, mid + 1, r);
	int k = 0, i = l, j = mid + 1;
	while (i <= mid && j <= r)
	{
		if (s[i] < s[j])temp[k++] = s[i++];
		else temp[k++] = s[j++];
	}
	while (i <= mid) temp[k++] = s[i++];
	while (j <= r)temp[k++] = s[j++];
	for (int i = l, j = 0; i <= r; i++, j++)
	{
		s[i] = temp[j];
	}
}
int main()
{
	cin >> n;
	for (int i = 0; i < n; i++)cin >> s[i];
	a(s, 0, n - 1);
	for (int i = 0; i < n; i++)cout << s[i] << ' ';
	return 0;
}

题2:逆序对的数量

主体思想:利用归并排序的将两个数组分别有序化后合并的特点,递归计算逆序对的数量

1.

因为思路同归并排序,我们直接讲不同点。

5

对于一组逆序对,可以分为图中的三种情况:

1.同时在左侧数组

2.同时在右侧数组

3.分别在左右两侧数组

发现对于1,2种情况,当我们将数组进行递归排序时,总有一个区间可以使得两个数分别在数组中点的左右两侧,从而转化为第3种情况,所以我们只考虑第三种情况。

2.

6

考虑图中的情况,因为两个数组都已经有序化,所以对于s[j]发现有s[i]到s[mid]的部分(图中红色部分)都大于s[j],所以后面的所有数都可与s[j]构成第三种逆序对,逆序对的数量为mid-i+1。

3.

完整代码实现:

const int N = 100010;
int n;
int a[N];
long long ans;
long long merge_sort(int l, int r)
{
	if (l >= r)return 0;
	int mid = l + r >> 1;//中点
	merge_sort(l, mid);//递归处理左右两边,是左右两边有序化
	merge_sort(mid + 1, r);
	int temp[N];//临时数组
	int i = l, j = mid + 1, k = 0;
	while (i <= mid && j <= r)
	{
		if (a[i] <= a[j])temp[k++] = a[i++];//将小的数放入临时数组
		else
		{
			temp[k++] = a[j++];
            //如果s[i]>s[j],说明s[i]-s[mid]的数都与s[j]构成逆序对
            //个数为mid-i+1
			ans += mid - i + 1;
		}
	}
	while (i <= mid) temp[k++] = a[i++];
	while (j <= r)temp[k++] = a[j++];
	for (int i = l, j = 0; i <= r; i++, j++)a[i] = temp[j];
	return ans;
}
int main()
{
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	cout << merge_sort(0, n - 1) << endl;
	return 0;
}

总结点3:二分

直接讲整数二分,浮点数二分只需要修改细节就好,后面会讲

1.

7

每次找到区间的中点,将s[mid]与x的值相比较。如果是图中的这种情况,x>s[mid] ,所以x存在于右区间。那么调整区间,将区间的左端点调整为mid+1(为什么是mid+1后面会讲),再去循环处理右区间。另一种情况同理。

2.

因为存在边界问题,所以这里的二分需要分情况讨论,对应的代码也有两种。

首先是边界调整的问题。重要的就是那边可以取到最后的值x,就将哪边的边界调整为mid。直接看代码讲。(代码题目是找到x在数组中的位置)

先看第一种

int l = 0,r = n-1//设置左右端点
while(l<r)
{
	int mid = l+r>>1;//设置中点
	if(s[mid]<=x) r = mid;
    //注意此时x可以在这个判断条件中取到,所以这个条件对应的边界调整为mid
	else l = mid+1;
    //另一个对应的调整为mid+1或mid-1;
}

再看第二种

int l = 0,r = n-1;
while(l<r)
{
	int mid = l+r+1>>1;
	if(s[mid]<=x) l = mid;
    //这时x可以在新的条件中取到,所以对应边界调整为mid,其余同理。
	else r = mid-1;
}

3.

另外的,两种情况所移动的方向不同。

当用第一种模版时,将会从数组左向右去找x的值,直到从左向右找到第一个数为止,此时的边界l就是第一个x的位置。

当用第二种模版时,将会从右向左,直到找到一个数为止,l代表找到的第一个x的位置,注意使用这个模版时,mid = l+r+1>>1。

我们下面看例题

题1:找到一个数的范围

主体思路:利用第一种模版找到左边界,然后利用第二种去找右边界

看代码:

int main()
{
	int n, q;//n是数组长度,注意默认是升序排列,然后q个查询
	cin >> n >> q;
	vector<int> s(n);
	for (auto& n : s)
	{
		cin >> n;
	}
	while (q--)
	{
		int x;
		cin >> x;
		int l = 0, r = n - 1;//设置左右边界
		while (l < r)//找到左边界
		{
			int mid = l + r >> 1;
			if (s[mid] >= x)r = mid;
			else l = mid + 1;
		}
		if (s[l] != x)//如果没有X就输出-1 -1
		{
			cout << "-1 -1" << endl;
		}
		else
		{
			cout << l << ' ';
			l = 0; r = n - 1;
			while (l < r)//找到右边界
			{
				int mid = l + r +1>> 1;//注意+1的细节
				if (s[mid] <= x)l = mid;
				else r = mid - 1;
			}
			cout << l << endl;
		}
	}
	return 0;
}

题2:数的三次方根

这个题就是浮点数二分,浮点数二分比整数二分简单,因为没有边界条件,两个值都去调整为mid。

不多说,直接看代码:

double n;
bool check(double x)//利用check函数去判断条件
{
	return x * x * x < n;
}
int main()
{
	cin >> n;
	double l = -10000, r = 10000;//题目中给的左右边界的范围
	while (fabs(r - l) > 1e-8)//浮点数相等的判断
	{
		double mid = (l + r) / 2;
		if (check(mid)) l = mid;//两个都去调整为mid
		else r = mid;
	}
	cout << fixed << setprecision(6) << l;//输出6位小数
	return 0;
}

总结点4:高精度

所有的高精度都是利用数组去储存一个数,然后通过模拟手算的方式去计算。(之前有发过一期高精度,但是看了y神的代码之后,决定去学习一下y神的代码,很美观)

1.高精度加法

高精加的主体思路就是将两个数组的相同位置上的数字相加,如果大于10就进位。

8

核心代码看一下:

vector<int > c;
int t= 0;
for(int i = 0;i<max(a.size(),b.size());i++)
{
    if(i<a.size()) t+=a[i];
    if(i<b.size()) t+=b[i];
    c.push_back(t%10);
    t/=10;
}
if(t) c.push_back(t);
return c;

完整代码如下:

string x, y;
vector<int> add(vector<int > a, vector<int> b)
{
	vector<int>  c;
	int t = 0;
	for (int i = 0; i < max(a.size(), b.size()); i++)
	{
		if (i < a.size())t += a[i];//如果a有这位数字,就加上
		if (i < b.size())t += b[i];
		c.push_back(t % 10);//每次将t的个位数输入到c中
		t /= 10;//保留t的10位进行下一次计算
	}
	if (t)c.push_back(t);//因为加法可能有进位,所以最后检查一下进位
	return c;
}
int main()
{
	cin >> x >> y;
	vector<int> a, b;
    //两个字符串倒序输入到数组中
	for (int i = x.size() - 1; i >= 0; i--)a.push_back(x[i] - '0');
	for (int i = y.size() - 1; i >= 0; i--)b.push_back(y[i] - '0');
	auto c = add(a, b);
    //倒序输出
	for (int i = c.size() - 1; i >= 0; i--)cout << c[i];
	return 0;
}

2.高精度减法

1.两个数相减,如果小于0,就进位。

9

vector<int> c;
int t = 0;
for (int i = 0,t = 0; i < a.size(); i++)
{
	t = a[i] - t;//t储存的是上一位的借位,先减去。
	if (i < b.size())t -= b[i];//如果b存在这位数,就减去
	c.push_back((t + 10) % 10);
    //减去之后,t有可能是负数需要借位,所以利用(t+10)%10,输出借位后的结果
	if (t < 0) t = 1;//如果t<10,就借位
	else t = 0;
}
while (c.size() > 1 && c.back() == 0)c.pop_back();//除前导0
return c;

2.那么到这里就有问题了,上面的代码我们只考虑了A>B的情况,那么A-B<0时怎么办?

很简单,只要用B-A,然后提起输出一个负号就好啦。

那我们先判断A是否大于B;

bool check(vector<int> a,vector<int> b)
{
    //如果位数不一样,位数多的大
    if(a.size()!=b.size()) return a.size()>b.size();
    //如果位数一样,就从高位到低位逐位判断
    //因为我们倒序将字符串转化为了数组,所以高位在最后面
    for(int i = a,size()-1;i>=0;i--)
    {
		if(a[i]!=b[i]) return a[i]>b[i];
    }
}

下面是完整代码:

string x, y;
bool cmp(vector<int> a, vector<int> b)//判断大小
{
	if (a.size() != b.size())return a.size() > b.size();
	for (int i = a.size() - 1; i >= 0; i--)
	{
		if (a[i] != b[i])return a[i] > b[i];
	}
	return true;
}
vector<int> sub(vector<int> a, vector<int> b)
{
	vector<int> c;
	for (int i = 0,t = 0; i < a.size(); i++)
	{
		t = a[i] - t;
		if (i < b.size())t -= b[i];
		c.push_back((t + 10) % 10);
		if (t < 0) t = 1;
		else t = 0;
	}
	while (c.size() > 1 && c.back() == 0)c.pop_back();
	return c;
}
int main()
{
	cin >> x >> y;
	vector<int > a, b;
	for (int i = x.size() - 1; i >= 0; i--)a.push_back(x[i] - '0');
	for (int i = y.size() - 1; i >= 0; i--)b.push_back(y[i] - '0');
	if (cmp(a, b))//如果a>b,正常计算
	{
		auto c = sub(a,b);
		for (int i = c.size() - 1; i >= 0; i--)cout << c[i];
	}
	else//如果b>a
	{
		auto c = sub(b, a);
		cout << '-';//先输出’-‘
        //然后输出b-a
		for (int i = c.size() - 1; i >= 0; i--)cout << c[i];
	}
}

3.高精度乘法(大数乘小数)

10

如图示(利用的就是乘法与c[i]的规律,具体证明省略,如想看,请评论区提问),直接就得到核心代码:

vector<int> mul(vector<int> a, int b)
{
	vector<int> c;
    //注意循环条件
	for (int i = 0, t = 0; i < a.size() || t; i++)//注意乘法时,当a被执行完后,t不一定为0,但还需要继续将t输入到c中
	{
		if (i < a.size())t += a[i] * b;//如果a有这位数,就t+=a[i]*b;
		c.push_back(t % 10);//将t的个位输入到c中
		t /= 10;
	}
	while (c.size() > 1 && c.back() == 0)c.pop_back();//去除前导0
	return c;
}

完整代码如下:

string x;
int b;
vector<int> mul(vector<int> a, int b)
{
	vector<int> c;
	for (int i = 0, t = 0; i < a.size() || t; i++)
	{
		if (i < a.size())t += a[i] * b;
		c.push_back(t % 10);
		t /= 10;
	}
	while (c.size() > 1 && c.back() == 0)c.pop_back();
	return c;
}
int main()
{
	cin >> x >> b;
	vector<int> a;
	for (int i = x.size() - 1; i >= 0; i--)a.push_back(x[i] - '0');
	auto c = mul(a, b);
	for (int i = c.size() - 1; i >= 0; i--)cout << c[i];
	return 0;
}

4.高精度除法

11

如图,我们设定b数组为被除数,a为除数,r为余数。

我们从b高位到低位进行计算,那么第一个商c0就应该等于b[4]/a的商,然后余数r就等于b[4]%a;

下一次的除数就等于r*10+b[i],反复计算。

下面看代码实现:(因为我们要保证加减乘除的输入方式统一,所以在输入a数组时依旧是倒序输入)

//代码中的a与b与图中的a,b相反。

vector<int> div(vector<int> &a, int &b,int &r)
{
	vector<int> c;
	for (int i = a.size() - 1; i >= 0; i--)//因为a是倒序输入,但是除法需要从高位到低位进行计算,所以i = a.size()-1
	{
		r = r * 10 + a[i];//每一次的被除数等于r*10+a[i]
		c.push_back(r / b);//商等于r/b;
		r %= b;余数等于r%b
	}
	reverse(c.begin(), c.end());//反转之后消除前导0
	while (c.size() > 1 && c.back() == 0)c.pop_back();
	return c;
}

完整代码如下:

vector<int> div(vector<int> &a, int &b,int &r)
{
	vector<int> c;
	for (int i = a.size() - 1; i >= 0; i--)
	{
		r = r * 10 + a[i];
		c.push_back(r / b);
		r %= b;
	}
	reverse(c.begin(), c.end());
	while (c.size() > 1 && c.back() == 0)c.pop_back();
	return c;
}

int main()
{
	string x;
	int b;
	cin >> x >> b;
	vector<int> a;
	for (int i = x.size() - 1; i >= 0; i--)a.push_back(x[i] - '0');
	int r = 0;
	auto c = div(a, b,r);
	for (int i = c.size() - 1; i >= 0; i--)
	{
		cout << c[i];
	}
	cout << endl << r << endl;
	return 0;
}

总结点5:前缀和与差分

前缀和与差分互为逆运算。

1.前缀和

首先我们先介绍前缀和。先去定义一个数组a,储存n个数据,然后定义一个数组b,去储存前缀和。

那么b[0] = a[0],b[1] = a[0] + a[1],b[2] = a[0] + a[1] + a[2],.......;

以上的b数组就是a的前缀和数组,

前缀和就是将前k个数相加,这个和就是前缀和。

然后我们根据b数组的特点,可以得出以下的递推公式:

b[i] = b[i-1] + a[i];

有了这个特点之后,我们就可以递推计算出b数组的全部初始值,代码如下:

	vector<int> s(n + 1);
	s[0] = 0;//注意两个数组的边界问题
	for (int i = 1; i < n + 1; i++)
	{
		cin >> s[i];
	}
	vector<int> a;
	a.push_back(0);//由于递推公式中有a[i-1],所以我们的循环从i = 1开始,以避免边界问题,对应的s也从i= 1开始储存。
	for (int i = 1; i <= n; i++)
	{
		a.push_back(a[i - 1] + s[i]);
	}

那么我们有了初始化之后的前缀和数组之后,就可以很快的计算出一个区间内所有元素的和,

我们输入所求期间的左边界l,和右边界r,那么这一段区间的和就可以表示为,b[r]-b[l-1];

完整代码实现如下:

int main()
{
	int n, m;//数组长度为n,询问个数为m
	cin >> n >> m;
	vector<int> s(n + 1);
	s[0] = 0;
	for (int i = 1; i < n + 1; i++)
	{
		cin >> s[i];
	}
	vector<int> a;
	a.push_back(0);
	for (int i = 1; i <= n; i++)
	{
		a.push_back(a[i - 1] + s[i]);
	}
	while (m--)//m个询问
	{
		int l, r;
		cin >> l >> r;
		cout << a[r] - a[l - 1] << endl;
	}
	return 0;
}

2.差分

那么前缀和和差分是分不开的,所以我们下面去介绍差分。

差分和前缀和是互为逆运算,那么我们先给出一个前缀和数组a,然后定义一个原数组b;

所以这个a叫做b的前缀和,b叫做a的差分。

那么我们根据两者的关系,就可以根据前缀和数组求出原数组,然而我们其实并不需要掌握求原数组的方法,重要的是掌握它的性质。

我们去看一个例题:

屏幕截图 2023-11-21 111919

1.首先我们将a,b,数组的全部元素全部初始化为0,

2.我们要进行的操作是在l-r之间的每个数+c,其实我们初始时输入的数组可以看成在i-i之间插入元素a[i],所以将两种操作合并为一种。

下面是重点思路:我们将要进行操作的数组看做前缀和数组a,然后假想一个原数组b。

13

要想在l-r之间的每个数+c,可以将原数组中的b[l]+=c;

那么在a[l]之后所有的a[]将都加上一个c,(因为图中红色部分和绿色部分所有的a[]都含有b[l]这一项);

但我们最终是要在l-r之间的数+c,所以需要打一个补丁(即将a[r+1]-=c,只有绿色部分含有a[r+1],而红色部分不含有),那么这样就使得a[r]之后的所有a[]回归正常。

总结以上的思路,可以得出核心代码:

void insert(int l, int r, int x)
{
	a[l] += x;
	a[r + 1] -= x;
}

完整代码如下:

const int N = 100010;
int a[N];
void insert(int l, int r, int x)//插入函数
{
	a[l] += x;
	a[r + 1] -= x;
}
int main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		int x;
		cin >> x;
		insert(i, i, x);//初始化数组可以看做在i-i之间插入x
	}
	while (m--)//m组操作
	{
		int l, r, c;
		cin >> l >> r >> c;
		insert(l, r, c);
	}
 
	for (int i = 1; i <= n; i++)//利用差分求出前缀和数组
	{
		a[i] += a[i - 1];
	}
	for (int i = 1; i <= n; i++)
	{
		cout << a[i] << ' ';
	}
	return 0;
}

3.二维前缀和

那么有了以上的基础,我们将前缀和拓展到2维,求一下二维前缀和;

同样的道理,我们定义两个二维数组a,b,a为前缀和数组,b为原数组;

14

我们要计算前缀和a[i,j];可以看出a[i,j]由两个红色部分,一个绿色部分和一个棕色部分组成。

那可以得出表达式:a[i,j] = a[i-1,j]->(一个绿色+一个红色)+a[i,j-1]->(一个绿色+一个红色)+b[i,j]->(棕色部分)-a[i-1,j-1]->(减去多余的绿色部分)

由此得出核心代码:

	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];
		}
	}

那么我们得到一个已经初始化的前缀和数组,就可以求出图形中任意一个矩阵中的元素和;

15

例如上图,我们要求白色矩阵中所有的元素和,可以用金色边框内所有的元素减去绿色部分,再减去蓝色部分,最后将两部分重叠的部分加回来,就得到了所求。

代码如下:

cout << b[x2][y2] - b[x1 - 1][y2] - b[x2][y1 - 1] + b[x1 - 1][y1 - 1]<<endl;

完整代码:

const int N = 1010;
int n, m, q;
int a[N][N], b[N][N];
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++)
		{
			b[i][j] = b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1] + a[i][j];
		}
	}
	while (q--)
	{
		int x1, y1, x2, y2;
		cin >> x1 >> y1 >> x2 >> y2;
		cout << b[x2][y2] - b[x1 - 1][y2] - b[x2][y1 - 1] + b[x1 - 1][y1 - 1]<<endl;
	}
	return 0;
}

3.二维差分

同样的不需要掌握如何求差分,只需要掌握它的性质。

直接看一道题

将数组(x2,y2),(x1,y1)构成的矩阵中的所有元素+c

16

我们还是将要操作的数组看做前缀和数组a,假想一个原数组b。

当我们给b[i,j]+c之后,四个涂色部分将全部+c;

然后我们去打补丁:将b[x1,y2+1]-=c,黄色和蓝色部分将-=c;将b[x2+1,y1]-=c,红色部分和蓝色部分将-=c;

然后发现蓝色部分多-=c,所以我们让b[x2+1,y2+1]+=c,那么蓝色部分将+=c;

大功告成,完整代码如下:

const int N = 1010;
int n, m, q;
int a[N][N];
void insert(int x1, int y1, int x2, int y2,int r)
{
	a[x1][y1] += r;
	a[x2 + 1][y2 + 1] += r;
	a[x1][y2 + 1] -= r;
	a[x2 + 1][y1] -= r;
}
int main()
{
	cin >> n >> m >> q;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			int x;
			cin >> x;
			insert(i, j, i, j, x);
		}
	}
	while (q--)
	{
		int x1, y1, x2, y2,r;
		cin >> x1 >> y1 >> x2 >> y2 >> r;
		insert(x1, y1, x2, y2, r);
     }
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
			cout << a[i][j] << ' ';
		}
		cout << endl;
	}
	return 0;
}

以后将不定时更新这个系列,如果对你有帮助的话请点赞支持一下!