trick or 坑点记录(技巧性思维)

  1. 概览:用递归方便处理括号类问题。

    在解决括号序列的展开(内部,比如 (ABA) 之类的)问题时可以考虑用 dfs 来做,好写又不容易错。注意可以用手写栈预处理出相邻对应括号之间的关系

  2. 概览:括号序列合法的另一个等价条件是,对于所有前缀,左括号不比右括号少,并且最后出现次数相同。

  3. 概览:括号类问题中左括号减右括号数非负。

    由 2 我们可以想到,在设计 dp 状态时,可以记录左括号减右括号数这一个值,因为它是非负的。这样就方便转移了。

  4. 概览:DP 初始化赋为极大值与极小值。

    在 dp 时,若要进行 \(\max\) 操作,最好把 dp 数组赋值为 \(- \infty\)。因为赋值成 \(0\)\(-1\),那么可能存在一些情况,不合法但因为某些 dp 值是 \(0\)\(-1\)(影响很小)解出错误的答案。

  5. 概览:路径信息拆成两条链的信息,在树论中大量使用。

    在解决一些关于树上简单路径的题时,可以考虑两个点 \(x\)\(y\),然后考虑维护 \(x\)\(\text{LCA(x,y)}\)\(\text{LCA(x,y)}\)\(y\) 两条链的信息。这种方法在 GESP 中很适用。

  6. 在跑负环的时候,尤其是在求最优比率环的时候要注意,有的时候为了提高程序效率,不必等到 cnt[y](入队次数)\(> n\) 时才 return 1;,可以提前返回 true。这有点像卡时技巧

  7. 概括:前后缀合并解决问题。

    如果在 dp 中遇到一些比较容易用前缀后缀拼起来的答案但很难直接一次 dp 搞定的问题,不妨分两次 dp,一次 dp 做前缀,另一次 dp 做后缀,这样思路会清晰很多。

  8. 概括:bitset 优化。

  9. 概括:正难则反

    1. 图论删边倒序做变成加边,用并查集做。
  10. 概括:多个数取 \(\max\)

    在取多个数 \(\max\),可以这样操作:max({a,b,c})

  11. 概括:DP 大致维度的确定。

    dp 的状态设计可以根据数据范围来确定开几维。

  12. 概括:\(\text{mex}\) 与值域无关。

    一个长度为 \(n\) 的序列 \(a\)\(\text{mex}\) 值在 \([0,n+1]\) 之间,而与 \(a_i\) 的值域 \(V\) 无关。

  13. 概括:\(\log\) 的性质。

    \(\log\) 可以将乘法换成加法,在差分约束系统有显著效果。例题:P4926 倍杀测量者

  14. 概括:\(\min\) 的处理。通常用单调栈或者笛卡尔树求出每个数作为最小值的区间。

  15. 概括:\(\text{med}\)(中位数)的处理。对于一个想成为中位数的 \(k\),则 \(\ge k\) 的数至少占序列的一半。将 \(< k\) 的数记作 \(-1\),将 \(\ge k\) 的数记作 \(1\),那么中位数至少为 \(k\) 的条件就是这些标记的和 \(\ge 0\)

  16. 概括:在可以差分的情况下,\([l,r]\) 可以拆成 \([1,r] - [l-1,r]\)\(1\) 为左端点,视情况而定)。

  17. 概括:一个小小的容斥思路。恰好为 \(L\) 的方案数可以拆成 \(\le L\) 的方案数减去 \(\le L-1\) 的方案数。

  18. 概括:方格格子处理。遇到一些方格状物的图形而又不好进行格与格之间的动态规划时,可以考虑把一个格映射成一个点,具体可见 P4667

  19. 概括:奇数位异或 \(1\)。在一些题目(考虑奇偶性,但是正着做难做)中,可以考虑使用奇数位异或 \(1\) 的想法,有时可以使解题难度降低,例题 礼物

  20. 冒泡排序性质(适于计数):记 \(b_i = \sum_{j<i} [a_j > a_i]\)。则冒泡排序的轮数为 \(\max_{i=1}^{n} b_i\)

    考虑如何证明这件事情。

    • 至多有一个 \(j\),使得 \(a_j\) 在本轮中被扔到 \(a_i\) 后面。设有两个位置为 \(j_1,j_2\),那么若 \(a_{j_1} > a_{j_2}\),那么 \(j_1\) 可以一直移动,\(j_2\) 因为 \(a_{j_1} > a_{j_2}\) 而被固定住了。否则 \(a_{j_1} \le a_{j_2}\),那么 \(j_1\) 无论如何都换不到 \(j_2\) 右边。证毕。(可以画图辅助理解)
    • 至少有一个 \(j\),使得 \(a_j\) 在本轮中被扔到 \(a_i\) 后面。考虑取前缀最大值,一定 \(> a_i\),那么比它小的 \(j\) 都会失效。
    • 综上,每轮,对每一个 \(i\) 来说,都会有恰好一个 \(j\),使得 \(a_j\) 被冒泡至 \(i\) 右边。

    不难发现此时的轮数就是 \(\max b_i\)

    题型运用:P14352 排序。

  21. 原图加上若干条边的 MST (加入的边都有用)一定不会用到原图的非 MST 边。(总觉得这条 trick 非常显然)运用:CSP-S 2025 T2。证明:Kruskal。

  22. wyp 学长:观察最终答案形态。

  23. 序列局部排序问题的常见解决方法是二分答案转 01 序列处理

  24. 概览\(\min (p,q) + |p-q| = \max (p,q)\)

  25. 概览:边权转点权。
    在树上的边(记为 \(u \rightarrow v\),其中 \(u\)\(v\) 的父亲)的边权可以挂到点权上处理。例题:P4315 月下“毛景树”。

  26. 概览:如果问题没有突破口,可以考虑枚举。例如 NOIP 2025 T1。

  27. 经典结论:

    对于函数

    \[f(n)=\oplus_{i=1}^ni \]

    我们有

    \[f(n) = \left\{\begin{aligned} n \space (n \equiv 0 \pmod 4) \\ 1 \space (n \equiv 1 \pmod 4) \\n+1 \space (n \equiv 2 \pmod 4) \\0 \space (n \equiv 3\pmod 4) \end{aligned}\right. \]

  28. 求出一组解再进行调整法。

    应用:P3513。

  29. 概览:单谷函数的充分条件。

    函数 \(f(x)\) 的导函数 \(f'(x)\) 如果单调不减 \(\implies\) \(f(x)\) 为单谷函数,这里默认一次函数是单谷函数。HHC 大神:一次函数在一段区间内的最小值可以看作是“谷”。

  30. 次大值等价于去掉最大值后的最大值。例子:U681932 second

  31. 构造题中好用的结论:在网格中确定行、列最大值考虑从大到小填数。

  32. 来自 li_cat:关于 2-SAT 的提醒:dfs 序的反序是拓扑序,仅针对 dfs 的完成序,dfs 的进入序拓扑序没有很好的关联

  33. 概括:用更少的信息简化 DP 状态。DP 若有多个信息需要维护,观察是否能通过更少的信息表达所有信息。这样可优化空间复杂度。

    例子:

    • P16687。
  34. DP 中的变换维度,当一个维度有后效性时考察其它维度的无后效性。

    例子:

    • P16686。
  35. 图论建模中的 \(|i-j|\) 形如这样的绝对值想到拆点之间的距离

  36. \(a \le b \le c\) 或者形如 \(b\) 在中间的又只能枚举一个,优先考虑枚举 \(b\)

  37. \(1\le a \le b \le c \le n\) 的计数。考虑映射 \(a'=a\)\(b'=b+1\)\(c'=c+2\),则 \(1\le a' < b' < c' \le n+2\),答案即为 \(\binom{n+2}{3}\),推广至 \(k\) 个中间元素,答案为 \(\binom{n+k-1}{k}\)

  38. 如果这样用倍增写 LCA:

    int lca(int x,int y){
    	if(d[x]<d[y])swap(x,y);
    	for(int i=20;i>=0;--i){
    		if(d[anc[x][i]]>=d[y])x=anc[x][i];//注意这里,若 dep[root]=dep[0]=0,会导致求一个任意的点和根节点的 LCA 时跳过去。
    	}
    	if(x==y)return x;
    	for(int i=20;i>=0;--i){
    		if(anc[x][i]!=anc[y][i]){
    			x=anc[x][i];
    			y=anc[y][i];
    		}
    	}
    	return anc[x][0];
    }
    
    
  39. 求 LCA 新做法\(dfs\) 序。核心思路:先分类讨论,再归一化处理,将求 \(u\)\(v\) 的 LCA 转化为求 \([dfn_u + 1,dfn_v]\) 深度最小的节点的父亲。用 ST 表来实现。实测跑得比倍增快的多。

    注意这里为了节省空间,我们甚至不需要记录一个点的父亲,直接 st[0][dfn[x]]=fa; 解决。

    好文推荐:this

  40. 概括:树的直径性质。

    好文推荐:this

    多条树的直径一定交于一点,且这个点是直径的中点。证明考虑反证法。

    • 假设两条树的直径不相交,那么如图所示

      所以树的直径是相交的。
    • 再来证明树的直径交于中点。分两步说明。

  41. (施工中)基环树思路。

  42. (施工中)AC 自动机题的思路。

    • 以自动机上的 \(\text{trie}\) 边作为 DP 转移的阶段。例:\(dp[i][j]\) 表示串长为 \(i\),当前在点 \(j\) 的 xxx。
  43. 一棵树进行 dfs 时,对遍历到到的点打 \(1\) 标记,回溯过的点再打 \(-1\) 标记,这样标记为 \(1\) 的点,组成根节点到当前点的一条链(有效路径)。例题:P2414 [NOI2011] 阿狸的打字机

  44. 高斯约旦消元无解或无穷解判定

  45. 快读注意事项:

    ::::warning[code]

    inline int read(){
    	int x=0;
    	int f=1;
    	char c=getchar();
    	while(c<'0' || c>'9'){
    		if(c=='-')f=-1;
    		c=getchar();
    	}
    	while(c>='0' && /* 这里是&& 不是 || */ c<='9'){
    		x=(x<<1)+(x<<3)+(c^48);
    		c=getchar();
    	}
    	return x*f;
    }
    

    ::::

  46. 线段树分治中,我采取的是这种写法:(P5787)

    ::::info[code]

      void dfs(int x,int l,int r,bool flag){
      	int lst=top;
      	for(auto g:tr[x]){
      		int u=g.u,v=g.v;
      		if(find(u)==find(v))flag=0;
      		merge(u,v+n);
      		merge(v,u+n); 
      	}
      	if(l==r){
      		if(flag)ans[l]=1;
      		else ans[l]=0;
      		while(top>lst){
      			auto p=st[top--];
      			fa[p.v]=p.v;
      			sz[p.u]=p.sz;
      		}
      		return ;
      	}
      	int mid=(l+r)>>1;
      	dfs(ls(x),l,mid,flag);
      	dfs(rs(x),mid+1,r,flag);
      	while(top>lst){
      		auto p=st[top--];
      		fa[p.v]=p.v;
      		sz[p.u]=p.sz;
      	}
      }
    

    ::::

    高亮处我对叶子节点进行了撤销操作,事实上,这里如果忘记写了,虽然被父节点通过 lst 撤销。还是会被 Hack! 因为它是先序遍历。

  47. 重链剖分不能写假!

    如下面代码片段所示:

    for (int y : e[x]) {
        if (y != wc[x]) {//这句话一定要写,否则链长为1,lca 时间退化为暴力求解
            dfs2(y, y);
        }
    }
    
  48. 关于最短路图两个结论:

    • 一条最短路径 \(S \to T\) 中的一条子路径 \(s \to t\) 也是一条最短路,若否,可以找到一条更短的 \(s \to t\) 的路径替换,\(S \to T\) 的最短路径变小,矛盾。
    • 最短路图上不存在环。若存在环 \(\color{blue} u_1 \to u_2 \cdots \to u_t \to u_1\)。那么 \(dis[u_2] = dis[u_1]+w(u_1,u_2) > dis[u_1]\),以此类推,\(dis[u_t] > dis[u_1]\)\(dis[u_1] > dis[u_t]\),故矛盾。
    • 均匀随机标号树(Prüfer 模型):期望高度 \(O(\sqrt n)\)
    • 均匀随机二叉搜索树 / 递归随机树:期望高度 \(O(\log n)\),儿子数 \(O(\log n)\)
  49. 加法恒等式 \(a+b = a \oplus b + 2(a \mathbin{\&} b)\)。这是因为异或是不进位加法,而 & 操作保留了进位,但是要进行左移,因为进位产生的效果在下一位,故而乘 \(2\)

  50. 两个有序数组(假定从小到大)\(a\)\(b\),长度均为 \(n\),问题是找一组匹配 \((a_i,b_j)\),使得 \(\sum |a_i - b_j|\) 最小(求和符号里面的东西可以是只跟 \(|a_i - b_j|\) 有关的其它式子)。

    我们猜测直接 \(a_i\)\(b_i\) 配对是最优的。下面使用邻项交换法给出证明:

    Case 1:

    Case 2:

    Case 3:

    Case 4:

  51. 多测时若使用 Tarjan 求强连通分量、ecc 或 vcc,要注意清空 dfn 和 low。low 的清空不是必要的,但若 dfn 未清空,会导致判断 !dfn[y] 时会出现错误。

  52. 区间 \(0/1\) 反转状态想到:

    • 线段树
    • 异或数组 \(d_i = a_i \oplus a_{i-1}\)
  53. 写倍增时,如果用到类似下面的代码片段:

for(int i=18;i>=0;--i){
  	if((p>>i)&1){
  		x=nxt[i][x][sx];
  		sx=(sx+(1<<i))%k;
  	}
  }

一定要小心,这里是固定了一个二进制最高位,若 \(p\) 比较大。而 ST 表(或类倍增过程)的第一行循环写 (1<<j)<=n\(n\) 的量级比 \(p\) 的低,就会访问出界,建议把两者进行统一。

  1. 生成树森林满足 \(V-E = C\),其中 \(C\) 为联通块数。因为一棵树满足 \(V-E = 1\)\(V-E = \sum_{i=1}^C V_i - E_i = C\)

  2. 一棵树的导出子图是生成树森林。

  3. 几种 STL 容器进行 swap 的时间复杂度:

    • vector:\(O(1)\)(交换内部指针、容量、大小)
    • deque:\(O(1)\)
    • list / forward_list:\(O(1)\)
    • map/set:\(O(1)\)(仅交换根指针)
    • array:\(O(n)\)(必须逐元素交换,固定大小无法只换指针)
  4. 在无向图中写 dfs(有环)时注意:

        vis[x]=1;
        for(auto G:e[x]){
            int y=G.v,id=G.id;
            if(id==pre)continue;
            if(!vis[y]){
              // do something here
            }else if(dep[y]<dep[x]){
              //这个 dep[y]<dep[x] 一定要判!
            }
        }
    

    原因:造一个环是 1 2,2 3,3 4,4 1。从 \(1\) 开始 dfs,搜索到 \(4\),此时所有点都被打上了 vis 标记,那么访问 \(1\) 的另一个连着的点 \(4\) 时,由于 vis[4]=1,会走下面的分支。因此要判深度关系。

  5. 两个圈做边集的异或,会形成若干个圈。这是由点的度数异或后仍为偶数决定的。

  6. 判定两个集合 \(S = S'\) 方法:

    • 随机化异或。
    • 若知道 \(S \subseteq S'\)\(S \supseteq S'\),只需判定 \(|S| = |S'|\)
posted @ 2026-08-07 11:12  lbh666  阅读(9)  评论(0)    收藏  举报