洛谷 P5280 [ZJOI2019]线段树 dp+线段树

版权声明:2333 https://blog.csdn.net/liangzihao1/article/details/89325583

题目描述
九条可怜是一个喜欢数据结构的女孩子,在常见的数据结构中,可怜最喜欢的就是线段树。

线段树的核心是懒标记,下面是一个带懒标记的线段树的伪代码,其中 t a g tag 数组为懒标记:

其中函数 Lson ( N o d e ) \operatorname{Lson}(Node) 表示 N o d e Node 的左儿子, Rson ( N o d e ) \operatorname{Rson}(Node) 表示 N o d e Node 的右儿子。

现在可怜手上有一棵 [ 1 , n ] [1,n] 上的线段树,编号为 1 1 。这棵线段树上的所有节点的 t a g tag 均为 0 0 。接下来可怜进行了 m m 次操作,操作有两种:

1   l   r 1\ l\ r ,假设可怜当前手上有 t t 棵线段树,可怜会把每棵线段树复制两份( t a g tag 数组也一起复制),原先编号为 i i 的线段树复制得到的两棵编号为 2 i 1 2i-1 2 i 2i ,在复制结束后,可怜手上一共有 2 t 2t 棵线段树。接着,可怜会对所有编号为奇数的线段树进行一次 Modify ( r o o t , 1 , n , l , r ) \operatorname{Modify}(root,1,n,l,r)

2 2 ,可怜定义一棵线段树的权值为它上面有多少个节点 t a g tag 1 1 。可怜想要知道她手上所有线段树的权值和是多少。

输入输出格式
输入格式:
第一行输入两个整数 n , m n,m 表示初始区间长度和操作个数。

接下来 m m 行每行描述一个操作,输入保证 1 l r n 1 \le l \le r \le n

输出格式:
对于每次询问,输出一行一个整数表示答案,答案可能很大,对 998244353 998244353 取模后输出即可。

输入输出样例
输入样例#1:
5 5
2
1 1 3
2
1 3 5
2
输出样例#1:
0
1
6
说明
[ 1 , 5 ] [1,5] 上的线段树如下图所示:

在第一次询问时,可怜手上有一棵线段树,它所有点上都没有标记,因此答案为 0 0

在第二次询问时,可怜手上有两棵线段树,按照编号,它们的标记情况为:

[ 1 , 3 ] [1,3] 上有标记,权值为 1 1
没有点有标记,权值为 0 0
因此答案为 1 1

在第三次询问时,可怜手上有四棵线段树,按照编号,它们的标记情况为:

[ 1 , 2 ] , [ 3 , 3 ] , [ 4 , 5 ] [1,2],[3,3],[4,5] 上有标记,权值为 3 3
[ 1 , 3 ] [1,3] 上有标记,权值为 1 1
[ 3 , 3 ] , [ 4 , 5 ] [3,3],[4,5] 上有标记,权值为 2 2
没有点有标记,权值为 0 0
因此答案为 6 6

分析:
我们把线段树上的点分成5类。
在这里插入图片描述
假设操作区间为 [ x , y ] [x,y] ,第一类点区间 [ l , r ] [l,r] 包含 [ x , y ] [x,y] 。也就是线段树操作经过的点,即白色点。
第二类点是修改点,即打上 t a g tag 的点,深灰色点。
第三类点是不直接遍历到,但是可以通过下传改变的点,即橙色点。
第四类点是第二类点的儿子,第五类点是第三类点的儿子。

我们设 f [ i ] [ j ] f[i][j] 表示前 i i 次操作后, j j 号节点 t a g tag 的个数。
g [ i ] [ j ] g[i][j] 表示第 i i 次操作后, j j 到根路径上不含 t a g tag 的个数。

对于第一类点,显然所有点 t a g tag 都被清掉,
f [ i ] [ j ] = f [ i 1 ] [ j ] f[i][j]=f[i-1][j]
g [ i ] [ j ] = g [ i 1 ] [ j ] + 2 i 1 g[i][j]=g[i-1][j]+2^{i-1}

第二类点都打上了 t a g tag
f [ i ] [ j ] = f [ i 1 ] [ j ] + 2 i 1 f[i][j]=f[i-1][j]+2^{i-1}
g [ i ] [ j ] = g [ i 1 ] [ j ] g[i][j]=g[i-1][j]

第三类点取决于上面有没有 t a g tag
f [ i ] [ j ] = f [ i 1 ] [ j ] + 2 i 1 g [ i 1 ] [ j ] f[i][j]=f[i-1][j]+2^{i-1}-g[i-1][j]
g [ i ] [ j ] = 2 g [ i 1 ] [ j ] g[i][j]=2*g[i-1][j]

第四类点和第五类点类似,都是影响不到的,只是修改后四类点的 g g 一定是0
f [ i ] [ j ] = 2 f [ i 1 ] [ j ] f[i][j]=2*f[i-1][j]
g [ i ] [ j ] = 0 + g [ i 1 ] [ j ] g[i][j]=0+g[i-1][j]

f [ i ] [ j ] = 2 f [ i 1 ] [ j ] f[i][j]=2*f[i-1][j]
g [ i ] [ j ] = 2 g [ i 1 ] [ j ] g[i][j]=2*g[i-1][j]

其中一二三类点为 O ( l o g n ) O(logn) 个,四五类点为 O ( n ) O(n) 个。对于四五类点,可以通过线段树打tag的方式,也就是个最浅的儿子打上乘2的tag。

代码:

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long

const int maxn=2e6+7;
const LL mod=998244353;

using namespace std;

int n,m,k,op,x,y;
LL f[maxn],g[maxn],a[maxn],b[maxn],sum[maxn];

void build(int p,int l,int r)
{
    g[p]=a[p]=b[p]=1;
    if (l==r) return;
    int mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
}

void pushf(int p,LL k)
{
    f[p]=f[p]*k%mod;
    sum[p]=sum[p]*k%mod;
    a[p]=a[p]*k%mod;
}

void pushg(int p,LL k)
{
    g[p]=g[p]*k%mod;
    b[p]=b[p]*k%mod;
}

void pushdown(int p)
{
    if (a[p]!=1)
    {
        pushf(p*2,a[p]);
        pushf(p*2+1,a[p]);
        a[p]=1;
    }
    if (b[p]!=1)
    {
        pushg(p*2,b[p]);
        pushg(p*2+1,b[p]);
        b[p]=1;
    }
}

void modify(int p,int l,int r,int x,int y)
{
    pushdown(p);
    if ((l==x) && (r==y))
    {
        f[p]=(f[p]+(LL)k)%mod;
        pushf(p*2,2);
        pushf(p*2+1,2);
    }
    else
    {
        int mid=(l+r)/2;
        g[p]=(g[p]+(LL)k)%mod;
        if (y<=mid)
        {
            modify(p*2,l,mid,x,y);
            pushdown(p*2+1);
            f[p*2+1]=(f[p*2+1]+(LL)k+mod-g[p*2+1])%mod;
            g[p*2+1]=g[p*2+1]*2%mod;
            pushf((p*2+1)*2,2),pushf((p*2+1)*2+1,2);
            pushg((p*2+1)*2,2),pushg((p*2+1)*2+1,2);
            sum[p*2+1]=(sum[(p*2+1)*2]+sum[(p*2+1)*2+1]+f[p*2+1])%mod;
        }
        else
        {
        	if (x>mid)
        	{
        		modify(p*2+1,mid+1,r,x,y);
        		pushdown(p*2);
                f[p*2]=(f[p*2]+(LL)k+mod-g[p*2])%mod;
                g[p*2]=g[p*2]*2%mod;
                pushf((p*2)*2,2),pushf((p*2)*2+1,2);
                pushg((p*2)*2,2),pushg((p*2)*2+1,2);
                sum[p*2]=(sum[(p*2)*2]+sum[(p*2)*2+1]+f[p*2])%mod;
        	}
        	else
        	{
        		modify(p*2,l,mid,x,mid);
        		modify(p*2+1,mid+1,r,mid+1,y);
        	}
        }
            
    }
    sum[p]=(sum[p*2]+sum[p*2+1]+f[p])%mod;
}

int main()
{
    scanf("%d%d",&n,&m);
    build(1,1,n);
    k=1;	
    for (int i=1;i<=m;i++)
    {
        scanf("%d",&op);
        if (op==1)
        {
            scanf("%d%d",&x,&y);
            modify(1,1,n,x,y);
            k=(LL)k*2%mod;
        }
        else printf("%lld\n",sum[1]);
    }
} 

猜你喜欢

转载自blog.csdn.net/liangzihao1/article/details/89325583