tricks

先警示一下

认真阅读题目(有的题目中包含部分解题信息)

注意预处理离线之类。


简单的差分思想有时优化显著。

对于序列区间操作问题,尝试将其通过前缀和差分转化为单点操作,对复杂度/推式子/再次优化有很大帮助。


异或哈希前缀和

如果不是这道题,甚至不知道这个

作用:判断区间内某个值是否出现偶数次。

原理\(a\bigoplus a=0\)

求异或前缀和,若 \(sum_{l-1}=sum_r\) ,则 \(col_r\) 出现偶数次。

(利用随机数赋值实现哈希效果,用mt19937_64, rand 太弱)

mt19937_64 rnd(time(0))


线段树叶子结点非空子集数为 \(2^n-1\)


网络流:

拆点建虚点,考虑inf的不同含义。

割表示选择或舍弃。

建图时借鉴之前思路,但不要局限于固有思维


判断质因数种类是否相同:

点击查看代码
for (int j = 1; j <= tot; ++j)
        for (int i = pri[j]; i <= n; i += pri[j])
        {
            e[i].push_back(j);
            ++cnt[j];
        }

之后map套vector判断


0/1矩阵,即邻接矩阵可转化成图论。

邻接矩阵的幂次表示路径的长度,即步数或跳数。

具体来说,一个图的邻接矩阵 \(A\)\(k\) 次幂中的元素 \(a_{i,j}\) 表示顶点 \(i\) 和顶点 \(j\) 之间长度为 \(k\) 的路径的数量。


若原问题没思路可以先考虑其弱化版。


实数域上二分时加减eps (变化量不是1)

example
ldb l = 0, r = 10000000, ans = r;
    while (r - l > eps)
    {
        ldb mid = (l + r) / 2.0;
        if (check(mid))
            r = mid - eps, ans = mid;
        else
            l = mid + eps;
    }

trie 空间复杂度为 \(\sum len\)


线性阶乘逆元倒推\(O(n)\)

example
inline void init()
{
    jc[0] = inv[0] = 1;
    for (int i = 1; i <= n; ++i)
        jc[i] = jc[i - 1] * i % mod;
    inv[n] = km(jc[n], mod - 2);
    for (int i = n - 1; i; --i)
        inv[i] = inv[i + 1] * (i + 1) % mod;
}

\(\vee\)为或,即 |,\(\land\)为与 ,即 &。

\(+\) : $a + b=a \vee b + a\land b $

\(\oplus\) : $a \oplus b=a \vee b - a\land b $

\(\therefore c+d=a + b , c-d=a \oplus b ,d \subseteq c\)


合法括号串:'('看作 1,')'看作 -1,前缀和为0且对于 \(l \le i \le r\) 都有 \(sum_r \le sum_i\) 即合法。


多个串的border相关问题:
建fail树,树上做(若最长公共border长为lca)


关于树的重心:
性质:\(dfn_i\)\(dfn_{(i+\frac{n}{2}-1)\%n+1}\) 一定不在同一连通块内(删重心后)
考虑该点是重心,则显然成立。
不是重心,则重心的祖先结点不超过\(\frac{n}{2}\)个,\(i+\frac{n}{2}\)一定不与 \(i\) 在同一连通块内


如果两条路径相交,那么一定有一条路径的LCA在另一条路径上


01序列排序:\(O(\log n)\)
线段树求区间和,区间推平


曼哈顿距离与切比雪夫距离转化


枚举二进制子集

点击查看代码
for(int j=(i-1)&i;j;j=(j-1)&i)//j是i二进制表示下的所有真子集的集合
    cout<<j<<<' ';

对于string的赋值:

可以使用s[i]='*'的形式单点修改,也可写s+=t在后面连接。

特别注意,第一种形式要提前开够空间(它不会帮你动态分配空间)

#include <bits/stdc++.h>
using namespace std;

signed main()
{
    string s;
    s[0] = 'o';
    cout << s << "\n";
    return 0;
}

输出空串。

原因是空间不够

应在赋值前开够手动空间。

改为

#include <bits/stdc++.h>
using namespace std;

signed main()
{
    string s;
    s.resize(10);
    s[0] = 'o';
    cout << s << "\n";
    return 0;
}

即可正常输出。

若使用字符串拼接的方式修改(即s+=t)则无需担心空间。

之前好多次因为这个出错,所以以为string单修不对,今天终于揭秘了,原来是空间的问题。


关于滚动数组

好像之前写假过。


线段树空间应开到\(2^{\left \lceil \log_2n \right \rceil}\).

动态开点线段树建议开到\(2\left \lceil \log_2n \right \rceil\)倍大小。


这道题记录一下。

本来想直接整体二分,结果就是TLE+MLE。

考虑类似扫描线思想:

我们对时间轴扫描,在时间上依次加入或删除。

之后线段树上二分求前缀或和

即可轻松解决(卡空间,线段树只能开2倍)


\(n\) 以内的素数为 \(\frac{n}{\ln(n)}\)级别,可优化复杂度。


推式子时有模,可考虑模的定义来拆模:

\(x \mod y=x-\lfloor\frac{x}{y}\rfloor y\)


有的式子中的位运算可拆位求,即拆为二进制每一位如何操作(找不到哪到题了)。


并查集维护序列段。

P1840
附题解

posted @ 2025-08-20 22:06  HS_fu3  阅读(22)  评论(0)    收藏  举报