本文旨在通过两道巧妙运用位运算的题,认识位运算的魅力
题目一
题意:
给定两个序列(A,B),求(A,B)的最长公共子序列
(|A|,|B|le 10^5)
时限:(5s)
目前求任意两序列的最长公共子序列,是没有复杂度低于(O(|A|cdot |B|))的算法的
回顾经典的(O(|A|cdot |B|))
显然有如下性质:
令(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))