首页 > 其他分享 >zroi3054 教育题:正交补空间的引入和应用

zroi3054 教育题:正交补空间的引入和应用

时间:2024-11-01 19:20:16浏览次数:1  
标签:dim int cap 正交 空间 perp 引入 zroi3054

题意相信大家都看过了。注意最后要求的其实是这两个东西:\(\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

相关文章

  • 给网站添加春节灯笼效果:引入即用,附源码!
    记得之前在别的网站上看到这个喜庆的春节灯笼效果,觉得非常吸引人。虽然网上有一些现成的API可以直接实现,比如这个春节灯笼API,但使用后我发现两个问题:一是手机端访问时灯笼没有自适应,二是灯笼上的“春节快乐”四个字不能自定义。为了解决这些问题,我找到了这篇文章,并“借鉴”了其......
  • 【日常记录-Java】应用引入Slf4J
    1.简介    SLF4J(SimpleLoggingFacadeforJava)是Java的一个简单日志门面,为Java日志访问提供了一套标准、规范的API框架。而具体日志的实现则可以根据这套接口去实现具体的日志框架,以便将来需要更换日志框架时,只替换实现框架即可。常见的具体实现有JUL、log4j、......
  • SBOM SaaS平台新功能上线,引入漏洞预警机制!
    随着数字化浪潮的推进,软件已成为我们生活中不可或缺的一部分。然而,随着软件复杂度的不断提升,其安全性问题也日益凸显。我们会通过体检来检查身体是否存在健康问题,软件同样需要一份“体检报告”。SBOM(软件物料清单)详细记录了软件产品所依赖的所有组件、库、框架等。这一清......
  • YOLOv8改进 - 注意力篇 - 引入GAM注意力机制
    #YOLO##目标检测##计算机视觉#一、本文介绍作为入门性篇章,这里介绍了GAM注意力在YOLOv8中的使用。包含GAM原理分析,GAM的代码、GAM的使用方法、以及添加以后的yaml文件及运行记录。二、GAM原理分析GAM官方论文地址:文章GAM官方代码地址:​GAM注意力机制:GAM采用了顺序的通......
  • QT creator中cmake管理项目,如何引入外部库(引入Eigen库为例)
    在Eigen的官网下载压缩包[点我进入]解压到当前项目的根目录(当然你也可以自己选择目录)在当前项目的CMakeLists.txt任意位置加入这句话include_directories(${CMAKE_SOURCE_DIR}/eigen)这时候就是测试是否引入成功,在main.cpp中加入#include<Eigen/Dense>,鼠标悬停如果出现路......
  • Ubuntu虚拟机&conda虚拟环境运行和打包引入SimNIBS软件包的python项目文件
    项目背景:项目是python代码写的,其中有一个模块SimNIBS不能通过pip安装,需要自己下载软件包,在Ubuntu虚拟机的虚拟环境中运行和打包。下面是整个流程和遇到的一些问题,写下来做个记录。(默认此时SimNIBS已经安装好了,还没安装好的话,参见文章Ubuntu虚拟机安装医学影像软件包SimNIBS及报......
  • HCIP 路由引入
    一、实验拓扑二、实验需求及解法本实验模拟OSPF与IS-IS互联的网络环境,完成以下需求:1.配置所有设备的IP地址。R1:interfaceGigabitEthernet0/0/1ipaddress13.1.1.1255.255.255.0interfaceSerial1/0/0ipaddress12.1.1.1255.255.255.0R2:interfaceGiga......
  • C++各版本引入的新特性
    作者:momo链接:https://www.zhihu.com/question/355400393/answer/3245544440来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。以下是C++各个版本的标准库特性:C++98:引入了以下新的库特性:RTTI(运行时类型信息),包括dynamic_cast和typeid类型转换......
  • 向量正交
    过原点的两向量\(u\)与向量\(v\)垂直相当于点\(a\)到点\(b\)的距离与点\(a\)到点\(-b\)的距离相等,也即它们的距离的平方相等。计算点\(a\)到点\(-b\)的距离:\(\begin{align}[distance(a,-b)]^2&=\lVertu-(-v)\rVert^2=\lVertu+v\rVert^2\\&=(u+v)\cdot(u+v)\\&=u\cdot(u+......
  • django传统项目引入bootstrap
    1.使用bootstrapv3:下载bootstrap的css,bootstrap的js,jquery引入<%@pagecontentType="text/html;charset=UTF-8"language="java"%><html><head><title>Title</title><linkrel="stylesheet"hre......