题意相信大家都看过了。注意最后要求的其实是这两个东西:\(\sum [a_i\neq a_{i+1}]\) 最小值,以及在前面这个最小的情况下的填数方案数。如果无法填数,输出 \(0\)。
考虑一个暴力 dp:设 \(f1_i\) 和 \(f2_i\) 表示只考虑 \(a_1\sim a_i\),原问题的最小值 \(f1\) 以及在此时情况下的方案数 \(f2\)。
容易写出如下决策:记录 \(S_i\) 为 \(a_i\) 不限制 \([l,r]\) 的取值集合。则 \(a_{[L,R]}\) 均能取为相同的数,当且仅当 \(S_{L}\cap S_{L+1}\cap \dots \cap S_{R} \cap [l,r]\neq \emptyset\)。此时我们就可以得到 \(L-1\rightarrow R\) 的转移。于是我们就有了一个看起来是 \(O(n^2poly(m))\) 的做法。
但是你会发现直接验证并不好办,于是想方设法求出 \(\bigcap\limits_{L\leq i\leq R}S_i\) 对应的空间(熟悉线性基的同学们都知道,像 \(a_i\) 这种超高位数二进制数,一定要把它们当成 \(\bmod 2\) 意义下的 \(m\) 维向量)。也就是,加法是按位异或,乘法是按位与,然后我们能得到它的一组基。对于要求的每个 \(S_i\),我们直接通过传统的贪心求线性基方法维护即可,复杂度是 \(O(\dfrac{nm^3}{w})\)。问题出就出在了求交上。
首先观察测试点 \(7\sim 8\) 的特殊性质。容易发现,\(a_1=a_2=0\) 一定是最优解,所以只需要统计 \(S_1\cap S_2\) 的集合大小即可。
众所周知,两个线性空间 \(S_1\) 和 \(S_2\) 的交 \(S_1\cap S_2\) 以及它们的和 \(S_1+S_2\) 都同样是线性空间。而且还有一个不神奇的性质:\(\dim S_1+\dim S_2=\dim (S1\cap S2)+\dim(S1+S2)\)。两组空间的和对应的线性基极其好算,所以我们就在 \(O(\dfrac{nm^3}{w})\) 的时间复杂度内求出了交集的维数,于是这档就会了。
但是刚才那个结论只能求出交集的维数,还没法拓展到多个空间的交。我们现在要去得到的是,交空间的一组基,这个朴素做法是 \(O(\dfrac{m^3}{w})\) 的,或许可以参考刚才结论的证明,后面不会再利用这个结论。下面,我们考虑一个复杂度仍然不变,但是可以拓展到任意维的做法。这时,我们就要引入正交补空间了:
当然要先有内积与正交的定义(给没学过内积空间相关结论的同学们安利一波):
\[\langle u,v \rangle:=v^{T}u \]\[u\perp v\iff \langle u,v \rangle=0 \]\[u\perp V\iff \forall v\in V,u\perp v \]对一个线性空间 \(U\) 的子空间 \(V\),定义 \(V^{\perp}=\{v|v\in U\land v\perp V\}\)。则有如下定理:
对至少本题中的空间 \(U\)(当然,换成其它 \(F_p\) 应该也是对的),\(\dim V+\dim V^{\perp}=\dim U\)。
这个证明并不是很容易,我们直接考虑一个构造性证明(也就是基推基):
一个贪心造法。求出 \(V\) 的线性基,把基中每个向量的关键位置对应的子矩阵消成单位阵(一般我们就是上三角就行了,这个要全消掉)。然后每个非关键位置都要对应一个正交补的基。正常的线性基(我将其称作"原基")的关键位置在最高位,我们让正交补中基的关键位置在最低位,刚好反过来。然后,其它位置的 \(1\),采用如下方法:
原基中关键位置是 \(x\) 的向量,若它在第 \(y\) 个分量为 \(1\),则我们把正交基中关键位置是 \(y\) 的第 \(x\) 分量赋为 \(1\) 即可。举个例子:\(\operatorname{span}(10101,01000)\) 以此法的正交基为 \(10100,00010,10001\)。
这样,对任何一个原基和任何一个正交基,它们的交集大小要么是 \(0\),要么是 \(2\),因此它们正交。于是,我们有了一个 \(O(m^2)\) 构造正交补空间的基的算法。
接下来,怎么去维护空间交呢?有一个我不会证的结论 \((A\cap B)^{\perp}=A^{\perp}+B^{\perp}\)。其实有一个 FWT 意义的证法(冷知识:FWT 可以证明很多不显然的结论!!以后再讲)。正交补空间还拥有不少有趣的结论,Sooke 大佬有一篇很好读的介绍推荐大家看看。
回到原题。所以只要先把 \(S_i^{\perp}\) 求出来,维护区间和,判正交补是否和 \([l,r]\) 有交再算出大小即可。可是后面交集大小怎么算呢?首先,做个差分,比如只统计 \(<l\) 的数的数量。接下来,朴素算法是先把它再正交补回去,去贪心;不过这样太麻烦了,复杂度或者常数可能会爆,我们直接转而从正交补的那组基上面考虑:
如何判断一个向量 \(v\) 是否合法?其实,我们只要把这些基按关键位置从大到小,到每个关键位置时,把这个基向量和 \(v\) 做内积,判断是否正交即可。因此,实际上决定这个是否 ok 的,只是所有关键位置处的取值。因此,可以仿照类似最大选俩数 xor 那题(做法是 01trie 从上往下做,只会走到一个子树),这题也类似,从大到小填每位,至多有一个需要递归下去的,甚至可能中途 break,这个大家可能要仔细理清楚怎么算的,不过应该不难。
最后,就剩下转移关键位置了。我们可能还要再关注Ivan and Burgers这题的一个 trick:线性基是可以"可持久化"的。具体就是每个向量记录一个时间戳,每次还是从大往小扫,遇到还没有的就直接填上,注意标记时间;遇到已经有过的,看谁的时刻更晚,晚的占据这一位的值,早的去异或后继续往下扫。
这题也差不多,每一位作为关键位置记录一个时刻,排个序。注意,每相邻一段对应的可行性以及方案数是相同的,不过 \(dp\) 值可能很不同。所以,大概要维护一个指针,表示和自己 \(f1\) 相同最远的位置。然后只用前缀和优化即可转移!
时间复杂度 \(O(\dfrac{nm^3}{w}+nm^2)\),时限 \(5s\) 还是很够的。因为不定长,需要手写 bitset。此外,一个小小的卡常细节是求 count 的时候只需要它的奇偶性,所以只要把它划成的四个部分异或起来,用表就可以了。下面展示代码:
#include<bits/stdc++.h>
#define mod 998244353
using namespace std;
#define ull unsigned long long
int n,m,mm;
char s[5005];
int bp[1<<16];
inline int builtin(ull x){
return bp[x>>48]+bp[x>>32&65535]+bp[x>>16&65535]+bp[x&65535];
}
struct biset{
ull b[72];
inline void set(int x){
b[x>>6]|=1ull<<(x&63);
}
inline void reset(){
memset(b,0,sizeof(b));
}
inline void reset(int x){
b[x>>6]^=1ull<<(x&63);
}
void operator^=(biset a){
for(int i=0;i<mm;++i)b[i]^=a.b[i];
}
bool operator<=(const biset &other)const{
for(int i=mm-1;i>=0;--i)if(b[i]!=other.b[i]){
return b[i]<=other.b[i];
}
return 1;
}
bool operator<(const biset &other)const{
for(int i=mm-1;i>=0;--i)if(b[i]!=other.b[i]){
return b[i]<other.b[i];
}
return 0;
}
bool operator[](int x){
return b[x>>6]>>(x&63)&1;
}
int count(){
int ans=0;
for(int i=0;i<mm;++i)ans+=builtin(b[i]);
return ans;
}
};
biset zr;
biset operator&(biset a,biset b){
biset c=zr;
for(int i=0;i<mm;++i)c.b[i]=a.b[i]&b.b[i];
return c;
}
biset b[5005],o[5005];
bool vist[5005];
int t[5005],da[100005];
int f1[100005],f2[100005],lt[100005],ss[100005];
const long long MOD=1ll*998244353*1000000009;
long long em[5005];
long long getans(int ti,biset b){
long long ans=0;
int h=0;
for(int i=0;i<m;++i)h+=(t[i]<ti);
for(int i=m-1;i>=0;--i)if(t[i]>=ti){
if(b[i]){
if((o[i]&b).count()%2){
ans=(ans+em[h])%mod;
break;
}
}else{
if((o[i]&b).count()%2)break;
}
}else{
--h;
if(b[i]){
ans=(ans+em[h])%mod;
}
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);mm=m/64+1;
for(int i=0;i<(1<<16);++i)bp[i]=__builtin_popcount(i);
biset L=zr,R=zr;
scanf("%s",s);
for(int i=0;i<m;++i)if(s[m-1-i]^'0')L.set(i);
scanf("%s",s);
for(int i=0;i<m;++i)if(s[m-1-i]^'0')R.set(i);
int oo=1;
for(int i=0;i<m;++i)if(R[i]==0){
oo=0;R.set(i);
for(int j=0;j<i;++j)R.reset(j);
break;
}
em[0]=1;
for(int i=1;i<=m;++i)em[i]=2*em[i-1]%MOD;
memset(f1,63,sizeof(f1));
f1[0]=0,f2[0]=ss[0]=1;
int rr=0;
vector<int>v1;
for(int i=1;i<=n;++i){
for(int j=0;j<m;++j)vist[j]=0;
for(int j=1;j<=m;++j){
scanf("%s",s);
biset a=zr;
for(int k=0;k<m;++k)if(s[m-1-k]^'0')a.set(k);
for(int k=m-1;k>=0;--k)if(a[k]){
if(!vist[k]){
vist[k]=1;b[k]=a;
break;
}
if(vist[k])a^=b[k];
}
}
for(int k=m-1;k>=0;--k)if(vist[k])
for(int j=k-1;j>=0;--j)if(vist[j]&&b[k][j])b[k]^=b[j];
for(int j=0;j<m;++j)if(!vist[j]){
biset z=zr;
z.set(j);
for(int k=j+1;k<m;++k)if(vist[k]&&b[k][j]){
z.set(k);
}
int x=i;
for(int k=0;k<=m-1;++k)if(z[k]){
if(!t[k]){
o[k]=z;t[k]=x;
break;
}
if(x>t[k])swap(z,o[k]),swap(x,t[k]);
z^=o[k];
}
}
set<int,greater<int>>v;
v.emplace(i);lt[i]=0;
for(int k=0;k<m;++k)if(t[k]){
v.emplace(t[k]);lt[t[k]]=0;
}
int ti=i+1,fl=0,ls=i+1;
for(auto z:v){
long long bs=1;
for(int k=0;k<m;++k)if(t[k]<z)bs=2ll*bs%MOD;
long long ans=((oo?bs:getans(z,R))-getans(z,L)+MOD)%MOD;
ti=z+1;da[z]=ans%mod;
lt[ls]=z;ls=z;
if(!ans){
fl=1;break;
}
}
if(!fl)ti=1;
int j=ti-1;
if(j<i){
f1[i]=f1[j]+1;
while(f1[rr]<=f1[j])++rr;
for(auto z:v)if(z>j){
int L=lt[z]+1,R=min(rr,z);
if(L>R)continue;
f2[i]=(f2[i]+1ll*(ss[R-1]-(L==1?0:ss[L-2])+mod)*da[z])%mod;
}
}
ss[i]=(ss[i-1]+f2[i])%mod;
}
printf("%d\n",f2[n]);
return 0;
}
标签:dim,int,cap,正交,空间,perp,引入,zroi3054
From: https://www.cnblogs.com/maihe/p/18519772