zoukankan      html  css  js  c++  java
  • [ZJOI2019]线段树 solution?

    ( ext{Sooke} ’s solution is good)
    点我


    人在荒原, 见滴水而喜qwq。

    教训
    写短小函数时多看两眼, 可能会减少错误率。

    前置芝士

    [sum_{xin S} x = sum_x x*(x在S中出现的次数) ]


    如果研究每次操作后所有线段树具体形态, 未免太难啦qwq。

    由于所有线段树的结构都一样, 所以建一棵参考用的线段树, 对其每个点 (u) 都记录在第 (i) 次操作后, 有多少棵线段树(当然不包括这棵参考用的)的 (“u”) 节点是有 (tag) 的, 记为 (f_{i,u}), 则第 (i) 次操作的答案就是 (sum_{u in T} f_{i,u})(T) 表示参考线段树的节点集)。

    ( ext{Sooke}) 的题解, 对于一次特定的修改操作, 线段树的节点被分为 (5) 类, 不同类的节点的 (f) 的计算方式显然不相同。

    如图。

    再结合题目给出的伪代码

    发现可以将线段树的节点分为这么几类:

    • 第一类为蓝色点, 修改时被经过, 修改后不会有 (tag)
    • 第二类为紫色点, 修改时于此为终点, 修改后被打上 (tag)
    • 第三类为灰色点, 虽然没有被经过, 但是可能会得到一个 (tag)
    • 第四类为白色点, 修改前后其是否有 (tag) 的状态不会改变。

    可以对每类节点分别维护 (f)

    对于一类点, (f_{i,u} = f_{i-1,u} + 0)
    对于二类点, (f_{i,u} = f_{i-1,u} + 2^{i-1})
    对于四类点, (f_{i,u} = f_{i-1,u} + f_{i-1,u})
    对于三类点, (f_{i,u} = f_{i-1,u} + ( 2^{i-1} - g_{i-1,u} ))

    (g_{i,u}) 表示第 (i) 次操作后, 节点 (u) 到根一个 (tag) 都没有(包括 (u))的线段树个数。

    对于 (g) 的维护, 有:

    对于一类点, (g_{i,u} = g_{i-1,u} + 2^{i-1})
    对于二类点, (g_{i,u} = g_{i-1,u} + 0)
    对于三类点, (g_{i,u} = g_{i-1,u} + g_{i-1,u})
    对于四类点, …………?

    发现白色点(四类点)的 (g) 的转移还跟其父节点的类别有关, 而白色点的父节点又只有灰、紫两种, 所以白色点就要再分成两类。

    这样, 点就分成了 (5) 类, 转移不难得出。 (再次 (orz ext{Sooke})

    接下来就是如何维护线段树内 (f) 和的问题了。
    发现每次操作时,除了白色点(原·四类点)要用懒标记维护外, 剩下的点直接在修改操作中维护就好。

    代码不难(?)写出:

    #include<bits/stdc++.h>
    using namespace std;
    #define li long long
    const int mod = 998244353;
    const int N = 1e6+15;
    int n,m,id=0;
    li jc2[100005];
    
    int rt, tot, ch[N][2];
    li sf[N], f[N], g[N], tf[N], tg[N];
    
    inline li add(li x,li y) { x+=y; return x>=mod ? x-mod : x; }
    inline li sub(li x,li y) { x-=y; return x>=0 ? x : x+mod;}
    inline void ud(int u) { sf[u] = add(f[u], add(sf[ch[u][0]], sf[ch[u][1]])); }
    
    void mlf(int u, li v) { tf[u]=tf[u]*v%mod; f[u]=f[u]*v%mod; sf[u]=sf[u]*v%mod; }
    void mlg(int u, li v) { tg[u]=tg[u]*v%mod; g[u]=g[u]*v%mod; }
    void ps(int u) {
    	if(tf[u]!=1) {
    		mlf(ch[u][0], tf[u]);
    		mlf(ch[u][1], tf[u]);
    		tf[u] = 1ll;
    	}
    	if(tg[u]!=1) {
    		mlg(ch[u][0], tg[u]);
    		mlg(ch[u][1], tg[u]);
    		tg[u] = 1ll;
    	}
    }
    
    void build(int &u,int l,int r) {
    	u = ++tot;
    	g[u] = tf[u] = tg[u] = 1ll;
    	if(l==r) return;
    	int mid=(l+r)>>1;
    	build(ch[u][0],l,mid);
    	build(ch[u][1],mid+1,r);
    	ud(u);
    }
    
    void modi(int u,int l,int r,int x,int y) {
    	if(x<=l&&r<=y) {
    		sf[u] = 2ll*sf[u]%mod;
    		sf[u] = sub(sf[u],f[u]);
    		sf[u] = add(sf[u],jc2[id]);
    		f[u] = add(f[u], jc2[id]);
    		//二类点 
    		tf[u] = 2ll*tf[u]%mod;
    		//给四类点加tag 
    		return;
    	}
    	ps(u);
    	g[u] = add(g[u], jc2[id]);
    	//一类点
    	int mid = (l+r) >> 1;
    	if(y<=mid) {
    		modi(ch[u][0],l,mid,x,y);
    		
    		sf[ch[u][1]] = sf[ch[u][1]]*2ll % mod;
    		sf[ch[u][1]] = sub(sf[ch[u][1]], f[ch[u][1]]);
    		sf[ch[u][1]] = add(sf[ch[u][1]], jc2[id]);
    		sf[ch[u][1]] = sub(sf[ch[u][1]], g[ch[u][1]]);
    		f[ch[u][1]] = add(f[ch[u][1]], jc2[id]);
    		f[ch[u][1]] = sub(f[ch[u][1]], g[ch[u][1]]);
    		g[ch[u][1]] = g[ch[u][1]]*2ll%mod;
    		//右儿子是三类点
    		tf[ch[u][1]] = tf[ch[u][1]]*2ll % mod;
    		tg[ch[u][1]] = tg[ch[u][1]]*2ll % mod;
    		//给五类点加tag 
    	} else if(x>mid) {
    		modi(ch[u][1],mid+1,r,x,y);
    		
    		sf[ch[u][0]] = sf[ch[u][0]]*2ll % mod;
    		sf[ch[u][0]] = sub(sf[ch[u][0]], f[ch[u][0]]);
    		sf[ch[u][0]] = add(sf[ch[u][0]], jc2[id]);
    		sf[ch[u][0]] = sub(sf[ch[u][0]], g[ch[u][0]]);
    		f[ch[u][0]] = add(f[ch[u][0]], jc2[id]);
    		f[ch[u][0]] = sub(f[ch[u][0]], g[ch[u][0]]);
    		g[ch[u][0]] = g[ch[u][0]]*2ll%mod;
    		//左儿子是三类点
    		tf[ch[u][0]] = tf[ch[u][0]]*2ll % mod;
    		tg[ch[u][0]] = tg[ch[u][0]]*2ll % mod;
    		//给五类点加tag
    	} else {
    		modi(ch[u][0],l,mid,x,y);
    		modi(ch[u][1],mid+1,r,x,y);
    	}
    	ud(u);
    }
    
    int main()
    {
    	scanf("%d%d", &n,&m);
    	jc2[0] = 1ll;
    	for(int i=1;i<=m;++i) jc2[i]=(jc2[i-1]*2ll)%mod;
    	build(rt, 1, n);
    	register int op=0, l=0, r=0;
    	while(m--)
    	{
    		scanf("%d", &op);
    		switch(op) {
    			case 1:
    				scanf("%d%d", &l,&r);
    				modi(rt,1,n,l,r);
    				++id;
    				break;
    			case 2:
    				printf("%lld
    ", sf[rt]);
    				break;
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    C#和C++除了语法上的差别外,还有什么其他的区别
    各种指针的的概览及造成原因
    批量操作Tomcat Shell脚本
    pi币pinetwork安装注册教程中文详细版【实操有效】
    Oracle分析函数
    Logger.Xml
    使用Redis / Zookeeper作为分布式锁的一些注意点
    Seata Server配置文件
    .gitignore忽略target无效
    MySql隔离级别:RU / RC / RR / S + 脏读 / 不可重复读 / 幻读 / 可重复读
  • 原文地址:https://www.cnblogs.com/tztqwq/p/13159531.html
Copyright © 2011-2022 走看看