• 「Gym103409H」Popcount Words


    题目

    点这里看题目。

    分析

    首先自然是研究一下 \(w()\) 有没有什么比较好的性质。

    这个其实猜都猜得到,\(w()\) 显然应当存在一定的倍增结构。具体地来说,我们考察一种特殊情况:

    定义 \(W_{n}=w(0,2^n-1),\overline{W_n}=w(2^n,2^{n+1}-1)\),则我们不难得到:

    • \(W_0=\mathtt{0},W_1=\mathtt{1}\)
    • \(W_n=W_{n-1}+\overline{W_{n-1}},\overline{W_n}=\overline{W_{n-1}}+W_{n-1},n\in \mathbb N_+\)

    当然,由此我们还不难得出一个简单的性质:

    \[w(k2^n,(k+1)2^n-1)= \begin{cases} W_n,\operatorname{popcount}(k)\bmod 2=0\\ \overline{W_n},\operatorname{popcount}(k)\bmod 2=1 \end{cases},k,n\in \mathbb N \]

    理解起来很容易,从 \(k2^n\) 一路 +1 到 \((k+1)2^n-1\),真正在变化的只有低 \(n\) 位,因而 \(k\)\(\operatorname{popcount}\) 可以和低位的 \(\operatorname{popcount}\) 分开。

    这个性质有可能是在后面要用到的时候才想起来去找的,不过无伤大雅,反正很容易发现就是了。

    下面考察一下 \(S\) 的结构。既然有了 \(W\) 这样的基础元素,我们就可以直接通过二进制,将 \(w(l,r)\) 拆分成 \(O(\log r)\)\(W\)\(\overline W\) 拼接的结果。

    具体过程有点像是树状数组

    • 设正整数 \(x\) 在二进制下最低位为 \(\operatorname{lowbit}(x)\)

    • 先从 \(l\) 上升,每次加入 \(w(l,l+2^{\operatorname{lowbit}(l)}-1)\),再令 \(l=l+2^{\operatorname{lowbit}(l)}\)

      \(l+2^{\operatorname{lowbit}(l)}>r\) 时结束。

    • 再逐步逼近 \(r\),每次加入 \(w(l,l+2^{\operatorname{lowbit}(r-l)}-1)\),再令 \(l=l+2^{\operatorname{lowbit}(r-l)}\)

      \(l=r\) 时结束。

    需要注意的是,最终我们形成的是 \(w(l,r-1)\),因此需要对于右端点稍稍调整一下,无伤大雅。


    回到原问题。多模式串匹配的问题,首先反手建立一个 AC 自动机不多说。哈哈哈,学麻了,一开始居然没有想到 AC 自动机,还试图多次询问单独跑 KMP

    注意到,最终我们的问题,可以被转化为:求 AC 自动机上每个结点各自被经过了多少次。我们已经将 \(S\) 拆分为了 \(O(n\log r)\)\(W\)\(\overline W\),所以可以看作是将 \(W,\overline W\) 按顺序放到 AC 自动机上分段转移。

    注意到两点:

    • 这是一个典型的离线查询的问题。
    • \(W,\overline W\) 本身就含有倍增的结构。

    我们不由得想到了打标记,结合 \(W,\overline W\) 则可以确定是逆序下放倍增标记的方法。

    更准确地描述是:我们只需要确定起始点 \(u\) 和转移的序列 \(W_n\)\(\overline {W_n}\),即可确定标记具体打在了哪里。因此,可以考虑状态 \(g_{u,n,0/1}\),表示从 \(u\) 出发,转移了 \(W_n\) 或者 \(\overline {W_n}\),这样的情况\(S\) 中总共出现了多少次。

    记录辅助状态 \(f_{u,n,0/1}\) 表示从 \(u\) 出发,转移了 \(W_n\) 或者 \(\overline{W_n}\) 后的终点;\(h_{u,n,0/1}\) 表示仅考虑由 \(S\) 拆分出来的 \(W\)\(\overline W\) 序列,从 \(u\) 出发转移了 \(W_n\) 或者 \(\overline{W_n}\) 的情况出现了多少次。

    可以写出如下的转移:

    \[g_{u,n,k}=h_{u,n,k}+g_{u,n+1,k}+\sum_{f_{v,n,1-k}=u}g_{v,n+1,1-k} \]

    举个例子,当 \(k=0\) 时,简单来说就是:考虑直接出发;考虑从 \(W_{n+1}\) 拆出来的部分(前半段是 \(W_n\));考虑从 \(\overline{W_{n+1}}\) 拆出来的部分(后半段是 \(W_{n}\))。

    这个可以做到 \(O((\sum|p|)\log r)\) 的预处理和下放,总时间复杂度为 \(O((n+\sum|p|)\log r)\)

    小结:

    1. 对于 \([l,r]\) 的拆分的思想值得学习,而不应该被局限于此题。

      关键在于,\(w\) 和树状数组有一定的相似性:树状数组中,每个位置记录了长为 \(2^{\operatorname{lowbit}}\) 的后缀和;而在 \(w\) 中,移动 \(2^{\operatorname{lowbit}}\) 步的字符串可以化归为 \(W\) 或者 \(\overline W\)

    2. 这里的倍增标记法的联想和运用确实很巧妙。典型的离线查询问题中,标记确实是很好用的。

    3. 哈哈哈,下次看到多模式串匹配别再犯傻了。

    代码

    复杂度写得这么对,到我这儿怎么就开始卡常了?

    #include <cstdio>
         
    #define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
    #define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
    
    typedef long long LL;
    
    const int MAXN = 1e5 + 5, MAXV = 5e5 + 5, MAXLOG = 35;
    
    template<typename _T>
    void read( _T &x ) {
        x = 0; char s = getchar(); bool f = false;
        while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
        while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
        if( f ) x = -x;
    }
    
    template<typename _T>
    void write( _T x ) {
        if( x < 0 ) putchar( '-' ), x = -x;
        if( 9 < x ) write( x / 10 );
        putchar( x % 10 + '0' );
    }
    
    LL cnt[MAXV];
    
    LL g[MAXV][MAXLOG][2];
    int f[MAXV][MAXLOG][2];
    
    int q[MAXV];
    int ch[MAXV][2], fail[MAXV];
    int ntot = 0;
    
    int ed[MAXN];
    int segL[MAXN], segR[MAXN];
    
    char buf[MAXV];
    
    int N, Q;
    
    inline int Lowbit( const int &x ) {
        return x & ( - x );
    }
    
    inline int Insert( const char *str ) {
        int p = 0, x;
        for( int i = 0 ; str[i] ; i ++ ) {
            x = str[i] - '0';
            if( ! ch[p][x] ) ch[p][x] = ++ ntot;
            p = ch[p][x];
        }
        return p;
    }
    
    inline void Build() {
        int h = 1, t = 0;
        if( ch[0][0] ) fail[q[++ t] = ch[0][0]] = 0;
        if( ch[0][1] ) fail[q[++ t] = ch[0][1]] = 0;
        while( h <= t ) {
            int u = q[h ++];
            ( ch[u][0] ? fail[q[++ t] = ch[u][0]] : ch[u][0] ) = ch[fail[u]][0];
            ( ch[u][1] ? fail[q[++ t] = ch[u][1]] : ch[u][1] ) = ch[fail[u]][1];
        }
    }
    
    int main() {
        read( N ), read( Q );
        rep( i, 1, N ) read( segL[i] ), read( segR[i] );
        rep( i, 1, Q ) {
            scanf( "%s", buf );
            ed[i] = Insert( buf );
        }
        Build();
        rep( i, 0, ntot ) 
            f[i][0][0] = ch[i][0], 
        f[i][0][1] = ch[i][1];
        rep( j, 1, 29 ) rep( i, 0, ntot )
            f[i][j][0] = f[f[i][j - 1][0]][j - 1][1],
        f[i][j][1] = f[f[i][j - 1][1]][j - 1][0];
        int p = 0;
        rep( i, 1, N ) {
            int l = segL[i], r = segR[i] + 1;
            while( l + Lowbit( l ) <= r ) {
                g[p][__builtin_ctz( l )][__builtin_parity( l )] ++;
                p = f[p][__builtin_ctz( l )][__builtin_parity( l )];
                l += Lowbit( l );
            }
            while( l < r ) {
                g[p][31 - __builtin_clz( r - l )][__builtin_parity( l )] ++;
                p = f[p][31 - __builtin_clz( r - l )][__builtin_parity( l )];
                l += 1u << ( 31 - __builtin_clz( r - l ) );
            }
        }
        per( j, 28, 0 )
            rep( i, 0, ntot ) {
            g[i][j][0] += g[i][j + 1][0];
            g[f[i][j][0]][j][1] += g[i][j + 1][0];
            g[i][j][1] += g[i][j + 1][1];
            g[f[i][j][1]][j][0] += g[i][j + 1][1];
        }
        rep( i, 0, ntot )
            cnt[ch[i][0]] += g[i][0][0],
        cnt[ch[i][1]] += g[i][0][1];
        per( i, ntot, 1 ) {
            int u = q[i];
            cnt[fail[u]] += cnt[u];
        }
        rep( i, 1, Q )
            write( cnt[ed[i]] ), putchar( '\n' );
        return 0;
    }
    
  • 相关阅读:
    读Hadoop3.2源码,深入了解java调用HDFS的常用操作和HDFS原理
    AI学习笔记:人工智能与机器学习概述
    CDN百科 | 最近,你的APP崩了吗?
    CDN HTTPS安全加速基本概念、解决方案及优化实践
    CDN百科第六讲 | 怎样用CDN抵御攻击?看完这篇漫画你就懂了
    CDN百科第五讲 | CDN和游戏加速器有什么区别?
    CDN百科第四讲 | 如何优雅地在云上“摆摊”——做直播带货,你不得不关注的技术
    CDN百科第三讲 | 如果用了云服务器,还需要做CDN加速吗?
    阿里云杨敬宇:边缘计算行业通识与阿里云ENS的技术演进之路
    CDN百科 | 假如没有CDN,网络世界会变成什么样?
  • 原文地址:https://www.cnblogs.com/crashed/p/16273698.html
Copyright © 2020-2023  润新知