• 巧妙运用位运算


    本文旨在通过两道巧妙运用位运算的题,认识位运算的魅力

    题目一

    题意:
    给定两个序列(A,B),求(A,B)的最长公共子序列
    (|A|,|B|le 10^5)
    时限:(5s)

    目前求任意两序列的最长公共子序列,是没有复杂度低于(O(|A|cdot |B|))的算法的

    回顾经典的(O(|A|cdot |B|))

    [f_{i,j}=max(f_{i-1,j},f_{i,j-1},f_{i-1,j-1}+[A_i+B_j]) ]

    显然有如下性质:

    [egin{aligned} &f_{i,j-1}le f_{i,j}\ &f_{i-1,j-1}le f_{i,j}\ &f_{i,j}-f_{i,j-1}le 1\ &f_{i,j}-f_{i-1,j}le 1 end{aligned}]

    (M_{i,j}=f_{i,j}-f_{i,j-1})
    考虑转移
    我们将( ext{M}_{i-1})划分为若干段,每段以一个(1)结尾:

    [000...01][000...01][000...0]
     ^^                        ^
     12                       |B|
    

    考虑(A_i)出现在(B)中的位置集合,令其为( ext{Ab})
    ( ext{X=M}_{i-1}| ext{Ab})
    对于( ext{M}_{i-1})的每一段,保留( ext{X})最前面的一个(1),则为( ext{M}_i)

    例如

    $M_{i-1}$=[000...01][000...01][000...0]
         $Ab$=[110...00][000...00][001...0]
        $M_i$=[100...00][000...01][001...0]
    

    我们已知了转移方式,考虑如何通过位运算得到

    对于某一段如:X=[0011001]
    考虑前导0变成1,第一个1变成0:[1101001]
    异或上X,得到[1110000]
    在按位与上X,得到[0010000]
    

    我们将( ext{X})翻转,第一步操作变成

    X=[1001100]
    [1001101]
    

    这个意义是(-1),这是一段的操作,对所有段如何快速进行呢
    发现我们将( ext{M}_{i-1})整体翻转后
    再左移,给最右边填上(1),效果为:

    $M_{i-1}$=[000...0][100...0][100...0]
               ^                      ^^
              |B|                     21
              [000...1][000...1][000...1]
    这恰好使得每一段都变成1
    

    那么直接减即可,具体细节见代码
    将矩阵的行压成(K)块,即可实现(O(|A|cdot K))的时间复杂度
    以下代码在(n,m=10^5),不开任何优化的情况下,跑进1s

    #include<bits/stdc++.h>
    typedef int LL;
    typedef unsigned long long ull;
    const LL maxn=1e5+9;
    LL Read(){
    	LL x(0),f(1); char c=getchar();
    	while(c<'0' || c>'9'){
    		if(c=='-') f=-1; c=getchar();
    	}
    	while(c>='0' && c<='9'){
    		x=(x<<3ll)+(x<<1ll)+c-'0'; c=getchar();
    	}return x*f;
    }
    LL n,m,ans;
    LL a[maxn],b[maxn];
    ull dp[maxn/63],pos[maxn][maxn/63];
    int main(){
    	n=Read(); m=Read();
    	for(LL i=0;i<n;++i){
    		a[i]=Read();
    	}
    	for(LL i=0;i<m;++i){
    		b[i]=Read();
    	}
    	for(LL i=0;i<m;++i){
    		pos[b[i]][i/63]|=1ull<<i%63;
    	}
    	LL up=(m-1)/63;
    	for(LL i=0;i<n;++i){
    		LL x(a[i]);
    		ull pre(1ull<<63);
    		for(LL j=0;j<=up;++j){
    			ull a(dp[j]),c(a|pos[x][j]|1ull<<63);
    			ull tmp(((c-(a<<1ull|1))^c)&c);
    			if(pre<(1ull<<63)){
    				tmp-=tmp&(-tmp);
    			}
    			pre=tmp;
    			dp[j]=tmp&((1ull<<63)-1);
    		}
    		if(pre<(1ull<<63)) ++ans;
    	}
    	printf("%d
    ",ans);
    }
    

    题目二

    题意
    给定(n)个点,第(i)个点为((i,p_i))(序列({p_i})({1,2,cdots,n})的某个排列)
    每个点还有一个颜色(c_i)
    (q)次询问,每次给定(l,r,d,u),求集合({i|iin[l,r],p_iin[d,u]})的颜色个数
    (n,qle 10^5)

    (A_{c,i})为第(i)次询问是否包含颜色(c),考虑得到其

    显然可以将(iin[l,r],p_iin[d,u])可以拆开
    (X_{i,j})为第(j)次询问是否满足(l_jle ile r_j),我们对于((l_j,j)(r_j+1,j))进行扫描线,能很轻松的得到
    (Y_{i,j})为第(j)次询问是否满足(d_jle ile u_j),同理可得
    那么((X_iAnd Y_j)_j)即为点(i)是否被包含在第(j)次询问中
    现在可以很简单得到(A)

    矩阵(A)显然为01矩阵,需要将各行按列相加,得到答案
    对于每列,这里我们如此维护其答案:

    (cnt_i)为第(i)目前的答案
    (cnt_i=sumlimits_{k=0}^{infty} 2^kcdot C_{k,i})(其中(C)为01矩阵)
    假设这里(cnt_ile N),那么改写成
    (cnt_i=sumlimits_{k=0}^{logN} 2^kcdot C_{k,i})
    (logN)为矩阵的行数,称为(h)

    那么我们分治,对于颜色(in[l,mid]),令其矩阵为(C_0),对于颜色(in(mid,r]),令其矩阵为(C_1)
    考虑合并(C_0,C_1)(C'=C_0+C_1),假设(C_0,C_1)(h_0,h_1)均为(l),为了方便,则矩阵(C)(h=l+1)
    合并的过程,相当于是二进制高精,考虑进位,很容易实现

    分析其时间复杂度
    令颜色种类为(N),令(K)为将矩阵(C)的行压成的块数
    (T(N)=2T(N/2)+KlogN)(K)为常数,提取出来
    (T(N)=2T(N/2)+logN),运用主定理得到(T(N)=N)
    时间复杂度为(O(Kcdot N))

  • 相关阅读:
    世纪末的星期
    马虎的算式
    蜜蜂飞舞
    Torry 的困惑
    级数调和
    数列
    最大最小公倍数
    蚂蚁感冒
    12.integer to Roman
    13.Roman to Integer
  • 原文地址:https://www.cnblogs.com/Grice/p/14291379.html
Copyright © 2020-2023  润新知