黑科技:bitset 优化 LCS

感觉挺有参考意义的,单独拎出来发一个。


Loj6564 最长公共子序列

a,b LCS 长度。

n,m7×104,1s,1GB。

O(n2) 还能往下卡吗?

可以的,O(n2w)

考虑 dpi,j 的转移,有 0dpi,j+1dpi,j1,即差分数组是 01 串,给差分压位吧。

  • 我们现在要匹配 a,b 两个串。
  • fi,j 表示 a 匹配到前 i 位,b 匹配到前 j 位的答案,差分 bitset 为 fi
  • 预处理出每个字符在 a 中出现的位置组成的 bitset gi
  • 考虑求 LCS 的过程。相当于是我们要尽量匹配前面的字符。
  • 放到 fi 里面也就是,对于 fi 的每段连续的 0001 ,在 gbi 的对应区间中找到最靠前的那一个 1,然后把 fi 的那个位置变成 1,并把这段区间原本最后的 1 变成 0,即往前匹配一位。如果 gbi 里找不到这一段就不变。特别地,最后的 0000 也算,如果找得到的话同样把 fi 那一位变 1,只是没有 10 的操作。
  • 这玩意用位运算表示是记 X=fi1orgbifi=(X((fi1<<1)|1))xorXandX
  • 大概解释下意义:
  • 取 lowbit 有这么一种写法:lowbit(x)=(x1) xor x and x
  • 因为分了段,不一样的就只是怎么把对 x 整个 1 变成对 x 的每一段都单独 1 了。
  • 这是好解决的,因为每一段在 fi1 中都以 1 结尾,右移一位变成每一段开头,再加上最前面的 1 然后减去它就可以了。
  • 再异或一下与一下就可以除掉原本里面 f 就有的值了。

最后 fn 1 的个数即为答案。

然后如你所见,因为带个二进制减法,bitset 要手写……

struct bts {
	using ull=unsigned long long;
	int n;
	ull a[N/64+7];
	bts() { memset(a,0,sizeof(a)); }
	void ini(int _n) { memset(a,0,sizeof(a)); n=_n/64+1; }
	void set(int p) { a[p>>6]|=(ull)1<<(p&63); }
	int cnt(int ret=0) {
		for (int i=0;i<n;i++) ret+=__builtin_popcountll(a[i]);
		return ret;
	}
	void sft() {
		ull flg=0,tmp;
		for (int i=0;i<n;i++)
			tmp=flg,flg=a[i]>>63&1,a[i]=(a[i]^(flg<<63))<<1|tmp;
	}
	friend bts operator & (bts x,bts y) {
		bts ret=x;
		for (int i=0;i<x.n;i++) ret.a[i]=x.a[i]&y.a[i];
		return ret;
	}
	friend bts operator ^ (bts x,bts y) {
		bts ret=x;
		for (int i=0;i<x.n;i++) ret.a[i]=x.a[i]^y.a[i];
		return ret;
	}
	friend bts operator | (bts x,bts y) {
		bts ret=x;
		for (int i=0;i<x.n;i++) ret.a[i]=x.a[i]|y.a[i];
		return ret;
	}
	friend bts operator - (bts x,bts y) {
		bts ret=x; ull lst=0;
		for (int i=0;i<x.n;i++)
			ret.a[i]=x.a[i]-y.a[i]-lst,lst=x.a[i]<y.a[i]+lst;
		return ret;
	}
} f,g[N],tmp;
int mtc(int n,int m,int *a,int *b) {
	f.ini(n),tmp.ini(n);
	for (int i=0;i<N;i++) g[i].ini(n);
	for (int i=1;i<=n;i++) g[a[i]].set(i);
	for (int i=1;i<=m;i++) {
		tmp=f|g[b[i]];
		f.sft(),f.set(0);
		f=((tmp-f)^tmp)&tmp;
	}
	return f.cnt();
}

Gym103652F Square Subsequences

给定一个字符串。求字符串中最长的满足条件的子序列:长度为偶数,且前半段和后半段相同。输出具体结果

1n3000,2s,256MB。

枚举分割点切成两个串再套上面的 LCS 即可。

然后这里会出现一个巨坑:

博主一开始写的时候直接把出来的 f 中每一个 1 的位置当成匹配到的位置,然后直接把每一个 1 上的字符输了出来……

但是根据转移式,每一个 1 不一定来自同一个 f 前面的值,还有可能来自已经死去了的 fi1……博主因为这一点调了三个小时。

由于这题匹配的串的特殊性,确定了分割点之后再跑一遍朴素 DP 确定答案即可。

理论上你很难在 bitset 里把匹配过程记下来……所以如果要求方案的话还是老老实实写 O(n2) DP 罢。如果有会的戳一下博主。

posted @   CarroT1212  阅读(263)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示