zoukankan      html  css  js  c++  java
  • 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[i1]bc,suma设a[i],c[i]分别表示原数列的第i个位置的值,第i个位置的变化量(可以为负),b[i]=c[i]-c[i-1]——b为c的差分数组,sum为a的前缀和
    那么i=1xb[i]=c[x]sum_{i=1}^x b[i]=c[x],于是a[x]+i=1xb[i]=a[x]+c[x](i)a[x]+sum_{i=1}^x b[i]=a[x]+c[x](第i个位置的当前值)
    那么前x项的和可表示为:
    sum[x]+i=1xj=1ib[j]=sum[x]+i=1x(xi+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=1x(x+1)b[i]i=1xib[i]sum[x]+sum_{i=1}^x (x+1)b[i]-sum_{i=1}^x i*b[i]((Σ)便i(x+1)x+1(这样前缀和(两个Sigma内的)就不方便用树状数组维护,因为前缀和中有与i无关的项(x+1),所以我们需要把x+1提出来)
    sum[x]+(x+1)i=1xb[i]i=1xib[i]sum[x]+(x+1)sum_{i=1}^x b[i]-sum_{i=1}^x i*b[i]
    那么我们用一个数组维护b[i]b[i]的前缀和,一个数组维护b[i]ib[i]*i的前缀和就行.
    (代码中具体为c[0],c[1])
    不禁感叹这种思路的巧妙
    自制卡long longlong ~long的数据之输入输出

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define g getchar()
    using namespace std;
    typedef long long ll;
    const int N=1e5+10;
    int n,m;
    long double c[2][N];//开long double是因为long long会溢出.(自己出的数据把自己的代码hack了——qwq) ,但是poj开long long照样能过.
    ll sum[N];
    long double ask(int k,int x)
    {
    	long double y=0;
    	for( ;x;x-=x&-x)y+=c[k][x];
    	return y;
    }
    void add(int k,int x,ll y)
    {
    	for( ;x<=n;x+=x&-x)c[k][x]+=y;
    }
    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)x=-x,putchar('-');
    	write(x);puts("");
    }
    int main()
    {
    	qr(n);qr(m);
    	for(int i=1;i<=n;i++)
    		qr(sum[i]),sum[i]+=sum[i-1];
    	while(m--)
    	{
    		char s[2];scanf("%s",s);
    		ll l,r,d;qr(l);qr(r);
    		switch(s[0]){
    			case 'C':
    				qr(d);
    				if(abs(d)>1e9){puts("No");return 0;}
    				add(0,l,d);
    				add(0,r+1,-d);
    				add(1,l,l*d);
    				add(1,r+1,-(r+1)*d);
    				break;
    			case 'Q':
    				ll ans=sum[r]-sum[l-1]-l*ask(0,l-1)+(r+1)*ask(0,r)+ask(1,l-1)-ask(1,r);
    				//sum[r]+(r+1)*ask(0,r)-ask(1,r)-(sum[l-1]+l*ask(0,l-1)-ask(1,l-1))
    				pri(ans);
    				break;
    			}
    	}
    	return 0;
    }
    

    其实sum数组可以省掉.就是b[i]=a[i]a[i1]b[i]=a[i]-a[i-1].(初始化b就行)
    代码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define g getchar()
    using namespace std;
    typedef long long ll;
    const int N=1e5+10;
    int n,m;
    long double c[2][N];//开long double是因为long long会溢出.(自己出的数据把自己的代码hack了——qwq) 
    long double ask(int k,int x)
    {
    	long double y=0;
    	for( ;x;x-=x&-x)y+=c[k][x];
    	return y;
    }
    void add(int k,int x,ll y)
    {
    	for( ;x<=n;x+=x&-x)c[k][x]+=y;
    }
    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)x=-x,putchar('-');
    	write(x);puts("");
    }
    int main()
    {
    	qr(n);qr(m);
    	ll a,b=0;
    	for(int i=1,j;i<=n;i++)
    	{
    		qr(a);
    		c[0][i]+=a-b;
    		c[1][i]+=i*(a-b);
    		j=i+(i&-i);
    		if(j<=n)
    			c[0][j]+=c[0][i],c[1][j]+=c[1][i];
    		b=a;
    	}
    	while(m--)
    	{
    		char s[2];scanf("%s",s);
    		ll l,r,d;qr(l);qr(r);
    		switch(s[0]){
    			case 'C':
    				qr(d);
    				if(abs(d)>1e9){puts("No");return 0;}
    				add(0,l,d);
    				add(0,r+1,-d);
    				add(1,l,l*d);//这样写是因为上面定义的b[l]加上了d,为了维护b[i]*i的前缀和,所以应该加上d*l
    				add(1,r+1,-(r+1)*d);//类似上面
    				break;
    			case 'Q':
    				ll ans=(r+1)*ask(0,r)-ask(1,r)-(l*ask(0,l-1)-ask(1,l-1)); 
    				pri(ans);
    				break;
    			}
    	}
    	return 0;
    }
    

    分块

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

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

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

    我们把数列A分成若干块长度不大于sqrt(n)sqrt(n)的块,其中第i块的左端点为(i1)sqrt(n)(i-1)*sqrt(n),右端点为isqrt(n)i*sqrt(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;
    }
    
  • 相关阅读:
    小米手机miui8.5连接电脑
    js数组map方法
    wxui入门
    动画函数封装
    系列属性(offset、scroll、client)
    定时器( setInterval和 setTimeout)
    BOM(浏览器对象模型)
    事件(绑定、解绑、冒泡)
    元素(element)创建
    节点(node)操作
  • 原文地址:https://www.cnblogs.com/zsyzlzy/p/12373904.html
Copyright © 2011-2022 走看看