【洛谷P4231】三步必杀【差分】

题目大意:

题目链接:https://www.luogu.org/problemnew/show/P4231
一个序列,初始值全部为0。每次修改一段区间 [ l , r ] [l,r] ,区间 [ l , r ] [l,r] 中第 i i 个元素加上等差数列的第 i i 项。求最终数列的异或和以及最大值。


思路:

n 1 0 7 n\leq 10^7 。连带 l o g log 的数据结构都不给过。
由于每次加的是一个等比数列,所以考虑使用差分来解决这个问题。
假设原数列为 a a ,差分数列为 b b

编号 1 2 3 4 5 6 7 8 9
a a 0 0 0 0 0 0 0 0 0
b b 0 0 0 0 0 0 0 0 0

这次在 [ 3 , 6 ] [3,6] 中加入等差数列,首项加 x x ,莫项加 y y ,公差为 d d

编号 1 2 3 4 5 6 7 8 9
a a 0 0 x x x + d x+d x + 2 d x+2d x + 3 d ( y ) x+3d(y) 0 0 0
b b 0 0 x x d d d d d d x 3 d ( y ) -x-3d(-y) 0 0

那么现在就是一个单点修改+区间修改的问题了。用线段树时间复杂度 O ( n log n ) O(n\log n)
这样的复杂度还是不够优秀,如果可以把区间修改去掉,那么就可以直接在数列上单点修改了。
那么就在 b b 数组上再进行一次差分,这样就可以把 d d 消掉。

编号 1 2 3 4 5 6 7 8 9
a a 0 0 x x x + d x+d x + 2 d x+2d y y 0 0 0
b b 0 0 x x d d d d d d y -y 0 0
c c 0 0 x x d x d-x 0 0 y d -y-d y y 0

所以我们每次修改时单点修改4个点就可以了。
那么考虑如何用 c c 数组推出最终的 a a 数组。
根据差分,有 a i = j = 1 i x k = 1 j c x k a_i=\sum^{i}_{j=1}\sum^{j}_{xk=1}c_{xk} 。这个 x k = 1 j c x k \sum^{j}_{xk=1}c_{xk} 可以把 c x k c_{xk} 做一个前缀和,然后易得 a i = a i 1 + s u m i a_i=a_{i-1}+sum_i 。这样就 O ( n + m ) O(n+m) 完成了这道题。


代码:

#include <cstdio>
#include <string>
#include <algorithm>
using namespace std;
typedef long long ll;

const int N=1e7+10;
int n,m,l,r;
ll a[N],sum[N],add,x,y,ans1,ans2;

ll read()
{
	ll d=0;
	char ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	while (isdigit(ch))
		d=(d<<3)+(d<<1)+ch-48,ch=getchar();
	return d;
}

int main()
{
	n=(int)read(); m=(int)read();
	for (int i=1;i<=m;i++)
	{
		l=(int)read(); r=(int)read(); x=read(); y=read();
		add=(y-x)/(ll)(r-l);
		a[l]+=x;
		a[l+1]+=add-x;
		a[r+1]-=add+y;
		a[r+2]+=y;
	}
	for (int i=1;i<=n;i++)
	{
		sum[i]=sum[i-1]+a[i];
		a[i]=a[i-1]+sum[i];
		ans1^=a[i];
		ans2=max(ans2,a[i]);
	}
	printf("%lld %lld\n",ans1,ans2);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/SSL_ZYC/article/details/94653550