一些计数问题

Published: by Creative Commons Licence

  • Tags:

图计数

颜色统计

题目1:给定一颗大小为n的树,每个顶点都有一个颜色。之后给定m个请求。每个请求分为两类:

  • 修改某个顶点的颜色
  • 查询某条路径上颜色为x的顶点数

提供一道题目

修改颜色的请求可以认为是删除一个颜色并增加一个新的颜色,即分解为两个请求。我们可以将所有请求按照颜色分组,同组按照发生顺序先后执行。

这样我们只需要处理单种颜色的增加删除,这可以视作每个顶点有一个点权,我们可以修改单点点权,并查询路径点权。这个问题就比较烂大街了,LCT或者用DFS序+差分技术都可以解决。

时间复杂度为O((n+m)log2n)

题目2:给定一颗大小为n的树,每个顶点都有一个颜色。定义fr(u)表示以r为根的时候,u为根的子树中不同颜色数。之后处理q个请求,每个请求给定uv,要求输出fu(v)。其中1n,m106,且强制在线。

先假定根为1,我们可以用dsu on tree技术,快速统计出f1(1),,f1(n)

之后考虑计算fu(v),如果uv的子树外,那么显然fu(v)=f1(v)。否则假设uv的某个孩子x的子树中,这时候fu(v)=fx(v)。我们可以用欧拉序加二分快速确定x。这时候v子树中的颜色数,等价于f1(1)g(x),其中g(x)表示有多少不同颜色,仅出现在x为根的子树中。

要计算函数g,我们可以通过欧拉序+LCA的技术确定每个颜色所在顶点的最小dfn和最大dfn序,之后找到它们的最低公共祖先l,并将l以及所有它的祖先的g属性全部增加1

总的时间复杂度为O((n+q)log2n)

题目3:给定一颗大小为n的树,每个顶点都有一个颜色。定义fr(u)表示以r为根的时候,u为根的子树中不同颜色数。要求输出n个整数,第i个整数值为nj=1fi(j)。其中1n106

提供一道题目

考虑离线请求,在树上dfs的同时,维护当前的总和(即如果当前访问的顶点为u,则目前总和nj=1fu(j))。初始的时候总和已经计算好了。之后考虑从顶点u进入它的孩子v带来的影响。很显然对于iu,v,有fu(i)=fv(i)成立,只有fu(u)需要切换为fv(u),以及fu(v)切换为fv(v),其差量正好为fv(u)fu(v)

可以发现这个过程最多计算了O(n)个不同的fu(v),通过题目2的方式我们可以O(nlog2n)实现整个过程。

删除叶子方法

题目1:给定一颗大小为n的树,每次操作你可以选择一个叶子删除。现在问对于k=0,,n,执行k次操作的可能操作序列有多少个,答案对素数p取模。

提供一道题目

首先考虑给定根为1的情况,记录dp(i,j)表示i的子树中执行j次操作的方案数。那么可以发现我们可以O(n2)实现计数。

接下来考虑不给定根的情况,这个情况比较复杂。但是我们可以枚举最后一次删除的顶点,以他为根进行dp。这样的话时间复杂度为O(n3)

子树计数

题目1:给定一颗包含n个顶点的树,树根为顶点1,每个顶点i上有一个数字ai。现在回答q个查询。第i个请求给定顶点r,以及一个整数m,定义顶点r的值为fr(r)=ar,定义其下子树中的任意顶点u的值为fr(u)=fr(pu)+au,其中pu表示u的父亲节点,要求找出以r为根的子树中有多少个顶点的值不小于m。其中1n,q5×1051ai,m109

这道题需要用到差分技术。可以发现fr(i)=f1(i)f1(r)+ar。因此我们只需要维护f1即可。之后我们把请求挂到对应的顶点上去,之后在树上dfs。一个结点dfs后把以该结点为根的子树中的所有f1值丢到一颗稀疏权值线段树中去,这样就能O(nlog2n)实现合并和查询操作,总的时间复杂度为O(nlog2n)

异构树

题目1:给定n个顶点,问这n个顶点给定的无根树数目。结果模素数p

根据prufer编码与树唯一对应关系可知,只需要统计它们的prufer编码数目。prufer编码的长度为n,其中每个元素均可以为1n之间的任意值,因此答案为nn2。这个答案对于n=1的情况也是成立的。

时间复杂度为O(log2n)

题目2:给定n个顶点。问这n个顶点给定的有根树数目。结果模素数p

题目1给出了无根树的数目为nn2,由于每个无根树都可以视作n个不同的有根树,因此有根树的数目为nn2×n=nn1

时间复杂度为O(log2n)

森林

题目1:计算存在多少包含n个顶点的无向无环图,其中1n105,结果对素数取模。

提供一道题目

T(k)表示包含k个顶点的树的数目,通过prufer编码我们知道T(k)=kk2。令F(k)表示拥有k个顶点的森林数目。很显然有

F(k)=ki=1F(ki)T(i)(k1i1)=(k1)!ki=1F(ki)(ki)!T(i)(i1)!

可以发现这是一个卷积递推的一个DP公式,我们可以用分治+FFT将时间复杂度优化到O(n(log2n)2)

函数图

考虑任意函数f:SS,其中S=[1,n]。它的函数图如下:建立n个顶点,对于1in,从if(i)加入一条有向边。

简单来看,可以发现函数图具有如下性质:

  • 每个顶点的出度正好为1
  • 每个连通块正好有一个环。
  • 每个函数图和S×S上的函数唯一对应。

题目1:给定n,问存在多少个不同的大小为n函数图。将结果对某个素数p取模。

可以直接统计函数数目,答案为nn

时间复杂度为O(log2n)

题目2:给定n,要求存在多少大小为n的函数图,满足图连通(将有向边视作无向边后连通)。答案对某个素数p取模。

在图连通的前提下,图实际上是一颗树加上一条边。当然树是无向边,而函数图是有向边,但是可以发现我们将函数图中唯一的环缩点后,并将这个顶点作为根,将树边全部从孩子指向父亲,就可以唯一恢复成函数图。因此可以发现函数图的数量和这样的树加上额外的一条边得到的图数量是相同的。

这里我们可以简单的枚举环的长度。考虑环的长度为i,那么缩点后共有ni+1个顶点,其对应的生成树的数目为nni1,注意这里之所以底数是n而不是ni+1,是因为我们如果缩点每次出现在prufer编码中,会使得它的度数增加1,我们需要考虑这条边具体连接到缩点前的哪个顶点,因此总的可能数是ni+i=n。同样如果prufer中一个顶点出现次数为x,则其度数为x+1,因此我们还需要额外乘上一个i。并且我们还需要考虑环中的元素和其排列,因此总数为:

ni=1(ni)×(i1)!×i×nni1

上面我们可以O(n)计算出结果。

题目3:给定n,对于k=1,2,,n,问存在多少不同的函数图,正好有k个连通块。将结果对某个素数p取模。

提供一道题目

我们可以利用题目2计算单个连通块的技术加上DP,这样可以得到一个O(n3)的算法。

但是我们实际上可以将算法优化到O(n2),方案如下。

我们先考虑只有一个连通块的情况,就是

ni=1(ni)×(i1)!×i×nni1

这里我们可以稍微修改一下流程,我们考虑在环中的元素数目为i,则其方案数为:

(ni)×i×nni1

之后我们将选中的i个元素拆分成k个非空环,这样就能得到结果。方案数可以通过无符号第一类斯特林数得到。因此对于给定的k,答案为:

ni=1(ni)×i×nni1×[ik]

对于特定的k,可以O(n)算出。总的时间复杂度为O(n2)

生成树

题目1:给定n个顶点m条边的无向(有向)图,计算对应的生成树数目。答案对某个素数取模。

矩阵树定理的基础应用。

时间复杂度为O(n3+m)

欧拉路径

题目1:给定n个顶点m条边的有向图,计算对应的欧拉环路数目。答案对某个素数取模。

BEST定理的应用。

时间复杂度为O(n3+m)

题目2:给定n个顶点,顶点ii+1(modn)有一条无向边i。给定a0,a1,,an1,要求计算存在多少条环路,满足经过边i正好ai次。结果对素数取模。

提供一道题目

考虑边i,我们记其正向经过次数为xi,逆向经过次数为yi。将每条边正向逆向出现次数相消后,图应该依旧满足欧拉环路的定理才有意义。此时每个顶点的入度出度相等,可以发现每条边i的正向或逆向经过次数均为某个数k

我们可以大力枚举这个数k,正向和逆向各需要枚举一次。之后边i剩余正向和逆向经过次数为(aik)/2。这时候图是有向图,我们可以用题目1的方式计算有向图的欧拉迹数目。每次计算的时间复杂度为O(k3)。总的时间复杂度为O(k3iai)

匹配计数

题目1:给定长度为n的两个序列a1,,an以及b1,,bn。如果aibj,则认为这两个数可以匹配。计算两个序列的最大匹配数目,结果对素数p取模。

我们可以预先对两个序列进行排序。现在考虑dp(i,j)表示处理完b1,,bi,记at为不大于bi的最大的数,a1,,at中有j个数尚未被匹配,此时的方案数。

上面的公式转移是O(1)的,因此时间复杂度为O(n2)

题目2:给定长度为n的两个序列a1,,an以及b1,,bn。如果aibj,则认为这两个数可以匹配。计算两个序列的极大匹配数目,结果对素数p取模。一个匹配是极大的,当且仅当所有未匹配的数之间都不能相互匹配,比如a=1,3b=2,4,那么(1,4)匹配后,剩下的两个数3,2无法相互匹配,因此(1,4)是极大匹配,但不是最大匹配。

提供一道题目

首先我们需要考虑a中最小的未被匹配的数,记其为at,则对于i<tai一定被匹配。考虑bk为最小的数,满足bkat,则对于ikbi一定被匹配。满足这些条件的匹配一定是极大的。

具体计算方法是,我们可以定义dp(i,j)表示b1,,bi已经全部处理完了,记ak为最大的数,满足akbi,其中a1,,ak中还有j个数需要被匹配。之后的转移和题目1类似。这里计算dp的时间复杂度为O(n2),总的时间复杂度为O(n3)

但是实际上我们可以把对t的选择一起放入到dp中,具体的做法是定义dp(i,j,k),其中i,j意义类似,但是k=1表示已经选择了atk=0表示at未选择。这样时间复杂度就可以优化到O(n2)

不相交路径计数

要解决不相交路径计数,需要用到一个神器:Lindström–Gessel–Viennot引理,不懂的先自行了解。

题目1:给定一个n×m网格图,其中单元格中或者为空白,或者为一个障碍物,所有合法路径都不允许经过存在障碍物的单元格。记P表示所有从左上角到右下角的所有合法路径的集合,考虑存在多少个二元组(p1,p2),满足p1,p2P,且两条路径仅经过两个公共网格(起点和终点),结果模素数p。其中2n,m1000

我们实际上要统计从起点A=((2,1),(1,2))到终点B=((n,m1),(n1,m))的不相交路径数,这时候仅可能存在的置换为π,因此我们通过Lindström–Gessel–Viennot引理可以直接计算出不相交路径数。

序列计数

相邻不同序列

题目1:有n个球,第i个球的颜色为ci。要求统计有多少不同的排列,满足排列中任意两个相邻的球的颜色不同,将结果模素数p后输出。其中n10000,且1cin

提供一道题目

记录Ci表示颜色为i的球的出现次数。首先我们可以预先认定同色球在最终序列中的出现顺序。之后记录集合Ai,j表示颜色为i的球,第j个出现的球和第j+1出现的球紧密相连的序列集合。则我们要求的是

|i1j<Ci¯Ai,j|=|S||i1j<CiAi,j|

接下来就是一个简单的容斥,可以展开为:

X<C[(1)X][i(Ci1Xi)][(nX)!i(CiXi)!]

这个公式可以化为多个生成函数的乘积。其中

Pi=Ci1j=0(1)j(Ci1j)1(Cij)!xj

记它们的乘积为Q=iqixi。则结果为iqi(ni)!

上面的算法的时间复杂度为O(n(log2n)2)。当然如果你用一般的DP也可以做到O(n2)

奇偶性计数

题目1:要求计算存在多少个长度为n序列,其中每个元素为1m,且数值i的出现次数ci满足ci=bi(mod2)。其中1m50001n109

我们要求的结果为:

c1++cm=nci=bi(mod2)n!c1!cm!

我们记

fn(x1,,xm)=c1++cm=nci=bi(mod2)n!c1!cm!xc11xcmm

那么我们要求的结果为f(1,,1)。记

g(x)=n0fn(x1,,xm)n!xn=n0c1++cm=nci=bi(mod2)(x1x)c1c1!(xmx)cmcm!=mi=1ci=bi(mod2)(xix)cici!=mi=1exp(xix)+(1)biexp(xix)2

x1=x2==xm=1代入得到:

g(x)=mi=1exp(x)+(1)biexp(x)2

上面的公式可以视作ex的多项式,即g(x)=mi=mdiexp(ix)。计算出所有系数的时间复杂度为O(m2)

之后我们计算的fn(1,,1)实际上是xnn!的系数。考虑到exp(x)=i=0xii!,因此我们的答案为:

mi=mdi×in

总的时间复杂度为O(m2+mlog2n)

题目2:提供一个R面筛子,其中有ai面写着数字i,其中1im。维护一个序列,初始为空,重复下面操作,直到序列长度达到n

  • 投掷筛子,将筛子上的数字追加到序列尾部。

检查序列中第i个元素出现的次数ci,是否满足ci=bi。如果满足,则称序列是好的。问序列是好的的概率。其中1mR5000,且1n109

这题和题目1实际上是相同的,只不过我们要求的是1Rnfn(a1,a2,,am)。时间复杂度为O(R2+Rlog2n)

题目3:提供一个R面筛子,其中有ai面写着数字i,其中1im。维护一个序列,初始为空,重复下面操作,直到序列长度达到n

  • 投掷筛子,将筛子上的数字追加到序列尾部。

检查序列中第i个元素出现的次数ci,是否满足ci=bi。如果满足,则称序列是好的。如果序列不是好的,则清空序列,并重新调用流程,直到得到好序列为止。记wi表示数字i的权重,定义序列的权重为mi=1ciwi,要求计算序列权重的数学期望。其中1mR5000,且1n109

提供一道题目

得到的好序列的方案数为fn(a1,,an)

hn(x1,,xm)=c1++cm=nci=bi(mod2)n!c1!cm!xc1w11ac11xcmwmmacmm

可以发现我们的好序列的所有方案总权为

(mi=1xihnxm)(1,,1)

我们用类似的方式得到:

p(x)=n0hn(x1,,xm)n!xn=ni=1exp(aixwiix)+(1)biexp(aixwiix)2

之后我们求mi=1xipnxm即可。这个东西可以通过动态规划计算,记dp(i,j,0/1)表示前i个多项式,exp(jx)的系数,第三个参数指定是否已经对一个变量进行偏微分了。这个部分可以O(R2)计算。

之后总权除上总方案数得到平均数即可。总的时间复杂度为O(R2+Rlog2m)

赋值方案

题目1:计算有多少个长度为n的序列x1,,xn,满足0xi<m,且ni=1xiS。其中1n,m,S106,结果对素数取模。

我们可以用容斥来计数,定义Ai表示xi>S这个条件,那么我们要求的就是|ni=1¯Ai|。通过容斥可以展开为

IN(1)|I|iIAi=IN(1)|I|p(Sm|I|,n)

其中p(i,j)表示存在多少种方案将i划分为j个带编号的未知数的和,其等价于(i+j1j1)

可以发现上面的公式中仅I的大小发挥作用,因此我们可以转而枚举I的大小。

0in(1)i(ni)p(Smi,n)

这样我们就得到了O(n+S+m)的做法。

题目2:给定序列a1,,an,计算有多少长度为n的序列b1,,bn,满足bi0ni=1aibi=S。其中1ai,S,且n,S,ni=1an105,结果对素数取模。

我们要求的是g=f1fnxS项的系数,其中fi(x)=j=0(xai)j=11xai

计算乘积得到g(x)=1ni=1(1xai)。我们只需要通过分治算法求出分母,之后对分母取逆即可。时间复杂度为O(mlog2mlog2n),这里m=ni=1ai

题目3:给定序列a1,,an,计算有多少长度为n的序列b1,,bn,满足bi0ni=1aibi=S。其中1ai,n10,且1S1018,结果对素数取模。

S较小的时候,容易想到用DP求解,其中dp(i,j)表示仅考虑x1,,xj,存在多少方案满足jt=1atxt=i。可以发现要计算dp(i,?),只需要知道dp(i10,?),,dp(i1,?)即可,并且它们的转移关系是线性的。其中总共涉及最多10n个状态,因此我们只需要维护一个长度为10n的向量v0,以及转移关系A。答案为vS=ASv0

这里可以采用矩阵快速幂进行计算,时间复杂度为O((10n)3log2m)

0和计数

题目1:给定n11,要求将它们组成一个长度为2n的序列,且其中恰好有k个非空连续子序列,和正好为0。要求统计组成的不同序列数目。其中1n30

提供一道题目

记得到的序列为a1,,a2n,而令si=ij=1aj。得到一个新的序列s0,s1,,s2n。那么我们实际上是要找到有多少i<j,满足si=sj。如果存在t个数的值均为x,那么它对结果的贡献为(t2)

https://img.atcoder.jp/arc117/cfa04e5167e82e883898fa042ea938ff.png

现在考虑dp(size,cc,pair)表示仅考虑长度为size的序列,其中有cc个连通块,共存在pair对下标拥有相同值。

我们考虑状态(size,cc,pair)的转移,下一层如果新增x个元素,那么很显然需要xcc+1,这时候所有连通块都合并为一块。之后考虑另外x(cc+1)个新的元素插入带来的变化,每次插入一个新元素,都会使得连通块增加1。因此新的转移状态为(size+x,x(cc+1)+1,pair+(x2)),而转移的权重为(x1cc2)(共cc+1个槽,即y1++ycc+1=xyi1)。

注意我们目前只算了0以及更上面的层次,而对于下层,(size,cc,pair)适配的状态为(2n+1size,cc1,kpair)

总的计算时间复杂度为O(n5),空间复杂度为O(n4)

置换群下去重计数

这类型的问题一般需要用到burnside和polya定理,请提前了解。

题目1:给一个六面的筛子,要求在每个筛子面上设置一个非负数,第i面上写的为xi,要求满足6i=1xi=S。两种方案相同,当且仅当可以通过旋转筛子得到相同的结果。要求计算方案数。其中0S1018,结果对素数取模。

提供一道题目

根据burnside定理可知,我们要计算的是

1|G|fG|{cf(c)=c}|

其中G为旋转操作带来的置换群,其中实际上只有24个元素。因此我们将问题转换为存在多少个c,在f作用下结果不变。这等价于计算iaibi=S,这个我们可以通过矩阵快速幂求解,总的时间复杂度为O(24×66log2m)

带比较计数

题目1:给定非负整数k,以及两个长度为n的序列a1,,anb1,,bn。其中a1,,an,b1,,bn1,,2n的一个置换。且要求满足bi<max。其中0\leq k < n\leq 10^6。统计可能的方案数,并对素数取模。

提供一道题目

由定义可以很容易得出2n\in a

我们考虑考虑所有排列等概率出现的情况下条件满足的概率,之后乘上(2n)!即是答案。

x_i表示b_i< \max_{1\leq j\leq \min(n,i+k)} a_j成立的集合,那么我们要求的是\mathrm{Pr}(x_1\cap x_2\cap \ldots\cap x_n)=\mathrm{Pr}(x_1)\mathrm{Pr}(x_2\mid x_1)\cdots \mathrm{Pr}(x_n\mid x_1\cap \ldots \cap x_{n-1})。注意这里我们不需要考虑2n\in a这个条件,因为当所有情况都满足的时候,这个条件是可以被推出的。

考虑\mathrm{Pr}(x_i\mid \bigcap_{j=1}^{i-1}x_j),其不成立当且仅当b_ib_1,\ldots,b_i,a_1,\ldots,a_{\min(n,i+k)}中的最大值,考虑到是随机排列,因此不成立的概率为\frac{1}{i+\min(n,i+k)},而成立的概率为1-\frac{1}{i+\min(n,i+k)}

因此我们可以得出答案为\prod_{i=1}^n(1-\frac{1}{i+\min(n,i+k)})=\prod_{i=1}^{n-k} \frac{2i+k-1}{2i+k}\prod_{i=n-k+1}^n \frac{i+n-1}{i+n}

我们可以O(n)计算结果。

不允许相邻计数

题目1:回答q个请求,每个请求给定nkd,要求统计有多少个长度为n的01序列,其中元素和为k,且任意连续的d个元素之和都不能超过1。其中1\leq q,n,k,d\leq 10^6

f(n,k,d)为答案。

可以把每个1理解为占用d个位置的块,之后我们把n延长d-1(允许我们将第i个元素设置为1),现在的问题变成了,有n+d-1-dk0块和k1块,有多少种组合方式,很显然答案为f(n,k,d)={n+d-1-dk+k\choose k}

预处理后,每个请求都可以O(1)回答。

题目2:回答q个请求,每个请求给定nkd,要求统计有多少个长度为n的01环(第一个元素和最后一个元素相邻),其中元素和为k,且任意连续的d个元素之和都不能超过1。其中1\leq q,n,k,d\leq 10^6

类似题目1,可以发现第1到第d个元素中最多有一个1,分两种情况处理:

  • 1到d中有一个1,这时候方案数为d\times f(n-(2d-1),k-1,d)
  • 1到d中没有1,这时候方案数为f(n-d,k,d)

因此可以发现答案为g(n,k,d)=d\times f(n-(2d-1),k-1,d)+f(n-d,k,d)的01环(第一个元素和最后一个元素相邻),其中元素和为k,且任意连续的d个元素之和都不能超过1

题目3:给定一个长度为n的序列a_1,\ldots,a_n,之后考虑所有可能的长度为n的01环b(第一个元素和最后一个元素相邻),其中元素和为k,且任意连续的d个元素之和都不能超过1。记b的权重为\sum_{i=1}^na_ib_i。要求计算所有满足条件的01环的权重之和。其中1\leq n,k,d\leq 10^6

我们记c_i表示第i个元素在所有满足条件的01环中为1的次数。很显然有c_1=c_2=\ldots=c_n,而所有方案的总和为k g(n,k,d)。因此c_i=k g(n,k,d)/n。答案为\sum_{i=1}^n c_ia_i

题目4:给定一个长度为n的序列a_1,\ldots,a_n,之后考虑所有可能的长度为n的01序列b,其中元素和为k,且任意连续的2个元素之和都不能超过1。记b的权重为\sum_{i=1}^na_ib_i。要求计算所有满足条件的01序列的权重之和。其中1\leq n,k\leq 10^6

提供一道题目

类似题目3,同样定义c_i

我们可以把问题转换成01环版本来求解。考虑所有满足01环的同时可以展开为一个01序列,而01序列的不一定能展开为01环,分两种情况考虑:

  • 能转换为01环的,共有g(n,k,2)种,我们将贡献c_1,\ldots, c_nkg(n,k,2)/n
  • 另外一种情况,这时候第1个元素和第n个元素都被设置为1。这时候对c_1c_n的贡献均为f(n-4,k-2,2)。而对于c_2,\ldots,c_{n-1}的贡献,我们可以发现这时候问题变成一个长度为n-4的01序列,其中元素和为k-2,且任意连续的2个元素之和都不能超过1。可以注意到问题的规模缩小了,我们可以递归解决问题。

每次递归流程的时间复杂度为O(1),递归的深度为n/2,因此时间复杂度为O(n)

题目4:给定一个长度为n的序列a_1,\ldots,a_n,之后考虑所有可能的长度为n的01序列b,其中元素和为k,且任意连续的d个元素之和都不能超过1。记b的权重为\sum_{i=1}^na_ib_i。要求计算所有满足条件的01序列的权重之和。其中1\leq n,k,d \leq 10^6

提供一道题目

不会。

包含特定子串的字符串数统计

题目1:提供大小为C的字符集,提供一个长度为n的串T,统计有多少长度为m的字符串S,使得S中含有T

这里讲多种做法。

一种比较简单的方是通过统计有多少不含T的串。简单的方法是用AC自动机,定义dp(i,j)表示构建长度为i的串,状态为j的方案数。这给出了O(Cnm)的时间复杂度。

可以发现上面的转移是线性的,因此我们实际上可以做到O(Cn^2+n\log_2m),或者O(Cn+n^3\log_2m),取决于你使用BM递推还是快速矩阵幂。

但是上面几种方法,在n=O(m)的情况下,都会达到O(n^2)的级别或更高。

其实还有O(m\log_2n)的做法。具体思路就是用kmp求出所有的border。之后考虑用容斥来找有多少字符串S包含T。具体的方案是令dp(i,k)表示TS的上一次出现的位置是iT的出现次数的为k。考虑用的是容斥,因此只需要知道出现次数的奇偶性即可,因此k只需要取值0,1

但是计算dp的时候,i不一定会转移到i+n或更后面的位置,因为T连续两次出现的位置可能有重叠部分。我们需要用border来处理这个问题。用kmp求出所有border。这样就可以给出时间复杂度为O(nm)

当然这个距离我们要的结果还有点远。由于border的关系会形成O(\log_2n)个等差数列,因此我们实际上可以仅考虑这O(\log_2n)个等差数列,每个状态的贡献都是某个区间,并且步长是固定的,通过一些前缀和技术可以快速计算转移。时间复杂度为O(m\log_2n)

带禁止的置换计数

题目1:给定n的一个置换a,计算存在多少n的置换p,要求\forall ip_i\neq a_i。结果模素数输出。

这个问题是容斥的经典应用。记A_i表示所有置换集合p,满足p_i\neq a_i。结果为:

\begin{aligned} &|\bigcap_{i=1}^nA_i|\\ =& |U|-|\bigcup_{i=1}^n\overline{A_i}|\\ =& \sum_{I\subseteq N}(-1)^{|I|} \bigcap_{i\in I}A_i\\ =& \sum_{I\subseteq N}(-1)^{|I|} (n-|I|)!\\ =& \sum_{i=0}^n (-1)^{i} (n-i)! {n\choose i}\\ =& n! \sum_{i=0}^n (-1)^{i} \frac{1}{i!} \end{aligned}

算法的时间复杂度为O(n)

题目2:给定n的一个置换ab,计算存在多少n的置换p,要求\forall ip_i\neq a_ip_i\neq b_i。结果模素数输出。

提供一道题目

我们依旧可以通过容斥来计算结果。我们可以DP计算违背的约束的数量,以及可行解数目。但是我们需要保证DP的过程中不会选择重复值,这个是关键,否则就不是一个有效的置换。下面我们讨论如何避免这个问题。

我们考虑建立n个顶点,之后对于i=1..n,加入边(p_i,q_i)。可以发现一个p的位置和一条边有关。同时可以发现每个顶点的度数均为2,因此得到的图是若干个简单环。

可以发现每个环我们可以独立统计,考虑一个长度为k的环,顺时针进行编号,我们可以记录dp(i,j,k,t)表示考虑前i个元素,j表示第一个元素是否被选择为逆时针元素,k表示第i个元素是否被选择为顺时针元素,总共有k个元素违背约束。算法的时间复杂度为O(k^2)

多个环的合并类似于背包合并。总的时间复杂度为O(n^2)

本质不同非空子串

题目1:给定一个序列a_1,\ldots,a_n,问有多少本质不同的子串。

后缀自动机或者后缀数组可以做到O(n)

题目2:给定一个序列a_1,\ldots,a_n,之后回答q个请求,每个请求给定l,r,要求计算a_l,a_{l+1},\ldots,a_r中有多少本质不同的子序列数目。

后缀自动机上维护线段树。如果不允许离线,就是用持久化线段树,否则使用差分技术。时间复杂度为O((n\log_2n+q)\log_2n),空间复杂度为O(n\log_2n\log_2n+q)O(n\log_2n+q)

本质不同子序列

题目1:给定一个序列a_1,\ldots,a_n。问有多少本质不同的非空子序列。结果对素数取模。

首先我们可以进行离散化。之后认为a_i\leq n。难点在于唯一性。

我们可以用贪心法保证序列的唯一性。具体就是如果我们目前的子序列尾部为第i个字符,且a_j不属于a_{i+1},\ldots,a_{j-1},那么子序列的下一个结尾可能为a_j。这样做的时间复杂度为O(n^2)

我们可以逆着做,具体就是从后向前DP,记x_i表示以第i个字符作为开始的本质不同子序列数目。我们可以从后向前计算x_i,同时维护y_j,以字符j开始的本质不同子序列数。计算x_i的时候有x_i=1+\sum y_{a_i},之后将y_{a_i}更新为x_i,这样就可以做到O(n)

题目2:给定一个序列a_1,\ldots,a_n。之后回答q个请求,每个请求给定l,r,问a_l,a_{l+1},\ldots,a_r有多少本质不同的非空子序列。结果对素数取模。

提供一道题目

第一种做法是每次重新计算,时间复杂度为O(nq)

下面讲一种O(r^2(n+q))的做法,r表示字符集大小。首先需要理解题目1中O(n)做法的转移,仔细观察会发现它每次转移实际上都是一个线性变换,并且是一个可逆的线性变换。这意味着我们可以预处理线性变换的后缀和A和后缀逆B。那么l,r之间的转移实际上等于A_lB^{r+1},于是乎我们可以O(r^3)回答每个请求,但是注意到如果我们按照A_l(B^{r+1}v)的顺序计算,则每个请求可以O(r^2)回答。

对于预处理后缀和和后缀逆,这需要O(r^2n)的空间复杂度。而对于时间复杂度,看上去是O(r^3n),但是可以发现每步操作对应的线性变换和逆变换,都是稀疏矩阵,其中只有O(r)个非空元素。因此我们可以把时间复杂度优化到O(r^2n)。总的时间复杂度为O(r^2(n+q))

集合计数

分组问题

题目1:将n个编号的球放入m个带编号的篮子里,有多少种放法。

每个球有m种放法,且不同的球的方法是独立的,因此结果为m^n

题目2:将n个未编号的球放入m个带编号的篮子里,有多少种放法。

x_i表示第i个篮子里的球数,此时有x_1+x_2+\ldots+x_m=n成立。这个可以用隔板法解决,答案是{n+m-1\choose m-1}

题目3:将n个带编号的球放入m个未编号的篮子里,有多少种放法。

可以将篮子先编上号,可以发现此时答案是m^n。之后我们发现每种真实的方案对应m!种编号后的方案,因此答案是\frac{m^n}{m!}

题目4:将n个带编号的球放入m个未编号的篮子里,且要求每个篮子不空,有多少种放法。

这实际上是第二类斯特林数,答案是\begin{Bmatrix}n\\m\end{Bmatrix}

题目5:将n个未编号的球放入m个未编号的篮子里,有多少种放法。

可以用动态规划求解。记dp(i,j,k)表示每个篮子最多放i个球,共j个篮子,放k个球的方案,这个dp可以O(n^2m)求解。

子集计数

题目1:给定包含n个元素的集合S,要求计算存在多少个S的子集族A,满足每个元素都出现在至少两个A的元素中。结果对素数p取模。

提供一道题目

X_i表示所有满足元素i出现在最多一个元素中的子集族组成的集合。那么我们实际上要统计|\bigcap_{i=1}^n \overline{X_i}|。我们可以用容斥统计这个数量:

\begin{aligned} |\bigcap_{i=1}^n \overline{X_i}|&=|S|-|\bigcup_{i=1}^nX_i|\\ &=\sum_{I\subseteq N}(-1)^{|I|}|\bigcap_{i\in I}X_i| \end{aligned}

其中

\begin{aligned} |\bigcap_{i\in I}X_i|&=\sum_{k=0}^{|I|}{|I|\choose k}\sum_{r=0}^{|I|}\begin{Bmatrix}k\\r\end{Bmatrix}2^{r(n-|I|)}\sum_{m=0}^{2^{n-|I|}}{2^{n-|I|}\choose m}\\ &=2^{2^{n-|I|}}\sum_{r=0}^{|I|}2^{r(n-|I|)}\sum_{k=0}^{|I|}{|I|\choose k}\begin{Bmatrix}k\\r\end{Bmatrix}\\ &=2^{2^{n-|I|}}\sum_{r=0}^{|I|}2^{r(n-|I|)}\big ((r+1)\begin{Bmatrix}|I|\\r+1\end{Bmatrix}+\begin{Bmatrix}|I|\\r\end{Bmatrix}\big) \end{aligned}

合并两个公式得到:

|\bigcap_{i=1}^n \overline{X_i}|=\sum_{i=0}^n(-1)^{i}{n\choose i}2^{2^{n-i}}\sum_{r=0}^{i}2^{r(n-i)}\big ((r+1)\begin{Bmatrix}i\\r+1\end{Bmatrix}+\begin{Bmatrix}i\\r\end{Bmatrix}\big)

上面的公式通过预处理,可以O(\sqrt{p}+n^2)计算得到。

题目2:考虑给定n个元素a_1,\ldots,a_n,记S=\sum_{i=1}^na_i。要求计算其中有多少个子序列(不要求连续)的元素和正好为0,1,2,\ldots,S。其中1\leq a_iS\leq 10^5。结果对素数M取模。

提供一道题目

考虑它的生成函数。

\begin{aligned} F(x)&=\sum_{k=0}^Sx^k\sum_{0\leq c_1,c_2,\ldots,c_n\leq 1\land \sum_{i=1}^nc_ia_i=k}1 \\ &=\sum_{k=0}^S\sum_{0\leq c_1,c_2,\ldots,c_n\leq 1\land \sum_{i=1}^nc_ia_i=k}x^{c_1a_1}x^{c_2a_2}\cdots x^{c_na_n}\\ &=\sum_{0\leq c_1,c_2,\ldots,c_n\leq 1}x^{c_1a_1}x^{c_2a_2}\cdots x^{c_na_n}\\ &=\prod_{i=1}^n x^{a_i}+1 \end{aligned}

之后分治+FFT即可,时间复杂度为O(S\log_2S\log_2n)

实际上我们可以把结果优化到O(n+S\log_2S)。具体的想法来自与A Simple Near-Linear Pseudopolynomial Time Randomized Algorithm for Subset Sum这篇论文。

我们可以先记G(x)=\ln F(x)。那么很显然有F(x)=\exp(G(x))。之后我们只需要能快速求出G(x)即可。

首先已知\ln(1+x)=\sum_{i=1}^{\infty}\frac{(-1)^{i-1}x^i}{i}

\begin{aligned} G(x)&=\ln \prod_{i=1}^n x^{a_i}+1\\ &=\sum_{i=1}^n \ln (x^{a_i}+1)\\ &=\sum_{i=1}^n \sum_{j=1}^{\infty} \frac{(-1)^{j-1}x^{j\cdot a_i}}{j} \end{aligned}

由于S是有限的,我们只需要考虑G(x)的前S+1项的即可。记c(i)表示ia中出现的次数。那么公式可以简化为:

\begin{aligned} \sum_{i=1}^S \sum_{j=1}^{\lfloor S/i \rfloor} \frac{c(i)\cdot (-1)^{j-1}x^{j\cdot a_i}}{j} \end{aligned}

上面的过程可以O(n+S\ln S)处理,之后的\exp(G(x))可以O(S\ln S)计算,总的时间复杂度为O(n+S\ln S)

题目3:给定n种不同颜色,第i种颜色共有a_i个球,记m=\sum_{i=0}^na_i。现在问对于q=1,\ldots,m,有多少种不同的大小为q的多重集合。结果对素数p取模。其中1\leq n\leq m\leq 3\times 10^5,且1\leq a_i

我们要求的是f_1,\ldots,f_n的乘积,其中f_i=\sum_{i=0}^{a_i}x^i。简单的可以推出f_i=\frac{x^{a_i+1}-1}{x-1}。我们可以用题目2中提到的方法将所有生成函数的分子和分母独立乘起来,之后取逆分母,做多项式卷积即可。时间复杂度为O((n+m)\log_2(n+m))

多重集合计数

题目1:考虑所有仅包含非负整数的多重集合,有多少大小为k的多重集合,它的和为n,记结果为f(n,k)。给定N,对于所有1\leq n\leq N1\leq k\leq N,输出f(n,k)。其中1\leq N\leq 5000

提供一道题目

我们可以用动态规划解决这个问题。用一般的动态规划的话时间复杂度会恶化到O(N^3)。这里我们可以换一个动态规划的思路。

问题等价于存在多少子序列x_1,\ldots,x_k,满足x_1\leq x_2\leq \ldots \leq x_k,且\sum_{i=1}^nx_i=n

上面提到的问题依旧不好解决。我们可以考虑x_i序列的差分形式d_1,\ldots,d_k,其中d_i=x_i-x_{i-1}(这里认为x_0=0)。可以发现x_i每增加1,整体和会增加k-i。所以这提供了一个dp的思路,令dp(i,j)表示仅考虑d_1,\ldots, d_i,其贡献和为j的方案数。但是这里会发现有一个我们不想要的变量n存在,这使得这个dp方案的时间复杂度同样为O(N^3)。一个非常简单的思路是,我们可以认为dp(i,j)表示仅考虑最后的i个差分变量,它们的贡献和为j的方案数,这种情况下可以发现n不见了,因为最后第i个差分变量每增加一,对结果的贡献始终是i

这样我们可以实现O(N^2)的一个算法。f(i,j)=dp(j,i)

网格计数

子矩形计数

题目1:给定一个大小为n\times m的01网格,要求计算有多少个k个位置形成的集合,满足每个位置中写的数字都是1,且记l为最左的位置的横坐标,r为最右的位置的横坐标,b为最下的位置的纵坐标,t为最上的位置的纵坐标,满足r-l<wt-b<h。其中1\leq w,h,n,m\leq 10^3

我们可以枚举lb,之后统计有多少k位置集合。我们只需要考虑以(l,b)为左下角的大小为w\times h的矩形中的数值和S,那么答案为{S \choose k}。但是这里存在重复统计,因此我们必须通过容斥计算,记S_1表示以(l,b)为左下角的大小为w\times h的矩形中的数值和,S_2表示以(l+1,b)为左下角的大小为w-1\times h的矩形中的数值和,S_3表示以(l,b+1)为左下角的大小为w\times h-1的矩形中的数值和,S_4表示以(l+1,b+1)为左下角的大小为w-1\times h-1的矩形中的数值和。那么答案为S_1-S_2-S_3+S_4

时间复杂度为O(4nm)

覆盖问题

题目1:给定一个大小为2\times n的网格,要求在网格上放一些矩形,使得矩形互不相交且网格被完全覆盖。求方案数,结果对素数取模。

提供一道题目

定义A(i)表示宽度为2\times i的网格覆盖方案数,且要求最右边两个网格被不同矩阵覆盖。定义B(i)表示宽度为2\times i的网格覆盖方案数,且要求最右边的两个网格是被同一个矩阵覆盖。

可以推出

\left\{ \begin{array}{l} B(i)=A(i-1)+B(i-1)\times 2\\ A(i)=B(i)+A(i-1)\times 2^2 \end{array} \right.

而答案为B(n)+A(n)

棋盘问题

题目1:给定一个n\times n的网格,要求在上面放n个棋子,同行或同列只能放一个棋子,问放法数目,结果模素数。

每一种放法实际上对应一种置换,因此答案为n!

时间复杂度为O(n)

题目2:给定一个n\times n的网格,要求在上面放n个棋子,同行或同列只能放一个棋子,且对角线上不允许放棋子,问放法数目,结果模素数。

可以用容斥来解决。答案为n!\sum_{i=0}^n(-1)^i\frac{1}{i!}

时间复杂度为O(n)

题目3:给定一个n\times n的网格,要求在上面放n个棋子,同行或同列只能放一个棋子,且部分网格上不允许放棋子,问放法数目,结果模2

考虑行列式的定义,可以发现行列式实际上就是统计有多少排列,之后乘上1-1,而在模2的情况下,-1=1,因此此时答案就是det(A)An\times n矩阵,允许放的位置为1,其它位置为0

时间复杂度为O(\frac{n^3}{32})

题目4:给定一个n\times n的网格,要求在同一条斜线上最多只能放一个棋子,要求正好放入k个棋子,问放法数目,结果模素数。

斜线上控制比较麻烦,将网格选择45^o得到一个棱形,这时候就是要求竖线和横线上不能放。

接下来可以用DP来统计,我们将行重新排列,较小的行排在前面优先放置。这样DP就不需要记录具体哪些列被放置了,而只需要记录放置了几列。

时间复杂度为O(n\min(n,k)),这里如果k>2n的时候是无解的。

题目5:给定一个n\times n的网格,每个网格允许放黑色棋子或者白色棋子,要求黑色棋子之间不能共行或共列,而白色棋子之间也不能共行或共列,且要求分别放n个黑色棋子和n个白色棋子,问有多少放法。结果模素数。

我们建立n个顶点,对于行i,黑色棋子出现的位置记作a,白色棋子出现的位置记作b,则我们加入一条有向边(a,b)。可以发现每个顶点的入度和出度均为1,且不存在自环。

因此问题实际上在问存在多少个置换p,其中p(i)\neq i,这个结果为n!\sum_{i=0}^n(-1)^i\frac{1}{i!}。之后把置换放入到网格中,我们可以对行做n!种重排,答案为(n!)^2\sum_{i=0}^n(-1)^i\frac{1}{i!}

时间复杂度为O(n)

题目6:给定一个n\times n的网格,每个网格允许放黑色棋子或者白色棋子,要求黑色棋子之间不能共行或共列,而白色棋子之间也不能共行或共列,且有些网格已经预先放好白色或黑色棋子了,要求放置一定数量的黑白其中,使得棋盘上n黑色棋子和n白色棋子的数目均为n,问有多少放法。结果模素数。

提供一道题目

a为已经同时放好黑白棋子的行数,b为仅放好黑色棋子的行数,c为仅放好白色棋子的行数。b_1b行中黑色棋子不存在同列白色棋子数,c_1c行中白色棋子不存在同列黑色棋子数。d为空列数。

我们需要对p(i)是否等于i进行容斥。答案为:

(n-a-b-c)!\sum_{x=0\rightarrow b_1}\sum_{y=0\rightarrow d}\sum_{z=0\rightarrow c_1}(-1)^{x+y+z}{b_1\choose x}{d\choose y}{c_1\choose z}\\ {n-a-c-y-x \choose b-x}(b-x)!\\ {n-a-b-y-z\choose c-z}(c-z)!\\ (n-a-b-c-y)!

由于b_1+d+c_1\leq n,因此b_1\cdot d\cdot c_1\leq (\frac{n}{3})^3,时间复杂度为O((\frac{n}{3})^3)

路径计数

题目1:给定一个n\times m的网格,其中你处于左下角(0,0),目的地是右上角(n-1,m-1)。允许每次向上或向右移动。其中存在k个点称为关键点(x_1,y_1),(x_2,y_2),\ldots,(x_k,y_k),保证起点和终点不是关键点。要求计算路径中正好经过0,1,2,\ldots,t个关键点的路径数。其中k\leq 1000n,m\leq 10^5t\leq 100

提供一道题目

dp(i,j)表示从起点到第i个关键点,总共正好经过了j个关键点的路径数。一般的转移计算方法是需要枚举上一个关键点的位置,但是计算二者的路径时,可能会途径其它的关键点,这一点很麻烦。为了解决这个问题,我们定义dp2(i,j)表示从起点到第i个关键点,至少经过了j个关键点的路径数。可以发现dp可以通过dp2差分得到。

现在考虑dp2(i,j)的计算方式。我们可以枚举路径上经过的正好第j-1个关键点p,之后从p转移到i。可以发现这样不会重复统计,且每条路径正好只被统计一次。这里比较特殊的就是dp2(i,0),它实际上等于{x_i+y_i\choose x_i}

数论

位运算

题目1:给定n个非负数a_1,\ldots,a_n,要求计算存在多少个非负整数x,满足x\leq \min(a_1,\ldots,a_n),使得a_1-x\oplus \ldots \oplus a_n-x=0。其中a_i\leq 10^{18}

提供一道题目

我们记录dp(i,j)表示仅考虑最低的i个二进制位,有多少方案存在,满足a中正好有j个数最低的i个二进制位小于x的最低的i个二进制位。答案很显然为dp(60,0)

dp(i,j)可能产生的操作仅两种,一种是设置第i+1位为0,还有一种是设置第i+1位为1。我们这里可以使用双指针技术来加速,因此时间复杂度为O(60n)

模运算

题目1:计算\sum_{i=0}^n[a+\lfloor \frac{i}{b} \rfloor = c+\lfloor \frac{i}{d} \rfloor],其中1\leq n\leq 10^9,且b,d>0

提供一道题目

不失一般性可以假设a\leq c。如果b>c,那么很显然答案一定是0。如果a=cb=c,那么答案很显然是n+1。如果a<cb=c,那么答案同样是0

f(i)=a+\lfloor \frac{i}{b} \rfloorg(i)=c+\lfloor \frac{i}{d} \rfloor。可以发现f的增长速度快于g,我们可以找到最小的x,满足f(x)=g(x)。可以发现此时一定有b|x,我们可以借此对x二分来找到这个位置。同理我们找到最小的y,满足f(y)=g(y)+1,以及最小的z满足f(z)=g(z)+2。很显然有x<y<z成立。

下面我们赖考虑如何计算上面的公式。f(i)=g(i)成立仅可能当x\leq i<z,且此时一定有且对于x\leq i<y,均有-1\leq f(i)-g(i)\leq 0,而对于y\leq i<z,有0\leq f(i)-g(i)\leq 1

要统计有多少位置f(i)=g(i),等价于统计有多少位置f(i)-g(i)=0。由于当x\leq i<y的时候,g(i)-f(i)仅可能取值10,每个1代表一个不匹配位置。因此我们可以得到匹配位置数为(y-x)-(\sum_{i=x}^{y-1}g(i)-\sum_{i=x}^{y-1}f(i)),而由于\sum_{i=x}^{y-1}g(i)\sum_{i=x}^{y-1}f(i)均可以通过类欧几里德函数进行快速计算,因此我们可以O(\log_2M)找到结果,其中M表示精度。对于y\leq i<z的时候同理。

总的时间复杂度为O(\log_2M)

题目2:给定n个互不相同的正整数a_1,\ldots,a_n。记f(k)=\sum_{1\leq i<k} a_i\pmod{a_k}+a_k\pmod{a_i}。要求输出f(1),\ldots,f(n)。其中1\leq a_i,n \leq 10^6

提供一道题目

首先我们要知道取模的定义是啥,x\pmod y=x-y\times \lfloor\frac{x}{y}\rfloor,要解决这个问题我们需要用到这个公式。

分两部分处理,第一部分是处理\sum_{1\leq i<k} a_i\pmod{a_k}部分。对于xa_k\leq a_i<(x+1)a_k,它对我们结果的贡献为a_i-xa_k。因此我们需要知道在每个区间[xa_k,(x+1)a_k),有多少个a_i出现,换言之我们要有能力查询区间和。同时我们在处理完a_k后,还需要把a_k的信息更新上去,换言之需要支持更新。我们可以直接用树状数组来维护这些信息。由于每个数都不同,而所有数的倍数是O(10^6\log_2 10^6)级别的。因此这部分的时间复杂度为O(10^6(\log_210^6)^2)

再考虑第二部分,\sum_{1\leq i<k} a_k\pmod{a_i}。继续用上面的公式可以发现对于xa_i\leq a_k<(x+1)a_i,它对我们结果的贡献为a_k-xa_i。我们可以同时维护一个数据结构,在处理完一个数a_k后,将它的所有倍数区间信息更新到这个结构上。而查询只需要单点查询即可。这也可以通过后缀数组做到。这部分的时间复杂度为O(10^6(\log_210^6)^2)

总的时间复杂度为O(10^6(\log_210^6)^2)

几何计数

矩形计数

题目1:提供n个不同的二维点p_1,p_2,\ldots,p_n,要求有多少个下标a<b<c<d,满足p_a,p_b,p_c,p_d作为四个点可以确定一个正方形,且正方形四边与坐标轴平行。其中1\leq n\leq 10^5

我们先将点分成两类,如果一个点,与它拥有相同横坐标的点超过\sqrt{n}个,那么记作二类点,否则为一类点。之后将一类点按照横坐标分组,二类点按照纵坐标分组。可以发现得到的分组的大小都不会超过\sqrt{n}

之后我们可以暴力枚举同一个分组中的两个点,另外两个点的位置仅两种选择,我们直接查询这两个点是否出现即可。这里可以用哈希表来存储点。

但是如果四个点都是同一类点,就会出现重复统计的情况,这里可以简单的在每次考虑完某个分组之后,就将分组中的点从哈希表中全部删除即可。

总的时间复杂度为O(n\sqrt{n}),当然你需要一个足够快的哈希表才行。事实上我们每次暴力枚举最多找到一个合法的正方形,而枚举的上限为O(n\sqrt{n}),因此可以证明这也是正方形数的一个上限。

题目2:提供n个不同的二维点p_1,p_2,\ldots,p_np_i=(x_i,y_i)。要求判断是否存在一组下标a<b<c<d,满足p_a,p_b,p_c,p_d作为四个点可以确定一个矩形,且矩形四边与坐标轴平行。其中1\leq n\leq 10^60\leq x_i,y_i\leq 3000,且所有点两两不同。

我们可以将所有点按照x坐标分组,之后暴力枚举两组来判断每组是否都存在相同的两个y坐标点。这个过程时间复杂度为O(3000^3)。我们可以用bitset优化这个过程,优化后的时间复杂度为(3000^3/32)

但是由于我们只需要判断是否存在至少一组下标,因此我们可以枚举每一列中的所有点对,考虑点对为(x_i,y_i)(x_j,y_j),我们将(y_i,y_j)状态记录下来。如果已经记录过相同的状态,我们就可以报告找到,否则标记这个状态出现过了。可以发现每个状态最多被标记一次,而总的状态数仅为O(3000^2),因此总的时间复杂度为O(3000^2+n)

题目3:考虑在二维坐标系以(0,0)(w,0)(w,h)(0,h)确定的矩形区域内,选择四个不同格点(横坐标和纵坐标均为整数),形成一个矩形的方案数。注意这里不要求矩形的边界和坐标轴平行。其中1\leq w,h\leq 10^3

提供一道题目

我的做法和标解不同,拥有更好的时间复杂度。

与坐标轴平行的非常好统计,任意选两行两列,取它们的交点即可。这部分的贡献为{w+1\choose 2}{h+1\choose 2}

下面说一下怎么统计与坐标轴不平行的矩形数。考虑任意一个不平行矩形,我们找到面积最小的包含它的平行的矩形,这时候矩形的左下角和右上角均为边长分别为a,b的三角形,而左上角和右下角均为边长分别为c,d的三角形。且满足\frac{a}{b}=\frac{c}{d},换言之ad=bc,且外部的平行的矩形的大小为(a+d)\times (b+c)

上面我们大致刻画了一下一个非平行矩形的特性。实际上如果我们能找到一组ad=bc,那么可以唯一确定一个平行矩形,以及它内部包含的一个非平行矩形。这样的平行矩形在我们的允许范围内可以出现(h-a-d+1)(w-b-c+1)次。稍微修改一下公式:

\begin{aligned} &\sum_{1\leq a,b,c,d}[h\geq a+d][w\geq b+c][ad=bc](h-a-d+1)(w-b-c+1)\\ =&\sum_{1\leq n}(\sum_{a|n}[h\geq a+\frac{n}{a}](h-a-\frac{n}{a}+1))(\sum_{b|n}[w\geq b+\frac{n}{b}](w-b-\frac{n}{b}+1))\\ =&\sum_{1\leq n}f(n)g(n) \end{aligned}

我们可以分别计算fg。计算的方法也很简单,枚举a,并枚举所有a的倍数进行贡献即可。由于1\leq ab\leq hw。因此总的时间复杂度为O(hw\log_2 hw)

Ad hoc

题目1:给定一个无限长的一维网格,其中有一个棋子。以及一个长度为n的字符串SS中仅包含3类字符A,B,?。之后游戏由A,B二人进行。流程如下。

  1. 读取并删除S最前面的字符c
    • 如果cA,那么A行动。A可以选择一个空网格,放置一个挡板。如果这时候棋子附近的两个网格都存在挡板,那么A获胜。
    • 否则cB,那么B行动。B可以移动棋子到一个附近的空网格中,或保持不动。
  2. 如果S为空,则B获胜,否则回到步骤1

其中1\leq n\leq 10^6。记k表示?的数量,那么允许你将?替换为AB,问总共2^k种替换的情况下,有多少种情况A能获胜。

提供一道题目

我们可以考虑这样一个问题,有一个二元组(a,b),之后如果下一个操作人为A,则二元组被替换为(\min(\lceil a/2\rceil,b),1),否则B操作,二元组被替换为(a,b+1)。当a=1的时候A就获得胜利。这个问题等价于原问题。

可以发现在B获胜的情况下,S中最多只允许出现O(\log_2 n)A。我们记mA的出现次数,b_i表示在第iA出现后以及第i+1A出现前,B允许操作次数。这样我们得到另外一个序列b_1,\ldots,b_{m-1}

可以发现A第2次操作后,二元组的第一个元素为b_1+1;第3次操作后,二元组的第一个元素为\min(\lceil (b_1+1)/2\rceil,b_2+1);第4次操作后,二元组的第一个元素为\min(\lceil (b_1+1)/2^2\rceil,\lceil (b_2+1)/2^1\rceil,b_3+1)。而最后一次操作后,结果为\min_{i=1}^{m-1}\lceil (b_i+1)/2^{m-i}\rceil,而要让B获胜,这个值必须大于1

我们可以倒着进行DP,记dp(i,j)表示确定从i往后的所有未知数且S_i被设置为A,且A已经操作了j次。可以发现DP可以O(n\log_2n)完成。

概率计数

题目1:给定字符集C,以及给定n个长度为m的互不相同的字符串s_1,\ldots,s_n。之后要求考虑这样一个问题,初始有一个空串,之后不断随机选择一个字符加入到空串尾部。记x_i表示n个字符串中首次出现位置最小的是第i个字符串的概率。要求输出x_1,\ldots,x_n。其中1\leq n,m\leq 300

提供一道题目

这道题的一般做法是建立AC自动机。之后根据转移关系搞出转移矩阵A,之后高斯消元求解即可。这样做的时间复杂度为O(n^3m^3)

这样比较慢,有一种很难理解的做法。就是加入一个新的变量x_0。之后则有:

\left\{ \begin{array}{lcr} \sum_{i=1}^nx_i&=&1\\ x_i+\sum_{j=1}^n\sum_{k=1}^{m-1}[pref(s_i,k)=suf(s_j,k)]\frac{x_j}{C^{m-k}}-\frac{x_0}{C^m}&=&0 \end{array} \right.

对上面这n+1个式子进行高斯消元就可以得出解了。当然我也不太明白这个公式是啥意思。

线性递推计数

题目1:给定线性递推关系,对于所有i\geq k,有F_i=\sum_{j=1}^kF_{i-j}a_j,以及F_0,\ldots,F_{k-1}。要求计算F_n,其中1\leq n\leq 10^{18}1\leq k\leq 10^5

BM算法+FFT,可以做到O(k\log_2k\log_2n)

题目2:给定线性递推关系,对于所有i\geq k,有F_i=\sum_{j=1}^kF_{i-j}a_j,以及F_0,\ldots,F_{k-1}。要求计算F_0,\ldots,F_n,其中1\leq n\leq 10^{6}1\leq k\leq 10^5

\overline{F}=\sum_{i=0}^{\infty}F_ix^i,令\overline{A}=\sum_{i=1}^na_ix^i,那么可以发现\overline{G}=\overline{F}*\overline{A}-\overline{F}仅有x^0,\ldots,x^{k-1}项的系数非0。因此我们可以在模x^k意义下计算出\overline{G}

之后可以发现有:

\overline{F}=\frac{\overline{G}}{\overline{A}-1}

我们可以用多项式求逆快速算出\overline{F}的前n+1项。总的时间复杂度为O(n\log_2n)

数论计数

题目1:处理q个请求,每个请求给定n,m,要求计算\sum_{i=1}^n\sum_{j=1}^m [gcd(i,j)=1],其中1\leq q\leq 10^3,且1\leq n,m\leq 10^6

不妨认为n\geq m,直接用莫比乌斯函数可以得出:

\ \begin{aligned} &\sum_{i=1}^n\sum_{j=1}^m [gcd(i,j)=1]\\ =&\sum_{i=1}^n\sum_{j=1}^m \sum_{d|gcd(i,j)} \mu(d)\\ =&\sum_{d=1}^m \mu(d) \sum_{i=1}^{\lfloor \frac{n}{d} \rfloor}\sum_{j=1}^{\lfloor \frac{m}{d} \rfloor}\\ =&\sum_{d=1}^m \mu(d) \lfloor \frac{n}{d} \rfloor \lfloor \frac{m}{d} \rfloor \end{aligned}

考虑到\lfloor \frac{n}{d} \rfloor\lfloor \frac{m}{d} \rfloor分别只有O(2\sqrt{n})O(2\sqrt{m})种可能取值,因此我们可以用数论分块,通过预处理\mu函数的前缀和,预处理的时间复杂度为O(10^6),之后每个请求可以O(\sqrt{n}+\sqrt{m})完成。

题目2:处理q个请求,每个请求给定n,m,要求计算\sum_{i=1}^n\sum_{j=1}^m gcd(i,j),其中1\leq q\leq 10^3,且1\leq n,m\leq 10^6

不妨认为n\geq m,类似题目1,展开后得到:

\begin{aligned} &\sum_{i=1}^n\sum_{j=1}^m gcd(i,j)\\ =&\sum_{g=1}^m g \sum_{i=1}^{\lfloor \frac{n}{g} \rfloor}\sum_{j=1}^{\lfloor \frac{m}{g} \rfloor} [gcd(i,j)=1]\\ =&\sum_{g=1}^m g \sum_{i=1}^{\lfloor \frac{n}{g} \rfloor}\sum_{j=1}^{\lfloor \frac{m}{g} \rfloor} \sum_{d|gcd(i,j)} \mu(d)\\ =&\sum_{g=1}^m g \sum_{d=1}^{\lfloor \frac{m}{d} \rfloor} \mu(d) \sum_{i=1}^{\lfloor \frac{n}{gd} \rfloor}\sum_{j=1}^{\lfloor \frac{m}{gd} \rfloor} \\ =&\sum_{g=1}^m g \sum_{d=1}^{\lfloor \frac{m}{d} \rfloor} \mu(d) \lfloor \frac{n}{gd} \rfloor \lfloor \frac{m}{gd} \rfloor \\ =&\sum_{gd=1}^m \sum_{g|gd} g\mu(\frac{gd}{g}) \lfloor \frac{n}{gd} \rfloor \lfloor \frac{m}{gd} \rfloor \\ \end{aligned}

我们令f(gd)=\sum_{g|gd} g\mu(\frac{gd}{g}),很显然我们可以通过枚举所有可能的gd来贡献gd,这样预处理f的时间复杂度为O(10^6\ln 10^6)

之后上面的公示化成了:

\sum_{gd=1}^m f(gd) \lfloor \frac{n}{gd} \rfloor \lfloor \frac{m}{gd} \rfloor

只要预处理f(gd)的和,对nm做数论分块,这样一个请求的处理时间仅为O(\sqrt{n}+\sqrt{m})