caioj 2057(2060)& poj 3468 & CH 0x40数据结构进阶(0x43 线段树)例题3:A Simple Problem with Integers

传送门

很明显可以用线段树!(简直是模板题)

线段树:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define g getchar()
#define lc (p<<1)
#define rc (p<<1|1)
using namespace std;
typedef long long ll;
const int N=1e5+10;
struct node{int l,r;ll c,ad;}tr[N<<2];
int n,m;
ll a[N];
void bt(int p,int l,int r)
{
	tr[p].l=l;tr[p].r=r;
	if(tr[p].l==tr[p].r){tr[p].c=a[l];return;}
	int mid=(l+r)>>1;
	bt(lc,l,mid);bt(rc,mid+1,r);
	tr[p].c=tr[lc].c+tr[rc].c;
}
void wh(int p)
{
	tr[lc].ad+=tr[p].ad;tr[rc].ad+=tr[p].ad;
	tr[lc].c+=tr[p].ad*(tr[lc].r-tr[lc].l+1);
	tr[rc].c+=tr[p].ad*(tr[rc].r-tr[rc].l+1);
	tr[p].ad=0;
}
void add(int p,int l,int r,ll k)
{
	if(l<=tr[p].l&&tr[p].r<=r)
	{tr[p].ad+=k;tr[p].c+=k*(tr[p].r-tr[p].l+1);return;}
	if(tr[p].ad)wh(p);
	int mid=(tr[p].l+tr[p].r)>>1;
	if(l<=mid)add(lc,l,r,k);
	if(mid<r)add(rc,l,r,k);
	tr[p].c=tr[lc].c+tr[rc].c;
}
ll ans;
void ask(int p,int l,int r)
{
	if(l<=tr[p].l&&tr[p].r<=r){ans+=tr[p].c;return;}
	if(tr[p].ad)wh(p);
	int mid=(tr[p].l+tr[p].r)>>1;
	if(l<=mid)ask(lc,l,r);
	if(mid<r)ask(rc,l,r);
}
template<class o>
void qr(o&x)
{
	char c=g;bool v=(x=0);
	while(!( ('0'<=c&&c<='9') || c=='-' ))c=g;
	if(c=='-')v=1,c=g;
	while('0'<=c&&c<='9')x=x*10+c-'0',c=g;
	if(v)x=-x;
}
void write(ll x)
{
	if(x/10)write(x/10);
	putchar(x%10+'0');
}
void pri(ll x)
{
	if(x<0)putchar('-'),x=-x;
	write(x);puts("");
}
int main()
{
	qr(n);qr(m);
	for(int i=1;i<=n;i++)qr(a[i]);
	bt(1,1,n);
	while(m--)
	{
		char s[2];int l,r;ll d;
		scanf("%s",s);qr(l);qr(r);
		switch(s[0]){
			case 'C':
				qr(d);
				add(1,l,r,d);
				break;
			case 'Q':
				ans=0;
				ask(1,l,r);
				pri(ans);
				break;
			}
	}
	return 0;
}

树状数组:

树状数组是只能维护前缀和的,但是这题一点树状数组的痕迹都没有.QwQ
对于区间加,我们很容易联想到差分。那我们试试吧。
设 a [ i ] , c [ i ] 分 别 表 示 原 数 列 的 第 i 个 位 置 的 值 , 第 i 个 位 置 的 变 化 量 ( 可 以 为 负 ) , b [ i ] = c [ i ] − c [ i − 1 ] — — b 为 c 的 差 分 数 组 , s u m 为 a 的 前 缀 和 设a[i],c[i]分别表示原数列的第i个位置的值,第i个位置的变化量(可以为负),b[i]=c[i]-c[i-1]——b为c的差分数组,sum为a的前缀和 a[i],c[i]i,i(),b[i]=c[i]c[i1]bc,suma
那么 ∑ i = 1 x b [ i ] = c [ x ] \sum_{i=1}^x b[i]=c[x] i=1xb[i]=c[x],于是 a [ x ] + ∑ i = 1 x b [ i ] = a [ x ] + c [ x ] ( 第 i 个 位 置 的 当 前 值 ) a[x]+\sum_{i=1}^x b[i]=a[x]+c[x](第i个位置的当前值) a[x]+i=1xb[i]=a[x]+c[x](i)
那么前x项的和可表示为:
s u m [ x ] + ∑ i = 1 x ∑ j = 1 i b [ j ] = s u m [ x ] + ∑ i = 1 x ( x − i + 1 ) ∗ b [ i ] = sum[x]+\sum_{i=1}^x \sum_{j=1}^i b[j]=sum[x]+\sum_{i=1}^x(x-i+1)*b[i]= sum[x]+i=1xj=1ib[j]=sum[x]+i=1x(xi+1)b[i]=
s u m [ x ] + ∑ i = 1 x ( x + 1 ) b [ i ] − ∑ i = 1 x i ∗ b [ i ] sum[x]+\sum_{i=1}^x (x+1)b[i]-\sum_{i=1}^x i*b[i] sum[x]+i=1x(x+1)b[i]i=1xib[i] ( 这 样 前 缀 和 ( 两 个 Σ 内 的 ) 就 不 方 便 用 树 状 数 组 维 护 , 因 为 前 缀 和 中 有 与 i 无 关 的 项 ( x + 1 ) , 所 以 我 们 需 要 把 x + 1 提 出 来 ) (这样前缀和(两个\Sigma内的)就不方便用树状数组维护,因为前缀和中有与i无关的项(x+1),所以我们需要把x+1提出来) ((Σ)便i(x+1)x+1
s u m [ x ] + ( x + 1 ) ∑ i = 1 x b [ i ] − ∑ i = 1 x i ∗ b [ i ] sum[x]+(x+1)\sum_{i=1}^x b[i]-\sum_{i=1}^x i*b[i] sum[x]+(x+1)i=1xb[i]i=1xib[i]
那么我们用一个数组维护 b [ i ] b[i] b[i]的前缀和,一个数组维护 b [ i ] ∗ i b[i]*i b[i]i的前缀和就行.
(代码中具体为 c 1 , c 2 c1,c2 c1,c2)
不禁感叹这种思路的巧妙

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define gc getchar()
#define ll long long
#define TP template<class o>
using namespace std;
const int N=1e5+10;

TP void qr(o&x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)){if(c=='-') f=-1; c=gc;}
	while(isdigit(c))x=x*10+c-'0',c=gc;
	x*=f;
}

TP void qw(o x) {
	if(x<0) putchar('-'),x=-x;
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}

TP void pr2(o x) {qw(x); puts("");}

int n,m;
ll a[N],c1[N],c2[N];

void add(int x,ll y) {
	for(ll z=x*y;x<=n;x+=x&-x)
		c1[x]+=y,c2[x]+=z;
}
void add(int l,int r,ll d) {add(l,d); add(r+1,-d);}

ll ask(int x) {
	ll s=0;
	for(ll y=x+1;x;x&=x-1) s+=y*c1[x]-c2[x];
	return s;
}
ll ask(int l,int r) {return ask(r)-ask(l-1)+a[r]-a[l-1];}

int main() {
	qr(n); qr(m);
	for(int i=1;i<=n;i++) qr(a[i]),a[i]+=a[i-1];
	while(m--) {
		char s[5];int l,r,d;
		scanf("%s",s); qr(l); qr(r);
		if(s[0]=='C') qr(d),add(l,r,d);
		else pr2(ask(l,r));
	}
	return 0;
}

分块

之前我们学 B S G S ( A x ≡ B ( m o d    C ) ) BSGS(A^x≡B(mod~~C)) BSGS(AxB(mod  C))就用到了分块的思想.
以后我们学莫比乌斯反演的时候,会用到整除分块——一种十分巧妙的方法。

现在我们来做算法进阶给出的分块模板题。

分块是一种用空间换取时间,达到时空平衡的“朴素算法”。效率往往比不上树状数组与线段树,但是它更加通用,容易实现。大部分常见的分块思想都可以用“大段维护,局部朴素”来形容

我们把数列A分成若干块长度不大于 s q r t ( n ) sqrt(n) sqrt(n)的块,其中第i块的左端点为 ( i − 1 ) ∗ s q r t ( n ) (i-1)*sqrt(n) (i1)sqrt(n),右端点为 i ∗ s q r t ( n ) i*sqrt(n) isqrt(n).
对于大段操作,我们直接给段打上标记.至于小段,直接暴力.
代码:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define g getchar()
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int T=333;//段数 
ll a[N],sum[T],add[T];
int L[T],R[T],n,m,t,pos[N];
void change(int l,int r,ll d)//大段维护,小段朴素 
{
	int p=pos[l],q=pos[r];
	if(p==q)
	{
		for(int i=l;i<=r;i++)a[i]+=d;
		sum[p]+=d*(r-l+1);
	}
	else
	{
		for(int i=p+1;i<=q-1;i++)add[i]+=d;
		for(int i=l;i<=R[p];i++)a[i]+=d;
		sum[p]+=d*(R[p]-l+1);
		for(int i=L[q];i<=r;i++)a[i]+=d;
		sum[q]+=d*(r-L[q]+1);
	}
}
ll ask(int l,int r)
{
	int p=pos[l],q=pos[r];
	ll ans=0;
	if(p==q)
	{
		for(int i=l;i<=r;i++)ans+=a[i];
		ans+=add[p]*(r-l+1);
	}
	else
	{
		for(int i=p+1;i<=q-1;i++)
			ans+=sum[i]+add[i]*(R[i]-L[i]+1);
		for(int i=l;i<=R[p];i++)ans+=a[i];
		ans+=add[p]*(R[p]-l+1);
		for(int i=L[q];i<=r;i++)ans+=a[i];
		ans+=add[q]*(r-L[q]+1);
	}
	return ans;
}
template<class o>
void qr(o&x)
{
	char c=g;bool v=(x=0);
	while(!( ('0'<=c&&c<='9') || c=='-' ))c=g;
	if(c=='-')v=1,c=g;
	while('0'<=c&&c<='9')x=x*10+c-'0',c=g;
	if(v)x=-x;
}
void write(ll x)
{
	if(x/10)write(x/10);
	putchar(x%10+'0');
}
void pri(ll x)
{
	if(x<0)putchar('-'),x=-x;
	write(x);puts("");
}
int main()
{
	qr(n);qr(m);
	for(int i=1;i<=n;i++)qr(a[i]);
	//分块 
	t=sqrt(n*1.0);
	for(int i=1;i<=t;i++)
	{
		L[i]=R[i-1]+1;
		R[i]=i*t;
	}
	if(R[t]<n)t++,L[t]=R[t-1]+1,R[t]=n;
	//预处理 
	for(int i=1;i<=t;i++)
		for(int j=L[i];j<=R[i];j++)
			pos[j]=i,sum[i]+=a[j];
	//指令 
	while(m--)
	{
		char s[2];scanf("%s",s);
		int l,r;ll d;qr(l);qr(r);
		switch(s[0]){
			case 'C':
				qr(d);change(l,r,d);
				break;
			case 'Q':
				pri(ask(l,r));
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值