斯坦纳树
dp的意义代码中写的很清楚,唯一要注意的是,有一个卡常的地方,显然对于n个点m条边取k个点的斯坦纳树,我们的dp有意义开到dp[1<<k][n]
吗? 这里是不必的,我们只需要开到dp[1<<(k-1)][n]
即可,很容易想到dp[(1<<k)-1][any]
中对于所有0<any<=k
都是答案,然而却不一定能想到dp[(1<<(k-1))-1][k]
也是答案,借此我们可以提升50%的时空性能
1 |
|
dp的意义代码中写的很清楚,唯一要注意的是,有一个卡常的地方,显然对于n个点m条边取k个点的斯坦纳树,我们的dp有意义开到dp[1<<k][n]
吗? 这里是不必的,我们只需要开到dp[1<<(k-1)][n]
即可,很容易想到dp[(1<<k)-1][any]
中对于所有0<any<=k
都是答案,然而却不一定能想到dp[(1<<(k-1))-1][k]
也是答案,借此我们可以提升50%的时空性能
1 | #include <bits/stdc++.h> |
用最少条数的路径覆盖树,这是一个树dp问题
1 | void dfs(int u,int father){ |
给你一个集合s 让你把这个集合划分为两个集合 在集合A中若x存在则a-x也存在 在集合B中若x存在则b-x也存在 # 数据范围 |s|<1e5 a<1e9 b<1e9 # 注意 集合中不包含相同的数
使用并查集维护, 容易证明若x和a-x存在,则他们必定在同一个集合中 x和b-x同理
1 | #include<bits/stdc++.h> |
多项式在计算机中一般以数组方式储存,假设有一个多项式\(f(x)\),并且\(x^i\)前面的系数为\(a_i\),那么显然我们恰好可以用一个数组\(a\)来描述这个多项式, 多项式的系数\(a_i\)恰好存在数组\(a\)的第\(i\)项\(a[i]\)
在一般的多项式中,如果模数允许,乘法采取ntt来做,因为速度较快,多项式乘法是整个多项式体系的基石,他的常数如果太大,将影响到后面的所 有的多项式操作
我们需要设置多项式最大长度,模数,原根,多项式最大长度的log,以及关键点reduce函数,传入一个-mod到mod之间的数x,返回x%mod,他比取模更快,然后是快速幂,在往后是复数i在模意义下的值,2i在模意义下的逆元,以及后面的逆元数组和他的初始化函数
1 | const int maxn=100005<<2,mod = 998244353,g=3;// const 能明显加速代码 |
蝴蝶变化必须预处理出来,否则太慢,最前面的是蝴蝶变换r[i][j]
数组, 之后是ntt单位根wn数组和他的逆元数组。接着是ntt的数组的预处理函数以及ntt函数本体
1 | int allr[maxn<<1],*r[30],ntt_wn[30],ntt_revwn[30]; // 特别详细,没啥其他可说的 |
为了后期代码可观性良好,以及少写一些奇怪的代码,poly_cpy(int*a,int n,int*b,int exn)
提供了多项式拷贝等操作,即将一个多项式拷贝到另外一个多项式,必须保证exn>=n ,即先将a[0...n-1]拷贝到b[0...n-1] 然后把b数组多余的部分清零。这里如果ab为同一个数组,就不必进行拷贝了。
1 | void poly_cpy(int*a,int n,int *b,int exn){// |
为了防止常数问题,这里依旧采取最简单的方式,不让数组发生过多的拷贝,我们只实现a*=b
这一个步骤,不实现c=a*b
这种,很套路的乘法,先变为点指表示法 然后变为系数表示法即可
1 | void poly_mul(int*a,int na,int*b,int nb){ |
若两个多项式\(f\)和\(g\),求出\(f*g\)然后让系数对mod取模,多项式忽略高于\(x^k\)次的项后等于1,则\(f\)和\(g\)互逆
这个地方很难以理解,因为有两个取模,所以会让很多初学者感到困惑,只要注意两个模是不同的,一个是系数对mod取模,另一个是多项式整体对\(x^k\)取模,即可
整个算法采取牛顿迭代法来完成,很容易被数学归纳法证明,\(f(x)*g(x)===1(x^k,mod)\),当k等于1的时候,非常好得出结果,显然那时候g(x)至少需要 1项,即常数项,大于常数项的部分无效的,这个很容易证明,这一项等于\(f(x)\)的常数项的逆元。然后我们来根据\(f(x)*g(x)===1(x^k,mod)\)推出\(f(x)*g(x)===1(x^2k,mod)\) 的结果,以下是推导过程,显然\(g(x)=g(x)*(2-f(x)*g(x))\),只要自己注意一下项数的变化即可,然后我们倍增即可得出答案时间复杂度\(T(n)=T(n/2)+nlg(n)\) 根据主定理\(T(n)=O(nlgn)\)img
1 | void poly_inv(int*a,int n,int*b){ // // %(x^n) b(2-ab) |
除法会有剩余,所以有两个返回值。
对于一个n-1次多项式f(x) 定义F(x)=x^nf(1/x) 则有以下推导img 余数直接ntt暴力即可
1 | void poly_div(int*a,int na,int*b,int nb,int*c,int*d){ |
img 于是求导、求逆、乘、积分即可完成
1 | void poly_der(int*a,int n,int*b){ // 微分求导 |
大佬已将讲的很清楚了,用泰勒展开求的img
1 | void poly_exp(int*a,int n,int*b){//a[exn(n+n-1)] // 保证f(0)=0 b(1-ln(b)+f), |
递归边界改为二次剩余即可img
1 | void poly_sqrt(int*a,int n,int*b){//a[exn(n+n-1)] //保证a[0]=1, (b+a/b)/2 比上面那个好一些 |
先取对数,然后乘,然后取exp
1 | void poly_pow(int *a,int n,int k,int *b){//a[exn(n+n-1)] // a^k not quick pow but quicker |
1 | void poly_sin(int*a,int n,int*b){//a[exn(n+n-1)]// 保证a[0]=0 -> c[0]=0 -> 可以exp |
。。。。。。没啥可说的,存粹的套路
1 | #include<bits/stdc++.h> |
1 | // a^x === b x=lg(a,b) |
对于一个离散函数\(f(x), x\in Z^+\) , 如果\(\forall \gcd(a,b)=1\)都有\(f(a\cdot b)=f(a)\cdot f(b)\), 则称函数\(f(x)\)为积性函数。
对于一个离散函数\(f(x), x\in Z^+\) , 如果\(\forall a,b\in Z^+\)都有\(f(a\cdot b)=f(a)\cdot f(b)\), 则称函数\(f(x)\)为完全积性函数。
我们根据积性函数的定义,其实只要我们计算出了所有素数的幂的函数值,则任何其他\(f(x)\)都可以快速获取。
对此笔者准备了一个模版,我们只需要修改其中的56到63行即可
\(\phi(n)\)就是小于等于n,且与n互质的数的个数。
1 | //O(lgn)适用于少求 |
1 | //求单个值 |
1 | #include<bits/stdc++.h> |
很早就想写这个了,觉得好厉害,结果分析停留在理论上,其实时间复杂度贼高
1 | #include<bits/stdc++.h> |
输出了斐波拉契700位,数据还行,就是太慢了。
1 | struct Tarjan:Graph{//强连通分量缩点 |
1 | struct Graph{ |
边双联通
1 | struct Graph{ |
1 | struct Graph{ |
程序跑起来都是跟地址相关的,可以试想,如果多个程序一起跑,难道不相互影响吗?又或者说 为什么我们写代码,从不去关心我们的程序在哪里运行?大家都用一个Memory,为什么没有相互破坏?好神奇!!
为了让多个程序在同一台计算机上更好的运行,而不发生相互破坏性影响,虚拟内存的概念被提了出来,从此多个程序都有了独立的地址空间。
虚拟内存,是与物理内存相对应的,可以设想,我们程序a运行起来,写地址Mx1,程序b运行起来 也写Mx1,结果是,他们没有相互影响,为什么?因为这个地址Mx1是虚拟内存,是假的,程序a的mx1 和程序b的mx1不是同一个地址,程序a写Mx1,结果写到哪去了呢?我们不知道,这件事是操作系统 完成的,他把程序a的Mx1映射到了一个地方,又把程序b的Mx1映射到了另一个地方,这两个程序就能 独立的跑起来了。
解决上述问题的方法很简单,是有一个叫做页表的东西来完成的。
为了更好的解决这个问题,我们将内存分页,就成了多个page,假设一个page有16KB,那么page里面的 地址就有16KB=2^14B,个地址,我们需要用14bits来表示这个地址,所以,基于这种page模式 地址的低14位就是page offset,页偏移量,其他的高位叫做page number,在虚拟内存里面 叫做Virutal page number,在物理内存里面叫做Physical page number ,注意物理内存和虚拟内存 的page offset是一样的。于是我们一个page一个page的映射,一次映射就是一整页。页内部保持地址 一致,地址映射只是page number变化。
我们之前说过,操作系统进行了一次映射,这个映射是怎么完成的呢?于是叶表出来了,有一张表 记录了所有虚拟内存地址页号(vpn)到物理内存地址页号的(ppn)的所有信息,显然为了高效性 这里我们使用数组来完成,在c++里面如果有一个数组a[4]={1,2,0,3};那么就有a[0]=1,a[1]=2,a[2]=0, a[3]=3;ok 映射完成了!!O(1)映射,高性能。但是还是有一个问题。这个数组可能回究极大大大大。 设想一个64GB的物理内存,以及1024GB的虚拟内存,页大小16KB,那么会导致36位的 physical address 40位的virtual address 以及14位的page offset,然后我们发现physical page number 达到了22位,virtual page number 达到了26位,我们来算一下这个数组有多大,26位的元素,2^26*22?bit,大概是150MB左右 这么大,缓存也放不下呀,只能放内存里面,可是这这东西读取频率是究极高的,放内存里面读写太慢了。
于是我们思考能不能把一部分经常要用到放到Cache里面?能啊!!为什么不能?为了让叶表用的更加得劲, 我们还专门给你弄一个Cache,重新取名位TLB。哈哈。TLB和普通Cache一样,page offset和 TLB index TLB tag和Cache的一摸一样,计算方法一摸一样,此处不再多言。
这也是一样的,只是多一个Access Control (一般 2 bits),也就多两位来着。Cache懂了这里很简单。