bitset 优化求解 LCS
题意:求两个字符串的最长公共子序列,串长、字符集 \(\le 7\times10^4\)。
分析
我们知道,求 LCS 的经典方法是 DP,状态已经是 \(O(n^2)\) 级别,而且无法省掉状态。但是,我们可以发现,对所有 \(i,j\) 都有 \(f_{i,j+1}-f_{i,j}\le 1\),由此可以发现 \(f_i\) 的差分是 01 序列,考虑 bitset 优化,分析一下转移式子 \(f_{i,j}=\max(f_{i-1,j},f_{i,j-1},f_{i-1,j-1}+[s_i=t_j])\),(从下面开始 \(f\) 代表着 DP 数组的差分)现在在 \(j\) 处出现了 \(i,j\) 之间的匹配,那么从 \(j\) 往后扫,只要扫不到 1,那么 \(f_{i,j}\) 最优一定取在 \(f_{i,j-1}\),对于扫到的第一个 1 也是如此,但该 1 往后便不再如此。即使这里面出现了更多能匹配的位置,\(f_{i-1,j-1}+1\) 的取值也只是和 \(f_{i,j-1}\) 相等,所以没有影响。
总结一下,我们要做的操作是:将其划分为若干段 00...01 序列,对每一段序列,找到其中出现的最早的能匹配的位置,将该位置设为 1,然后将最后一个 1 设为 0。对于最后面的一段全 0 序列,也如此做,但没有将最后一个 1 设为 0 的环节。
考虑这玩意怎么用 bitset 优化。常规的那些操作显然没啥效果,设 \(g_i\) 表示字符 \(i\) 的取值下标,注意到如果我们将 \(f_{i-1}\) 与 \(g_{s_i}\) 或起来得到 \(h\),那么对于每一段划分出来的序列,相当于取序列的 lowbit,而取 lowbit 有一个做法是 \((x\oplus (x-1))\& x\),套用这个做法,我们想要对每一段序列都减 1。发现 \(f_{i-1}\) 有一个性质是每一段的最后一位都为 1,那么把这个 1 通过右移移到下一个序列中,第一个序列手动在开头创造一个 1,然后让 \(h\) 减掉这个东西就能顺利套用了。
省流版:\(h=f_{i-1}|g_{s_i},f_i=(h-(f_{i-1}<<1|\{1\}))\oplus h\&h\)
时间复杂度 \(O(\frac{n^2}{\omega}\)),由于有减法操作,所以要手写 bitset。
int n,m,a[maxn],b[maxn];
template<int MAXN>struct Bitset{
u64 arr[(MAXN>>6)+1];
void set(int x){
arr[x>>6]|=1ull<<(x&63);
}
int count(){
int ans=0;
rep(i,0,MAXN>>6)ans+=popc(arr[i]);
return ans;
}
int get(int x){
return (arr[x>>6]>>(x&63))&1;
}
friend Bitset operator&(Bitset x,Bitset y){
rep(i,0,MAXN>>6)x.arr[i]&=y.arr[i];
return x;
}
friend Bitset operator|(Bitset x,Bitset y){
rep(i,0,MAXN>>6)x.arr[i]|=y.arr[i];
return x;
}
friend Bitset operator^(Bitset x,Bitset y){
rep(i,0,MAXN>>6)x.arr[i]^=y.arr[i];
return x;
}
void operator<<=(int x){
u64 lst=0;
rep(i,0,MAXN>>6){
u64 nw=arr[i]>>(64-x);
arr[i]=arr[i]<<x|lst,lst=nw;
}
}
friend Bitset operator-(Bitset x,Bitset y){
u64 jw=0;
rep(i,0,MAXN>>6){
u64 _jw=x.arr[i]<y.arr[i]+jw?1:0;
x.arr[i]-=y.arr[i]+jw,jw=_jw;
}
return x;
}
};
Bitset<maxn>f,g[maxn],h;
inline void solve_the_problem(){
n=rd(),m=rd();
rep(i,1,n)a[i]=rd();
rep(i,1,m)b[i]=rd(),g[b[i]].set(i);
rep(i,1,n){
h=f|g[a[i]],f<<=1,f.set(1);
f=(h^(h-f))&h;
}
write(f.count());
}

浙公网安备 33010602011771号