近期总结

7.31:
1、(p-2)!%p=1  (p为质数) 感性证明:2~p-2中每个数必然存在它的逆元,互相抵消为1

2、费用流建图时,要让一个点只能经过一次,且有相关费用,可以将它拆成两个点,用中间的边(流量1)来限制(经过这条边才表示真正经过这个点)。

3、求最小最大时想二分

 

8.1:

1、最长反链长度(即一个有向无环图中,选出最多的点,使得点互不到达)=最小链覆盖(用最小数量的链,覆盖整个图,使得每个点至少经过一次)

最小链覆盖怎么求呢?先把它转化为最小路径覆盖(定义类似,但保证每个点严格经过一次,转化方法就是n^2或n^3(直接floyed)搞出每个点能直接或间接到达的点,重新连边),然后拆点做匈牙利(即每个点都可以作为起点一次和终点一次的匈牙利),在有向无环图中,最小路径覆盖=点数-最大匹配数 (感性理解:一开始假装每个点都是一条路径,然后每找到一个匹配,就相当于将两条路径合并为一条,所以找到多少个匹配,总点数就要减去多少,得到答案。

扩展:给定n个点,已知选了一个点i,就不能选ai集合中的所有点,求最多能选多少个点?

(给对应关系连边,将问题转化为求最长反链长度,按照上面即可

2、给出n个位置,每个位置上有数字a1~n,每次可以将任意一个位置上的数字减去1,但不能连续两次减同一个位置的数字,求将所有位置的数字减至0的方案数。

转化,每次减去某个位置的数,相当于在一个序列中记下该位置,要使序列中相邻的位置不同。考虑dp,设f[i][j]表示当前处理到第i个位置,得到j个非法空(即连续的同个位置在序列中出现) ,枚举p,q,分别表示当前把i分配给p个空,包含q个非法空区域在内

那么转移到j-q+a[i]-p(删去了q个非法空,a[i]-p的部分就是新的非法空)

方程f[i][j-q+a[i]-p]+=f[i-1][j]*C(j,q)*C(s-j,p-q)*C(a[i]-1,p-1) (j个非法空中取q个,s-j个合法空中取p-q,将a[i]个数分配到p个空中)

写得有点凌乱 建议看原题http://120.77.82.93/senior/#main/show/3424

 

8.2:

1、(看了下B组T2发现自己不会) 给出一个数列,两个人轮流删数,每次只能删当前数列的头或尾,求先手能删的数的总和的最大值。考虑dp,f[i][j]表示在数列i~j中,先手所能取到的最大和。先手要不取数列头,要不取数列尾,然后就变成另一方先手了,我们就是要选取完后另一方先手最小的取法,所以转移为f[i][j]=sum[i][j]-min(f[i][j-1],f[i+1][j])

扩展:给出一个环,两个人轮流删数,第一次任意删一个,然后每次只能删上一个人删的位置的旁边(左或右),求第一次删有哪些位置是先手必胜。 

枚举每个位置,删了它后就相当于将环破成链,等价于每次删头或尾的模型(链的头尾就是环中的左右两边)。

2、有 n个关卡,初始有 q 条命,通过每个关卡的概率为p, 每通过一个关卡,会得到 u 分和1条命,生命上限为 q。其中 u=min(最近一次连续通过的关数,r), 若没有通过这个关卡,将会失去1条命,并进入下一个关卡。当没有生命或没有未挑战过的关卡时,游戏结束,得到的分数为每关得到的分数的总和。求一个最小的p,使得在p下的期望得分能超过s。

二分p,判断是否可行。一般期望题都是要dp概率,然后正着推会好搞些。期望+=每一步的概率*该步产生的答案。设g[i][j][k]表示当前已经通过第i关,j相当于当前的u,剩k条命的概率。

则若第i+1关获胜:g[i+1][min(j+1,r)][min(k+1,r)]+=g[i][j][k]*p; 若第i+1关失败:g[i+1][0][k-1]+=g[i][j][k]*(1-p);获胜会产生min(j+1,r)的积分,对总期望的贡献为:ans+=g[i][j][k]*p*min(j+1,r);

如果n很大,就考虑矩乘优化。怎么用矩阵搞这坨东西呢?将后两维压缩成一维(钦定下表),然后设f[a][b][i]表示第i轮状态a到状态b的概率,f[a][0]存答案。显然有f[a][c][i+1]+=f[a][b][i]*f[b][c][i],这玩意就是矩乘(f自己乘自己)。初值f[a][b][1]就是上面的式子后面的部分当作a,左边部分当作b,答案其实表示的是假设一定能走到这一步,它对答案的贡献。最后的期望答案为f[初始状态][0](它必然包含了所有状态的答案)。

总结:也可以构造两个状态之间的关系(二维),然后用矩乘优化

 

8.3:

1、(a&b)+(a|b)=a+b 原理:位运算。

2、错位重排(编号是1、2、…、n的信件,装入编号为1、2、…、n的n个信封,要求每封信和信封的编号不同,问有多少种装法?) 公式为f[n]=(n-1)*(f[n-2]+f[n-1])

推导:对于1号信件,可以把它放到2~n号信封,共(n-1)中放法,我们假设它放到了x号信封,那么x号信件就有两类放法:放到1号信封或不放到1号信封。如果它放到1号信封,那么还剩n-2个信件,方案数是f[n-2],如果它不放到1号信封,那这和n-1个信封的错位重排本质上是一样的(把n-1个信件放入n-1个信封,每个信件都有一个不能放的信封,且每个信件所不能放的信封都互不相同),所以方案数是f[n-1]。所以整理一下就是f[n]=(n-1)*(f[n-2]+f[n-1])。

3、斜率优化套路:钦定i>j>k,且j比k优,写出相应的不等式,移项得到一个形如(f[j]-f[k])/(s[j]-s[k])<(或>)s[i]的式子(f为dp数组,s为已知数组),若不等式符号为<则维护下凸,为>则维护上凸(即每次加入一个点,就删去一些点)。在有效点集中,我们二分查找一个斜率小于s[i]且最大的点(或斜率大于s[i]且最小的点,看不等式符号决定),即为当前i的最优决策点。特殊地,如果s[i]是递增或递减,那么我们也可以用单调队列维护(就是不断删队头)。

4、求1~n的逆元。递推,f[i]=f[p%i]*(p-p/i)%p  这样也可以通过递归单求n的逆元(那还不如用费马小定理呢

推导:令p=a*i+b,则a*i+b\equiv0(%p)。因为要求i的逆元,所以应将式子转化为i^-1=...的形式。即1/i=-a/b=-y/x*(y%x)^-1,为避免出现负数,还应加上p。所以得到上述递推式。

5、对拍程序。先打出data(出数据的),std(暴力),test(要测试的程序)(都不用打文件输入输出),将它们的exe文件弄到一个文件夹中,在文件夹中建立一个bat文件,代码如下:

:again
data>std.in
std<std.in>std.out
test<std.in>test.out
fc std.out test.out
if not errorlevel 1 goto again
pause

然后就可以啦。背板的注意事项:行末不用打分号,again前要加:,fc是比较,errorlevel 1不能换成别的数字,>的输出,<是输入,引用的exe文件名要写前面,记得打pause停住页面……

 

8.4:https://blog.csdn.net/txl199106/article/details/71504478 根据这上面的知识点开始学习(复习)。

1、差分约束其实就是将一些不等式的约束条件转化为图,然后跑最短或最长路。

2、求树的直径:先找出到根距离最远的点t,再找出距离点t最远的点p,然后t到p的链的长度就是树的直径了

3、树的重心就是以该点为根,它的最大的子树节点数最少,删去重心后,生成的多棵树的节点数会尽可能平均。求法就是树型dp,O(n)。

4、一把i-1等级的武器和一把i-2等级的武器有p的概率融合成一把i等级的武器,有(1-p)的概率融合成一把i-2等级的武器。只可购买0等级武器或1等级的武器,花费已知,其它等级的武器只能靠融合生成,求生成n等级武器的期望花费。

(这题有点像随机游走啊都是列式子解方程) 我们可以发现i等级武器是由一个i-2等级武器和若干i-1等级武器融合生成。设g[i]表示i-2等级武器进化到i等级武器的花费,f[i]表示生成i等级武器的总花费。g[i]=f[i-1]*p+(f[i-1]+g[i])*(1-p) ,意思就是有p的概率,买一把i-1就成功了,有1-p的概率失败,那i-1的武器就白花了,又回到了原点重新来过。 解方程就得到g[i]=f[i-1]/p,那f[i]=g[i]+f[i-2],因为你首先要有一把i-2的武器。

5、中国剩余定理(孙子定理):有个形如x%mi=ai(所有mi两两互质)的一元线性同余方程组,设P=m1*m2*m3……,则在模P意义下,x有且只有一个解,设pi=P/mi,ti为pi在模mi下的逆元,则这个x的唯一解为ai*ti*pi%P的和。证明似乎长这样:(这次百度百科莫名好懂) 我们要证明X为整个方程组的解,就是将X代入方程组的每个方程都行得通。X=a1*t1*p1+a2*t2*p2……对于第i个方程,因为ai*ti*pi%mi=ai(ti和pi是模mi下的逆元),而其它aj*tj*pj%mi=0(pj是由除了mj外的所有m相乘得到的,自然是mi的倍数),所以x%mi=ai。得证!

6、快速求1~n每个数的m次幂:设f[i]=i^m,这是个积性函数,显然有f[i]=f[p]*f[i/p](i%p=0),那么我们就可以先预处理出所有质数的m次幂,然后O(n)扫过去,用上面的式子算合数的m次幂。

7、已知n,m,求(x^m -x)%n=0且x在n以内的解的个数。n=p1*p2*p3……*pc(p为质数)

将式子转化为形如(x^m -x)%pi=0的同余方程组,对于每个同余方程枚举得到它在pi以内的解。最终答案就等于所有解的个数相乘,为什么呢?假设最终的解的其中一个为X,其必然满足X%p1=x1,X%p2=x2……,其中xi为第i个方程的其中一个解。由于p固定,对于每个不同xi的组合,其得到的X都是不同且唯一的(中国剩余定理),所以把每个方程的解的个数相乘,就是总的解的个数。

 

8.5:

1、我们在求a^m%p时,如果m很大,p较小且为质数,可以将式子转化为a^(m%(p-1))%p,因为根据费马小定理,a^(p-1)%p=1,也就是说在模p意义下每p-1个a相乘就得到1,所以只有m%(p-1)个a是有效的(或者说是分成p-1块时多出来的)

2、有一个n*m的矩阵A,a[i][j]=(i-1)*m+j, 操作是将某些行某些列乘上某个数,求操作完后矩阵的数值和。

ans=a[i][j]*h[i]*l[j]的和(h[i]为第i行乘上的值,l[j]为第j列乘上的值),对于一列j,这列的权值和=l[j]*(每个a[i][j]*h[i]的和),我们先算出第一列的每个a[i]*h[i]的和,那么第二列的a[i]*h[i]和就等于第一列的加上所有h的和(想想原本同一行第一列和第二列间相差1,乘上h[i]后列之间的差就变成了h[i]),以此递推。

3、一个n*m的棋盘,每个格子有权值,起点在(1,1),每次可能走到当前右上、正右或右下,在这三个格子中选择一个最大的走(确保每次只有一种走法),有时会修改某个点的权值,在线询问在当前点走k(k很大)步会到达哪个点。

发现走的点会是循环的,循环节最大为n*m,所以可以将k缩小很多。但还不够,怎么办呢?
发现最多走m步就必然会经过第一列(每次都要向右走),设f[i]表示第一列的第i行走m步会到第一列的第几行,每次修改维护f[i],对于查询,先将它走到第一列,然后在第一列里面做循环节(这样就相当于把以点为单位的循环节压缩成了以行为单位的循环节)。怎么维护f[i]呢?可以知道如果第j列的l~r的行要修改,那第j-1列所有会经过第j列l~r的点都要修改,这个区间显然一定是连续的,且每次最多只会扩展一个,最后找到第1列要修改的范围,将它们都改成原始修改点所会到达的第一列的行。

4、给出n个数,若区间[l..r]中的所有数按从小到大排序后是连续的,则该区间是优美区间。每次询问求包括区间[li..ri]的优美区间。

给定的区间[l..r],说明一定要使[l..r]中出现的所有数都变为连续,也就是说令mn=min[l..r],mx=max[l..r],则所有mn~mx的数都务必要被答案区间包含(当然我们的答案区间为了包含它们可能要被迫要包含一些更大或更小的东西)。那我们每次就要找出ll=mn~mx的数中位置最小的那个,rr=mn~mx的数中位置最大的那个,将答案变成区间ll~rr,然后判断是否合法(如果不合法再继续扩张,以此类推)

 

8.6:

1、主席树求mex。mex的定义是区间内一个没有出现的最小数字x。

建立n棵权值线段树(就是个主席树),第i棵保存的是在1~i的位置范围内每个数字出现的最右端(如果它在1~i的位置没出现过值就是0)。对于每次询问l,r,就相当于找出第r棵子树中数值最小的那个最右端小于l的数(这就相当于那个数在位置l~r中没有出现)。权值线段树维护的是最小值。

2、splay能支持但线段树支持不了的东西:插入、删除、区间反转。涉及区间最值、区间翻转之类的一般是按照位置来构造splay,而不涉及区间的就按照点权来构造splay。

如果我们要翻转区间l~r,我们先将l的前驱旋转到根,r的后继旋转到根的右儿子,那么r的后继的左儿子那一块就是区间l~r了,直接对它做个标记,每次再标记下传,翻转它左右儿子。

如果我们要插入一个节点,那么就像构造splay那样插入,从根节点下来,找到它合适的位置。插入后再将它旋到根。

如果我们要删除一个节点x,那就将x的前驱旋转到根,x的后继旋转到根的右儿子,则这个x的后继的左儿子就是只有x了,直接把它删掉就ok啦。

如何求x的前驱或后继呢?先将x旋转到根,则x左子树中最靠右的那个点就是x的前驱,x的右子树中最靠左的点就是它的后继。

旋转:每次将x向上旋转一个位置,注意如果x的父亲y和祖父z在一条链上,那么要先旋转y再旋转x。

int y=fa[x]; int z=fa[y]; //y是x父亲,z是x祖父
int k=chk(x);
fa[x]=z; a[z][chk(y)]=x;
fa[a[x][k^1]]=y; a[y][k]=a[x][k^1];
fa[y]=x; a[x][k^1]=y;  //6步搞定 要注意顺序 别覆盖了
updata(y); updata(x); //更新x、y上挂的东西

后记:如果我们是用位置来构造splay的,给出一个权值,如何知道它在splay中点的下标呢?建立一个权值到点的下标的映射即可(因为不管如何翻转点的下标都不变)

如果我们现在知道一个点的下标x,如何求x排第几呢?直接把它旋转到根然后输出它左子树+1就好啦。

3、树上差分,有很多个a,b,覆盖树上a到b的路径,询问点x被覆盖了多少次?每次找到a和b的LCA点c,然后差分数组f[a]++,f[b]++,f[c]--,f[father(c)]--。查询点x被覆盖多少次就是求f数组点x的子树和。

4、RMQ是怎么O(1)查询的呢?我们维护f[i][j]表示i~i-1+2^j的最大(小)值,对于询问l~r区间,注意到它可能不能刚好是一块大小为2的幂的块,但是它可以由两个大小为2的幂的块覆盖(可重复),于是我们设k=log2(r-l+1)(下取整),答案就是f[l][k]和f[r+1-2^k][k]更优的那个(也就是说从l向右延伸出一个最大的但不越过r的大小为2的幂块,从r向左延伸出最大的但不越过l的大小为2的幂的块,这两个块必然相交,将l~r完全覆盖)。

5、树链剖分:一个点x的重儿子是它子树最大的那个,由重儿子组成很多重链,将整棵树拆分成很多条链,通过dfs(每次先走重儿子)对整棵树重新编号,使得一条重链的节点编号是连续的(区间修改的前提是点的编号连续)。这样,在做修改时,我们就可以用线段树对每条重链上的一部分进行区间修改。

6、一棵n个节点的有根树,每次给出l,r,x,询问[l..r]区间内每个点和x的最近公共祖先。

对于[l..r]内的每个点a,假设每条边的边权为1,a和x的最近公共祖先的深度就是a和x重复走过的路径和+1。那我们就每次将[l..r]每个点到根的路径的边都加上1,最后统计x到根的路径的边权和就好啦。

但我们这样每次还要枚举[l..r]的啊,有点慢,怎么办呢?注意到[l..r]的答案等于[1..r]的答案-[1..l-1]的答案。那么我们可以依次插入那n个点,每插入一个点i,就可以知道[1..i]与任意一个x的答案啦。要离线处理。用树链剖分维护一个点到根的修改或求和。

7、给一棵n个节点的树,m个操作:1.把节点x的点权增加a;2、把以节点x的为根的子树中所有点的点权都增加a;3、询问x到根的路径中所有点的点权和。

8、一棵n个节点的树,操作m个分为三种:1.把节点 x 的点权增加 a ;2.把以节点 x 为根的子树中所有点的点权都增加 a ;3 .询问节点 x 到根的路径中所有点的点权和。

这题可以直接树链剖分,也可以只用线段树:对于操作1,它的子树的所有答案都要+a,对于操作2,设x的子树包含节点y,则y的答案应该加上(deep[y]-deep[x]+1)*a,化一下式子变为a*deep[y]-(deep[x]-1)*a,对于每个y,减去的(deep[x]-1)*a相当于常数,会变化的只有a*deep[y]。那么我们每个点存两个值,一个是加减s1,一个是乘法s2 (s2最后要乘上deep[y])。每次就每个点的s1统一减去(deep[x]-1)*a,每个点的s2统一加上a,最后询问就是s1+s2*deep[y]。

9、http://hzwer.com/8053.html  上面有很多好玩的分块题目,慢慢搬上来。

 

8.7:

1、一棵n个节点的树,m个点对(x,y)表示在一条树上路径上走了点x就不能走点y,求合法的路径数。

按dfs序将这n个点重新编号后,可以发现每次就是规定一段区间不能到达另一段区间(或者如果x和y是祖先关系的话就是一段区间不能到达另两段区间)。那么我们考虑建立一个二维平面,平面上一个位置(x,y)就表示x到y的这条路径合不合法,显然我们可以将每次的规定转化为一个矩形覆盖这个平面,最后计算没被覆盖的点就是答案(扫描线,注意标记不下传,因为这样方便撤销。具体实现就是设一个f,一个bz,如果这个区间bz有值f就为0,反之f为左右儿子f之和)

2、引出一个问题,什么时候线段树的标记不用下传呢?在标记一段区间均为某个值,且每个标记所标记的值都相同时。

 

8.8:https://www.cnblogs.com/WAMonster/p/10118934.html莫队学习。

 

8.9:

1、n*n的平面,有些位置上有围墙,求每个点到围墙的最近距离。

先想dfs,分别从每个围墙出发,做标记,时间复杂度是O(n^4)左右的,太大了。为什么呢?因为dfs无法保证每一步走的都是最优,于是就要重复走。如果我们把它改成bfs,一开始将所有围墙加入队列,每次就都可以保证到达的点一定是最短的,时间复杂度O(N^2)

 

2、一棵由key为关键字构造的二叉排序树,每个点上有一个值val,给出每个点的key和val,要求若两个点相连则两个点的key值的最大公约数>1,求在合法构造这棵二叉排序树的前提下,树上所有节点的sum值和最大为多少(定义一个点的sum为这个点的子树的val和)。

这种想不出构造策略是怎样的题就想想dp。按key排序,如果我们给这段区间规定一个根节点,则左边的就是左子树,右边的就是右子树(二叉查找树性质)。我们可以设f[i][j][k]表示i~j的区间内根节点为k的最大值,但是想想我们真的有必要记录根节点吗?我们记录根节点是为了保证一条边连接的两个点的key的最大公约数>1,那我们不妨把这个判断放到儿子那去判,判断它和它父亲的~,可以发现,一段[l..r]的父亲一定是l-1或r+1,具体是哪个开一维记录一下就好了,然后[l..r]选出一个父亲(深度最浅的点),判断可不可以和这一段的父亲(也就是该点的父亲)连边。

 

3、n个1~n的互不相同的数,求选择一段区间翻转一次后(也可以不翻转),最多有多少个i满足i=a[i]。

可以发现如果i≠a[i],那么(i+a[i])/2会是一个旋转中心,枚举每个旋转中心,必然会有一些点对是以该旋转中心为中心的(即该旋转中心可以使它们归位),然后我们将这些点排序,每次看看这个区间内会有多少个点归位,统计最大值(注意在此同时翻转操作也会使一些点乱位,要减去)

 

8.10:

1、向上取整:ceil(x) 向下取整:floor(x) 或者(int)x  四舍五入:round(x) 或者(int)(a+0.5) (如果有负数的话就别用int那种写法。

 

2、向量(有大小有方向的量)可以用坐标(x,y)表示(即从(0,0)到(x,y)带箭头),向量与向量相加就是横坐标与横坐标相加,纵坐标与纵坐标相加,用图像表示就是平行四边形法则((0,0)、(x1,y1)、(x2,y2)分别为平行四边形的三个点,而相加后构成第四个点)。向量与数x相乘就是横纵坐标分别和数x相乘,用图像表示就是把原来那条带箭头的直线方向不变,大小变成原来的x倍。

 

3、欧拉序(括号序),主要用于树上莫队。dfs时每次进入一个点,把它记在队列上,从那个点出来时,也把它记在队列上(即每个点在队列中会出现两次)。

 

8.11:

1、dij如果要用自带的优先队列,就要用结构体(因为要存两个值)。要先定义一个结构体Node,然后priority_queue<Node> q; 要重载运算符(类似快排的cmp):

bool operator < (Node a,Node b)  //这个<不能变!!!内部比较是用<号的

{

return a.val>b.val; //这个是从小到大,如果要从大到小改成<(类似greater和less啦)

}

用的时候q.top()返回的会是结构体,而q.push()也要插入结构体。

 

2、最小斯坦纳树类似于最小生成树,但与最小生成树不同之处在于它可以包含一些非必经点。也可以说,最小生成树是特殊的最小斯坦纳树。最小斯坦纳树的求法本质上是状压dp,设f[i][s]表示以i为根(接口),必经点走的状态为s的最小代价(注意这里i如果是必经点,s中并不一定要求i那一位为1)。转移有两步:1、f[i][s]=min(f[i][s]+f[i][s’’])(其中s’’是s关于s的补集);2、f[i][s]=min(f[i][s],f[j][s]+d[i][j]);(其中i到j有边相连。) 注意第二步转移的s是相同的,也就是说有后效性,我们可以将所有点扔入spfa的队列中进行转移(dij也行,当然也可以将转移中d[i][j]变成i到j的最小距离,然后直接扫过去)。初值f[i][1<<(i-1)]=0,其余为无穷大。

枚举s的子集时有一个技巧:for(int ss=(s-1)&s;ss;ss=(ss-1)&s)(每次-1并&一下s)

 

3、一个平面上有n个格点被覆盖,这些格点是相连的,且不会围成一个洞,求每个点到其它所有点最短距离和(两点相邻则距离为1)。

一个点到另一点则行要到那个点的行,列也要到那个点的列,这是可以拆分开计算的。考虑把每一行连续的点缩成一个点,把相邻的点连边,则会形成一棵树。树上的每一条边对答案的贡献就是它上面的节点和*它下面的节点和(每个上面的节点都要穿过这条边到达每个下面的节点)。这样我们就搞完了每个点到达另一个点行的总贡献。接下来考虑列,和行一样的操作(也是缩点然后树形dp)。

 

4、后缀数组sa[i]表示排第i名的字符串的编号,rank[i]表示编号i的字符串的排名。Height[i]表示排第i名的字符串和排第i+1名的LCP(最长公共前缀)。有一个性质:height[rank[i]]>=height[rank[i-1]]-1。意思就是说编号为i的字符串与排它前一位的字符串的LCP必然大于等于编号为i-1的字符串与排它前一位的字符串的LCP-1。

排名i的字符串和排名j的字符串的LCP是height[i+1~j]的最小值。

 

5、如何求一个字符串中不同子串的个数呢?

显然每个子串都可以用某个后缀的前缀表示,所以相当于求后缀之间不相同的前缀个数。将后缀排好序,每次加上(n-sa[i]+1)个新前缀,减掉height[i]个已经算过了。

 

8.12:

1、n(n≤5)*m(m≤200000)的格点图,有些格子有障碍无法通过。q(q≤50000)个操作,每次询问从一个格子到另一个格子的距离或修改某个格子的障碍状态。

解法1:m很大,n很小。先考虑按m分块,每根号m为一块,每块记录首列中每一行到尾列中每一行的距离(共两列,暴力预处理)。对于每次询问,找到起点所属的块,将起点走到所属块的尾列(记录到尾列每一行的代价)。然后dp,求出到终点所属块的首列的每一行的最小距离。然后再从终点块的首列中每一行出发,到达终点块,统计最小代价。每次修改则暴力更新修改点所属块。时间复杂度O(q*sqrt(m)*n*n),极限大概五亿多,正常觉得是过得去的,但是常数莫名很大,要跑好几秒。

解法2:解法1的缺点是将每个块的大小都固定死了,块与块之间不能合并分离,每次查询都不能直接调用,每次修改整块都要暴力重构。我们考虑线段树,l~r列,保存第l列每一行到第r列每一行的距离,合并两段区间的时候就类似解法1的dp。 这样对于查询就可以直接调用,对于修改只要修改那一列,然后把它的所有祖先(包含那一列的区间)都更新。

 

2、有n个黑点,n个白点,如果黑点x与白点y的曼哈顿距离大于d,则黑点x向白点y连有向边,反之白点y向黑点x连有向边。对于同色的点,则它们之间有且只有一条有向边,方向自定。求由三个点组成的,其中至少有一个黑点一个白点的环的最多个数和最少个数。

考虑环中有两个黑点一个白点(另一种情况同理),则这两个黑点一个要连向白点,一个要被白点连向。Maxs=sum(max(f[i]-g[i][j],f[j]-g[i][j]))(最小同理)。i,j均为黑点,f[i]表示i所连向的白点个数,g[i][j]表示i和j均连向的白点个数。每一次的取max求的是i和j和两个黑点最多可以和多少个白点组成环。这个又要求f又要求g太麻烦。我们化简式子:maxs=sum(max(f[i],f[j]))-sum(g[i][j])

   =sum(max(f[i],f[j]))-sum(C(ff[p],2)) (其中这里ff[p]表示是白点p连向多少个黑点)。求f可以用扫描线,每个点都可以延伸出一个菱形,求菱形所覆盖的点。但这个菱形有点难搞,我们想办法把它转化为矩形。有个东西叫作切比雪夫距离,对于两点x1,y1,x2,y2,它的切比雪夫距离是max(abs(x1-x2),abs(y1-y2)),这样我们将每个(x,y)变成(x+y,x-y),则两点之间原先的曼哈顿距离就等于现在的切比雪夫距离。所以我们以每个点为中心弄出一个长宽都为2d的正方形,在正方形内的点与该点的距离都≤d。

 

3、引出一个问题,一个平面内有很多点,也有很多矩形,如何求每个矩形所覆盖的点数呢?

这里要用到前缀和的思想。一个(y1,y2,l,r)的矩形(占据l~r行,y1~y2列),它的点数就是f[l~r][1~y2]-f[l~r][1~y1-1]。那我们按y从小到大依次询问,每次询问都已经将列为1~y-1的点都加了进去,就可以直接询问[l..r]行的点数啦。

 

8.13:

1、线性求1~n中每个数i的阶乘i!的逆元:f[i]=f[i+1]*(i+1)%p.

证明:由费马小定理得i!^-1=i!^(p-2)=(i+1)!^(p-2)*(i+1)=(i+1)!^-1)(i+1)  (乘上(i+1)是为了消掉(i+1)^(p-2),这样就可以由n倒着推来了)

2、给出三个整数n,m,k,求k元组(a1,a2...ak)的个数,满足a1+a2...+ak=n且没有一个a是m的倍数。

发现条件没有一个a是m的倍数很难处理。我们把所有a%m后,就可以将题目转化为每个a’都在[1..m-1]且(a1'+a2'...+ak')%m=n%m,我们可以找到大约k个合法的(a1'+a2'...+ak'),对于每个和设其为B,那我们的总答案就变成了将B分为k份每份都在[1..m-1]的方案数*把(n-B)/m个m分成k份且每份可以为0的方案数。后者就是可以为空的隔板问题,前者可以用合法-不合法(枚举至少有多少份超过m-1,用容斥)。其实这种感觉会算重的东西都可以想想容斥。当规定一个数x不能是m的倍数时,不妨想想把x%m,将规定转化为x在[1..m-1]中,最后再想办法加会若干m。

3、spfa判负环:一个点被更新的次数不会超过总点数(最多每个点都更新它一次),超过即出现负环。

 

8.14:

1、已知x1,x2,...xn(n<=6且x1较小其余x很大),求用这n个数所不能构造出的最大的数(构造方式为直接相加,每个数可以加多次)。

因为x1很小,我们从它下手。如果我们能构造出一个最小的p%x1=y,则所有大于p且%x1=y的数我们都可以通过p加上若干x1构造出。这样假设我们已经构造出了p%x1=[0..x1-1]的所有最小的p(对于在模x1下每个不同的余数都记录一个最小的p)。那么每个p都加上若干x1就可以构造出任意数。而且显然每个p-x1都是我们构造不出的(因为p-x1和p在模x1下同余,如果我们可以构造出p-x1,则p肯定不是在那个余数下最小的)。所以我们只要找到所有(最小的)p中最大的那个,把它-x1就是答案(可以证明不存在比它更大的不可构造之数,因为它作为最大的一个p,%x1为其它的所有数在那时都已经可以被构造了)。

于是我们可以用p[i]表示%x1=i的可以构造出的最小的p。首先p[0]=x1,然后对于每个p[i],我们都可以用它来尝试更新所有p[(i+xj)%x1]。因为这个dp是有后效性的,我们可以用spfa来做(每次更新了一个点把它又加入队列,因为它被更新后又可以尝试更新其它点)。

2、给一张n个点m条边的带权无向图,求是否存在一条从1号点到n号点的路径,满足长度恰好为T(可以重复走)。(n,m<=50, t<=10^18,wi<=10000;)

假设走的长度S=T,则必然有S%p=T%p。这样我们似乎可以将T和S都变成模p意义下的,然后如果走到终点存在S%p=T%p,就说明存在长度恰好为T的路径。但仔细一想不对啊,这样S加上若干p确实等于T,但如果不存在代价为p/2的边呢?所以p为任意值的情况下不能保证如果S%p=T%p则存在长度恰好为T的边。那我们只要把p设为随便某条边的长度的两倍,就可以解决了(这样它就一定可以在这条边上来回摩擦若干次来消除模数的影响)。我们再思考一下,是否把p选为某条边长度的二倍就代表一定要走这条边呢?显然不是。因为对于任意一条不经过那条边的路径,假设它的长度S=T,则S%p=T%p,也符合我们先前的判定标准。这样我们把p随便乱设为某条边的二倍就真的没问题了。

更正:p的取值应为与起终点相连的某条边/2.

然后就是直接上spfa啦(因为dp会有后效性),设个数组f[i][j]表示到点i,S%p=j的长度是否存在。最后判定f[n][T%p]就好了。

这类题目的关键在于有一个很大的数,我们想办法把它取模来计算。

3、一个序列,若最长下降子序列长度不超过2,则可以转化为两个最长不下降子序列将它们完全覆盖(可重复),还可以转化为:对于一个数,要不所有位置在它前面的数都要比它小,要不所有比它小的数位置都在它前面。

4、闭合子图:一个子图内所有点所连向的点都在子图内的子图。假设每个点都有点权(可能为负),则拥有最大的点权和的闭合子图就是最大闭合子图。

将每个正权点和源点连一条容量为点权的边,将每个负权点向汇点连一条容量为点权的绝对值的边,每个点之间之前有边的则连一条容量为无穷大的边。答案就为所有正权点的点权和-最大流。

这个做法有点神奇,感性理解一下:一个正权点如果你要选,你就要付出一定代价,要不就是那个代价过大你干脆不选。这里的最大流就等于每个点要付出的代价和那个点的贡献的最小值。(大雾)

5、二分图中极大匹配就是每次任意选择匹配,直到不能匹配为止。完美匹配是左边n个点能和右边n个点一一对应的匹配。要使得任意一个极大匹配都是完美匹配,则要求二分图中任意一个连通块都是左右点数相等的完全二分图(左边每个点都连向右边所有)。

6、在平面上,每次可以向上或向右走,求从(x1,y1)走到(x2,y2)的方案数(保证(x2,y2)不会在(x1,y1)的左下方)。

已知有(x2-x1)个向右操作,(y2-y1)个向上操作,则相当于将这些向右操作插入向上操作中,也就是将向右操作装入向上操作+1个篮子,每个篮子可以为空(其实将向右向上操作反过来也可以),就是隔板问题。

7、扩展一下,假如我们要求从(x1,y1)走到(x2,y2)并且不经过某条y=x+b的直线的方案数呢?

考虑用总方案数-不合法的方案数。不合法的方案数怎么算呢?将(x2,y2)对于该直线翻折得到(x2',y2'),不合法的方案数就是(x1,y1)到(x2',y2')的方案数。因为对于每一条经过该直线的路径,都必定存在有且只有一条的镜像路径,所有经过该直线的路径所对应的镜像路径也一定会穿过该直线。注意如果这条直线斜率不是tan45°,则不能用这种方法计算,因为翻转所得到的镜像路径可能是斜线。

8、有上下界的网络流大致分为四种:无源汇有上下界可行流、有源汇有上下界可行流,有源汇有上下界最大流,有源汇有上下界最小流。(无源汇的自然不存在最大最小流啊) 学习博客: https://www.cnblogs.com/liu-runda/p/6262832.html 

无源汇有上下界可行流:

既然每条边都至少有流下界,那么我们就先假设每条边都流了下界,但是这样每个点的流量就不一定守恒了啊(网络流中每个点都流量都要满足守恒,即流入的流量等于流出的流量)。所以对于某些边,我们可能还要使它多流些,以保证流量守恒。

我们令a[i]表示在每条边都只流下界时,流入i的流量-流出i的流量。接下来我们考虑的都是每条边要在流下界的情况下多流多少,所以每条边的容量就变成了[0..上界-下界]。对于一个a[i]>0,也就是说它现在的流入要比流出多a[i],那么我们自然想让它再多流出a[i]以达到守恒,在网络流中如何让一个点多流出a[i]呢?那么必然要有边流入a[i],我们设超级源SS连一条到i的容量为a[i]的边。对于a[i]<0同理,我们设超级汇TT,i向TT连一条容量为-a[i]的边。

然后对于这个图跑一次最大流,如果最大流=所有正的a[i]的和(等于所有负的a[i]和的绝对值),那么就说明你把你新加的边都流满了,也就是说你现在满足流量守恒了,所以就存在可行流。这时每一条边你的流量就是该边的流量下限+该边跑完最大流后反向边的值。

有源汇有上下界可行流:与无源汇不同的是,它多了一个源点S和汇点T(这不废话。),注意到S和T是不满足流量守恒的,本来在普通网络流中它们作为起点终点这没什么关系,但因为我们是上下界网络流,我们跑最大流时起点和终点不是S,T,是SS,TT,这就有大问题了。怎么办呢?直接由T向S连一条无穷大的边,然后像无源汇有上下界可行流那样搞就行啦。

这时整个可行流的流量就是T到S的那条无穷边的反向边流量。

有源汇有上下界最大流:

求出了可行流后,它不一定是最大的。这时候再在残量网络从S到T(注意不是SS到TT)跑一次最大流。

最终的最大流流量=可行流流量+新跑出的最大流流量。

有源汇有上下界最小流:

求出了可行流后,它也不一定是最小的。这时候再在残量网络从T到S跑一次最大流。

最终的最小流流量=可行流流量-新跑出的最大流流量。

8.15:

1、一些奇怪的结论:https://www.cnblogs.com/liu-runda/p/8073477.html

2、n个同学对一个项目进行投票,每个人有p[i]的概率(0<=p[i]<=1)选择支持,(1-p[i])的概率选择反对,现在要从中选出k(k为偶数)个同学,使得平票的概率最大。(若有k/2个人支持,k/2个人反对,则定义为平票)

10.5:

1、最小割=最大流。 so 最大流搞不了的时候可以换个思维。

2、手头有一棵树,先想树形dp,想不到就想并查集、生成树(特别是对边的大小有要求的,排一次序再一次次加边)。

3、最小割树算法:给出一张图,求图中任意两点最小割。 每次从当前点集中随便选两个点,求最小割,然后按照最小割将点集一分为二,不断下去,发现它是一棵二叉树,树边就是那个分的时候最小割的值。然后两点间的最小割就是在树上的路径的最小值。倍增维护。就酱。

10.6:

1、整出分块:即求1~n的下取整n/i的和。发现它们是值是一段一段的,且若一段的开头的数是i,则结尾的数为n/(n/i)。时间复杂度O(根号n)。神奇!(记不住结论的话记得要打表找规律。。

11.2:

1、田忌赛马可以用权值线段树搞。对于每个树上的位置,存三个值a,b,s,表示在该位置所对应的值域内,A类的数还剩a个,B类的数还剩b个(显然这b个数都要比那a个数小),匹配数为s。两节点向上合并时,右边的点的b一定能匹配左边点的a,s更新。这样的复杂度是O(n)的,而且支持修改。也就是说可以把A类或B类某个元素删去,花费logn的时间即可得到答案。

11.4:

1、sum(C(i,n)^2) (0<=i<=n)   =  C(n,2*n) 。

11.7:

1、tarjan的几个模板:

强联通分量(一般为有向图,要visit数组):

void tarjan (int p) {
    dfn[p] = low[p] = ++tim;
    v[p] = 1;
    s.push(p);
    for (int i = head[p]; i; i = e[i].nex) {
        int y = e[i].to;
        if (!dfn[y]) {
            tarjan(y);
            low[p] = min(low[p], low[y]);
        }
        else if(v[y]){
            low[p] = min(low[p], dfn[y]);
        }
    }
    if (dfn[p] == low[p]) {
        color[p] = p;
        v[p] = 0;
        while (s.top() != p) {
            color[s.top()] = p;
            v[s.top()] = 0;
            s.pop();
        }
        s.pop();
    }
}

割点(一般为无向图,若要变为有向图则要先跑次强联通分量):

void tarjan (int p){
    dfn[p] = low[p] = ++tim;
    int kidsCnt = 0;
    for (int i = head[p]; i; i = e[i].nex) {
        int y = e[i].to;
        if (!dfn[y]) {
            ++kidsCnt;
            tarjan(y);
            low[p] = min(low[p], low[y]);
            if ((p == root && kidsCnt >= 2) || (p != root && dfn[p] <= low[y])) {
                cut[p] = 1;
                //++cutsCnt;会重复统计数量
            }
        }
        else {
            low[p] = min(low[p], dfn[y]);
        }
    }
}

割边(也是无向图):

void tarjan (int p, int ed) {//ed为来的边
    dfn[p] = low[p] = ++tim;
    for (int i = head[p]; i; i = e[i].nex) {
        int y = e[i].to;
        if (!dfn[y]) {
            tarjan(y, i);
            low[p] = min(low[p], low[y]);
            if (dfn[p] < low[y]) {
                bridge[i] = bridge[i ^ 1] = 1;
            }
        }
        else if (i != (ed ^ 1)) {
            low[p] = min(low[p], dfn[y]);
        }
    }
}

11.10:
求若干个关键点两两之间最小的最短路:
一定是一个点走最短路到某条边,再走最短路到另一个点。

所以弄个超级源点,与每个关键点连零边,记录到每个点的最小值,以及是有哪个关键点出去的,然后枚举每条边,若是不同关键点出去的,则可以尝试更新。

3.2:

1.求区间绝对众数:

在线段树上维护数字x和次数y
合并时若x相同,则y相加,若x不同,则取y较大的,且y变为两个y的差绝对值。

2.已知a是[l1,r1]内的随机实数,b是[l2,r2]内的随机实数,求a>b的概率。
用数形结合思想,在二维坐标系中x轴上画出a的取值范围,y轴上画出b的取值范围,连成一个长方形,直线y=x下方与长方形的面积交比上长方形总面积就是概率。

3.7:

1.冒泡排序,对于一个数来说,若它之前有t个数比它大,那么前t轮中每轮它会往前移1个位置,之后便不再会往前移了。

2.对于一个无向联通图来说,判断是否可以选出一些边,使得每个点的度数为奇数,只需要看总点数。总点数为偶数则能,反之不能。

3.20:

给出n个位置,每个位置i可以填A[i]或B[i],要使得这n个位置单调不递减,且选A的占一半,选B的占一半(n为偶数),求一种方案。
考虑DP,设f[i][j][0/1]表示到第i个数,选了j个A,当前选A或B,是否合法。最后再从后往前推具体方案。
考虑优化,发现对于f[i][j][0/1]而言,使得它合法的j一定是一个区间,且使f[i][j][0]合法的j的区间与使f[i][j][1]合法的j的区间一定相交或相连。(数学归纳可知)。然后转移显然。

4.5:

设树的直径为x,y,则树上任意一点r到另一点的最大距离,等于r到x或y的最大距离。

4.6:

整除分块:

一个打表可以发现(并不)的结论是n/i的值是一段一段的,且对于段首i而言,段尾是n/(n/i)。然后我们就可以直接搞。

O(根号n)的时间,但是常数有点大。

原式=

具体怎么推的不太清楚 反正当结论来记就完了
直接枚举来求

下面这种做法是常数比较小的 比较优秀。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值