ZCETHAN の Tricks

用来记录一些不属于正统算法,但是是一些常见的经典套路的技巧。科技?

记录的东西会有点 naive。

以及一些简单的结论。(但是看起来简单,用起来惊为天人)

分块时间换空间#

牛了,常见套路,一般用分块来用时间换空间。

求一个可修改序列中 [l,r] 区间内不连续取 3 个数的所取的数最大和是多少。
n1.2×106m218+5
时间限制:2s
空间限制:32MB

如果是朴素的 dp,大家都会。
如果我们考虑修改和区间询问,那么大家都会 DDP。

但是发现空间完全开不下。
空间:int 开 8 个 1e6 的数组,long long 开 4 个。

这时候,对原数列分块,然后块间建线段树,然后块内暴力构造矩阵和求解。虽然会慢一点,但是最终空间是够的,时间也能过去。

Code

前缀优化建图#

一种优化时空的建图方式。我们考虑如果每次要求一个点对一堆点连边,除了线段树优化建图以外,可以考虑是否可以用前缀优化建图。

比如说在 2-sat 中的前缀优化建图:

n 个点,分成 k 部分,m 条限制表示两个点至少选一个,每部分恰选一个。求有无可行选法。

一般我们会把一个点拆成两个,表示选或者不选。现在我们拆成 4 个,表示选或者不选,以及前缀中是否有选,后缀中(不包括本身)是否有选。
这样一来,我们对一堆点连边就变成了向一个点连边,那么对于一堆的点,我们需要预先连一些边。

  1. 如果一个点选了,那么它前缀中肯定有选了;
  2. 如果后缀 i 有选,那么 i 这个点不能选(同一堆中不能一起选);
  3. 如果前缀 i 有选,那么前缀 i+1 有选;
  4. 如果后缀 i+1 有选,那么后缀 i 有选;
  5. 如果 i 选了,那么 i1 的后缀选了;
  6. 如果前缀 i 有选,那么 i+1 不选(后面的点交给前缀 i+1)。

这样会得到这样的图:

然后直接连边就可以了。

或者考虑网络流的优化,具体见这题

O(logn) 分解质因子#

众所周知每个数都只有 logn 级别的质因子。那么的话我们可以利用线性筛每个数都是用它的最小质因子筛掉的,记录一下,然后分解的时候每次除以这个最小质因子就可以精准获取质因子,也就是 O(logn) 分解了。

斐波那契前缀和公式#

i=0nFibi=Fibn+2

太神奇了,二次以上也是有公式的。

C++ 语法#

求一个数组中连续区间的最值:

*max_element(a+l,a+r+1)

向上取整的数论分块#

你可以这样:

xy=x1y+1

然后就变成向下取整的数论分块了。

代替弱化的平衡树的堆#

还是来记一下。用来解决加入,删除,查询最值。可以用极短的代码来代替平衡树。你搞两个堆,我们叫做 AB 吧,每次插入的时候,把元素压到 A 中,然后删除就压到 B 中。每次查询最值的时候就比较 AB 的堆顶是否相同,如果相同就同时弹掉,否则就返回 A 的堆顶就是当前询问的最值了。

struct Heap{
    priority<int> A,B;
    void ins(int x){A.push(x);}
    void del(int x){B.push(x);}
    int ask(int x){
        while(!A.empty()&&!B.empty()&&A.top()==B.top())
            A.pop(),B.pop();
        if(A.empty()) return -1;
        return A.top();
    }
};

Kosaraju#

myee 嘴了一个叫做 Kosaraju 的东西,好像可以很方便地代替 Tarjan 的图论算法。

其流程是这样的:首先我们对整张图跑 dfs,然后按照出递归栈的顺序压入一个 vector 中。接下来倒序遍历 vector 中的点,然后在反图上跑搜索,此时当前能访问到的点就是和当前点在同一个强连通分量中的。

一个套路#

对于查询区间中计算子序列的信息,有一个通用的做法就是,对于所有询问离线,然后移动右指针,然后用 DS 维护每一个左端点的信息。这样的话对于询问区间 [l,r],我们在右端点移动到 r 的时候,查询 [l,r] 区间内的值。然后就得到了右端点为 r 的所有子区间的值。为了统计到所有的子区间,还需要记录一下每个节点的历史最值。这使得这个方法只能维护区间中子区间的最值之类满足覆盖的信息。

分数规划#

好像是很陈旧的东西了。就是给你一堆东西,问你一个分数的值最大是多少。一般采用二分答案,然后在不等式上移项,如果当前二分的答案会使得存在一个策略使得不等式反向,那么答案可以更进一步,否则说明答案太过于优秀,没有办法达到。

一个很界外的东西#

XJ 模拟赛遇到的。问题是:有 n 个人打架,给定一个概率 p,如果 i<j,那么 i 打赢 j 的概率是 pj 打赢 i 的概率是 1p。对于 i[1,n),求能找出一个大小为 i 的子集,使得里面的任意一个人都打赢了子集外的每一个人的概率。

容易想到令 dpi,j 表示 i 个人,其中 j 个人在集合中的概率。不难得到转移:dpi,j=dpi1,j×pj+dpi1,j1×(1p)ij

好消息是这个东西最优只能做到 O(n2),但是这题要求 O(n)。怎么办?

然后考虑这个真的有必要么,直接说当前是连续的 i 个人,然后里面可以选出 j 个人在集合中。这样我们除了可以在最后面加一个编号最大的人之外,还可以在最前面加一格编号最小的人,即:dpi,j=dpi1,j×(1p)j+dpi1,j1×pij

变魔术: 我们把这两个式子联立起来!得到:

dpi1,j×pj+dpi1,j1×(1p)ij=dpi1,j×(1p)j+dpi1,j1×pij

然后把 i1i,得到:

dpi,j×pj+dpi,j1×(1p)i+1j=dpi,j×(1p)j+dpi,j1×pi+1j

接着移项,合并,除过去得到:

dpi,j=dpi,j1×pi+1j(1p)i+1jpj(1p)j

然后就把 i 这一维去掉了!你不界外吗?

总结: 如果遇到一个题,每一个元素的地位等价性很高,而当前的 dp 式子不好优化的时候,可以考虑多考虑几种转移,使得某一维可以被抵消掉。

势能分析的一种套路#

感觉经常遇到这种套路。

就是把大于等于某个阈值的数都减去这个阈值。我们假设它为 c。那么我们把所有数分成 [0,c),[c,2c),[2c,+) 三部分,然后对左边不用处理,对最右边用数据结构维护一下,然后中间的可以暴力做。

分析方法是考虑中间那部分被减后数值必然不多于原来的一半,所以最多暴力 logc 次。

扫描线+DS#

具体见博客

时间倒流#

如果一个题,常见于图论,顺序加边的时候很难保证复杂度(或者删边,总是正向做)或者根本找不到一个多项式复杂度的做法,那么就可以考虑倒过来做,前提是可以离线。

矩阵加速分段优化#

例题

如果题目需要做分段矩乘,然后发现时间比较紧张,那么可以考虑预处理转移矩阵的二次幂,然后直接用 dp 向量来乘。然后在矩阵乘法的时候要注意如果某一次乘法肯定没有意义,那就直接省去一次循环,这样矩阵乘向量的复杂度是 O(siz2) 的。

相当于平衡掉分多段的复杂度。

点权 Kruskal 重构树#

目的相同,想要建立一棵在无关于长度,只有关于可达性的树的时候,想建立一棵有单调性的树。可是是点权的时候,没法用 Kruskal 的排序来做。

但是我们可以直接把点按点权排序,从点权优到劣排。然后开一个 vis[i] 数组,顺次枚举每个点,并枚举与它相连的所有点,然后如果某一个点的 vis[i]1,说明它的点权比当前点小,那么在用并查集判断完两个点的连通性之后,直接把当前点和那个点的根连一条边。最后就建出一棵 n 个节点的,满足普通重构树所有性质的树。

示例代码
iota(f+1,f+1+n,1);
iota(p+1,p+1+n,1);
sort(p+1,p+1+n,[&](int x,int y){return C[x]<C[y];});
rep(i,1,n){
    for(int s:g[p[i]]){
        int sf=find(s);
        if(vis[sf]&&merge(sf,p[i]))
            e[p[i]].pb(sf);
    }vis[p[i]]=1;
}
posted @   ZCETHAN  阅读(1942)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示
主题色彩