ARC084F XorShift【多项式】【位运算】
ARC084F XorShift
Description
有 \(n\) 个二进制数 \(a_i\),你可以将场上的任意两个二进制数异或起来得到一个新的数,也可以将某个二进制数乘 \(2\) 得到新的二进制数。请问你能最多能得到多少个 \(\le Lim\) 的数。 \(n\le 6,a_i,Lim\le 2^{4000}\),可加强至 \(\le 2^{20000}\)。
Solution
这已经不是第一次遇到二进制数的异或以及左移这样的问题了,也并不是第一次遇到将异或转化为 \(\mathbb{F}_2[x]\) 域下的多项式加法(上次出现是 这里的 恋歌,但我却依然没有及时想到这种思路,希望下次能够记住这一点。
可以将题目种的一个二进制数转化为 \(\mathbb{F}_2[x]\) 域下的多项式 \(f(x)=\sum c_ix^i\) 其中 \(c_i\) 为该二进制数从低到高第 \(i\) 位,那么异或就是多项式加法,乘 \(2\) 就是让 \(f(x)\cdot x\)。
既然操作只有加法与乘法,那么设 \(D(x)\) 为所有二进制数的最大公因式,显然无论如何操作,最终得到的数都得是 \(D(x)\) 的倍数。不仅如此,我们可以进一步说明,所有 \(D(x)\) 的倍数都可以被表示出来。
如果我们能够通过操作得到 \(D(x)\),那么可以容易的得到所有 \(D(x)\) 的倍数。考虑如何得到 \(D(x)\)。首先求出两个多项式的 \(gcd\),再与第三个多项式求 \(gcd\),如此反复即可得到 \(D(x)\)。
求两个多项式的 \(gcd\) 时,考虑经典的辗转相减法。注意异或即可被看作多项式加法,也可被看作多项式减法,因此直接实现辗转相减法,即可得到 \(D(x)\)。同时,实现代码时也可以通过辗转相减法得到 \(D(x)\) (当然是选择辗转相除法,多项式的取模是可以通过 \(\mathcal O(\dfrac{d^2}{\omega})\) 完成的)。
现在问题转化为了求出所有 \(<Lim\) 的 \(D(x)\) 倍数的个数,有一个结论:如果一个多项式除最低 \(deg(D)-1\) 位的数均已确定,那么符合这样条件的多项式中只有唯一一个 \(D(x)\) 的倍数。证明也是容易的,直接用该多项式对 \(D(x)\) 取模,就会得到最终 \(deg(D)-1\) 位的数字。
因此考虑枚举该 \(D(x)\) 的倍数是在第几位处开始 \(<Lim\) 的(前面都与 \(Lim\) 相等)。不妨设为第 \(l\) 位,那么还有 \(m=deg(Lim)-l-deg(D)+1\) 项未被确定,若 \(m>deg(D)-1\) ,那么还有 \(m-deg(D)+1\) 位可随意选择,共 \(2^{m-deg(D)+1}\) 位;否则,后 \(deg(D)-1\) 位也已确定,只需检查这一个数是否 \(\le Lim\) 即可。
最终我们只需要实现 \(n\) 次求 \(gcd\),一次多项式取模,总复杂度为 \(\mathcal O(\dfrac{nd^2\log(d)}{\omega})\)。
Code
#include<bits/stdc++.h>
using namespace std;
const int mod=73946381,N=20001;
char s[N];int pw[N],n;
struct poly{
int d;
bitset<N> v;
poly(){d=0;v.reset();}
inline bool operator [](int x){return v.test(x);}
inline void set(int x){v.set(x);}
inline void shrink(){while(d>0&&!v[d-1])d--;}
inline poly operator %(const poly &g)const{
poly ret=*this;
for(int i=d-1;i>=g.d-1;--i)
if(ret[i]) ret.v^=g.v<<(i-g.d+1);
ret.d=g.d-1;ret.shrink();
return ret;
}
inline void read(){
scanf("%s",s+1);int len=strlen(s+1);d=len;
for(int i=0,j=len;j;i++,j--) if(s[j]=='1') v.set(i);
}
inline void print(){
printf("deg is%d\n",d);
for(int i=0;i<d;++i) printf("%d ",v.test(i));puts("");
}
}f,g,a[6];
inline poly gcd(poly f,poly g){
int n=f.d,m=g.d;
if(n<m) swap(n,m),swap(f,g);
for(;g.d;swap(f,g)) f=f%g;
return f;
}
inline int add(int x,int y){return (x+y>=mod)?x+y-mod:x+y;}
inline void inc(int &x,int y){x=(x+y>=mod)?x+y-mod:x+y;}
int main(){
scanf("%d",&n);
f.read();
for(int i=0;i<n;++i){
a[i].read();
if(i==0) g=a[i];
else g=gcd(g,a[i]);
}
int x=f.d,y=g.d,ans=0;
pw[0]=1;
for(int i=1;i<max(x,y);++i) pw[i]=add(pw[i-1],pw[i-1]);
for(int i=x-1;i>=y-1;--i) if(f[i]) inc(ans,pw[i-y+1]);
poly tmp=f;
for(int i=0;i<y-1;++i) tmp.v[i]=0;
poly h=tmp%g;
for(int j=y-2;j>=0;--j){
if(h[j]<f[j]) break;
else if(h[j]>f[j]){ans--;break;}
}
ans++;
printf("%d\n",ans%mod);
return 0;
}