【ZJOI2017】树状数组

题意:树状数组

解析:由于他把树状数组写反了,再由于树状数组维护信息的本质可得它实际上得到的值为区间[l-1,r-1]的值的和,于是我们就很容易的发现一个事实就是如果l-1与r两个位置上的值相同,那么最终询问就是正确的值(以下值均在膜2意义下)

但是由于是两个位置,一维的数据结构必然支撑不起这样的信息维护,那么我们就只能用二维的树套树(矩阵信息显然可以合并)

那么我们考虑一下修改时显然可以分为两种情况就是两个数都在修改区间内,要么就是一个在区间内,一个在区间外,那么我们分别修改即可(都是一个矩形)

其次我们思考一下由于题目中的伪代码中有个特判,就是l==1就return 0,这种情况我们发现当位置r上的值与总的值一样时,答案正确,所以我么只需要额外开个线段树维护一下就行了

优化:这个是这道题的重点,本来要维护两个东西,一个乘,一个加,但是由于他们之间所具有的性质,可以使空间得到大大的节省

性质1:修改之间具有独立性,思考修改可以交换发现这显然,这样就可以标记永久化,节省空间

性质2:乘与加之间的数具有关联,那么可以节省一半空间

注:能不用long long就不要用long long     

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL mod=998244353;
const int N=1e5;
struct node{
	int s;
	int l,r;
};
node tree[N<<2],tree_tree[N*400];
LL inv[N+10];
int n,m,opt,l,r,cnt,sum;
int put_new()
{
	cnt++; 
	tree_tree[cnt].s=0;
	return cnt;
}
void init()
{
	inv[0]=inv[1]=1;
	for (int i=2;i<=n;i++) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
}
void p(int &a,LL x)
{
	a=((LL)a+x)%mod;
}
void mul(int &a,LL x)
{
	a=((LL)a*x)%mod;
}
void change(node &a,LL x){
	mul(a.s,(mod+1-2*x%mod)%mod); p(a.s,x);
}
//opt
void changeopt(int l,int r,int k,int ll,int rr,LL t)
{
	if (l==ll&&r==rr) {
	    change(tree[k],t);
	}
	else {
		int mid=(l+r)>>1;
		if (rr<=mid) changeopt(l,mid,k<<1,ll,rr,t);
		else if (ll>mid) changeopt(mid+1,r,k<<1|1,ll,rr,t);
		else changeopt(l,mid,k<<1,ll,mid,t),changeopt(mid+1,r,k<<1|1,mid+1,rr,t);
	}
}
node findopt(int l,int r,int k,int t)
{
	node x; x.s=0;
	change(x,tree[k].s);
	if (l^r) {
		int mid=(l+r)>>1;
		if (t<=mid) change(x,findopt(l,mid,k<<1,t).s); else change(x,findopt(mid+1,r,k<<1|1,t).s);
	}
    return x;
}
//la
void change_tree_tree(int l,int r,int k,int ll,int rr,LL t,int opt)
{
	if (ll==l&&r==rr) {
        if (opt==1) change(tree_tree[k],t);
        else change(tree_tree[k],2LL*t%mod);
	}
	else {
		int mid=(l+r)>>1;
		if (ll<=mid) {
           if (!tree_tree[k].l) tree_tree[k].l=put_new();
           change_tree_tree(l,mid,tree_tree[k].l,ll,min(rr,mid),t,opt);
		}
		if (rr>mid) {
			if (!tree_tree[k].r) tree_tree[k].r=put_new();
			change_tree_tree(mid+1,r,tree_tree[k].r,max(mid+1,ll),rr,t,opt);
		}
	}
}
void change_tree(int l,int r,int k,int l1,int r1,int l2,int r2,LL t,int opt){
     if (l1==l&&r1==r) {
           change_tree_tree(1,n,k,l2,r2,t,opt);
     } 
     else {
     	int mid=(l+r)>>1;
     	if (r1<=mid) change_tree(l,mid,k<<1,l1,r1,l2,r2,t,opt);
     	else if (l1>mid) change_tree(mid+1,r,k<<1|1,l1,r1,l2,r2,t,opt);
     	else change_tree(l,mid,k<<1,l1,mid,l2,r2,t,opt),change_tree(mid+1,r,k<<1|1,mid+1,r1,l2,r2,t,opt);
     }
}
void change_la(int l,int r)
{
	// cout << "zhouzhendong AK ZJOI2019" << endl;
    changeopt(1,n,1,l,r,inv[r-l+1]);
    // cout << "zhouzhendong AK ZJOI2019" << endl;
    if (l>1) change_tree(1,n,1,1,l-1,l,r,inv[r-l+1],1);
    // cout << "zhouzhendong AK ZJOI2019" << endl;
    if (r<n) change_tree(1,n,1,l,r,r+1,n,inv[r-l+1],1);
    change_tree(1,n,1,l,r,l,r,inv[r-l+1],0);
    // cout << "zhouzhendong AK ZJOI2019" << endl;
}
//findans 
node findans_lalala(int l,int r,int k,int t)
{
	node x; x.s=0;
	change(x,tree_tree[k].s);
	// cout << l << ' ' << r << endl;
	if (l^r){
		int mid=(l+r)>>1;
		if (t<=mid) {
          if (tree_tree[k].l) change(x,findans_lalala(l,mid,tree_tree[k].l,t).s);
		}  else {
			if (tree_tree[k].r) change(x,findans_lalala(mid+1,r,tree_tree[k].r,t).s);
		}
	}
	return x;
}
node findans_la(int l,int r,int k,int t1,int t2)
{
	node x; x.s=0;
	change(x,findans_lalala(1,n,k,t2).s);
       if (l^r) {
       	int mid=(l+r)>>1;
       	if (t1<=mid) change(x,findans_la(l,mid,k<<1,t1,t2).s); else change(x,findans_la(mid+1,r,k<<1|1,t1,t2).s);
       }
    return x;
}
void findans(int l,int r)
{
	// cout << "zhouzhendong AK ZJOI2019" << endl;
	if (l==1) 
	{
		node s=findopt(1,n,1,r);
		LL ans=(mod+1-s.s)%mod;
		if (sum==0) printf("%lld\n",ans);
		else printf("%lld\n",(mod+1-ans)%mod);
	}
	else {
		node s=findans_la(1,n,1,l-1,r); 
		printf("%lld\n",(mod+1-s.s)%mod);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	init();
	for (int i=1;i<=(n<<2);i++) tree[i].s=tree_tree[i].s=0;
	cnt=(n<<2);
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&opt,&l,&r);
		if (opt==1) {
			change_la(l,r); sum=(sum+1)%2;
		} else findans(l,r);
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值