ACM-ICPC/CCPC/JSCPC板子总结(不断更新ing)

ACM-ICPC/CCPC/JSCPC板子

Team: Three alchemists(三个炼金师)
@zhoubo,18CS,Suzhou University of Science and Technology
@huangyangbang,20ICS,Suzhou University of Science and Technology
@shiweichun,20CS,Suzhou University of Science and Technology

一、数论

1.试除法判定质数
bool is_prime(int x)
{
    if (x < 2) return false;
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
            return false;
    return true;
}
2.试除法分解质因数
void divide(int x)
{
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
        {
            int s = 0;
            while (x % i == 0) x /= i, s ++ ;
            cout << i << ' ' << s << endl;
        }
    if (x > 1) cout << x << ' ' << 1 << endl;
    cout << endl;
}
3.朴素筛法求素数
int primes[N], cnt;     // primes[]存储所有素数
bool st[N];         // st[x]存储x是否被筛掉
void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (st[i]) continue;
        primes[cnt ++ ] = i;
        for (int j = i + i; j <= n; j += i)
            st[j] = true;
    }
}
4.线性筛求素数
int primes[N], cnt;     // primes[]存储所有素数
bool st[N];         // st[x]存储x是否被筛掉
void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}
5.试除法求所有约数
vector get_divisors(int x)
{
    vector res;
    for (int i = 1; i <= x / i; i ++ )
        if (x % i == 0)
        {
            res.push_back(i);
            if (i != x / i) res.push_back(x / i);
        }
    sort(res.begin(), res.end());
    return res;
}
6.约数个数和约数之和
如果 N = p1^c1 * p2^c2 * ... *pk^ck
约数个数: (c1 + 1) * (c2 + 1) * ... * (ck + 1)
约数之和: (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)
7.欧几里德算法
int gcd(int a, int b) {return b ? gcd(b, a % b) : a;}
8.求欧拉函数

欧 拉 函 数 公 式 : ϕ ( x ) = x ∏ i = 1 n ( 1 − 1 p i ) 欧拉函数公式:\phi(x) = x\prod_{i=1}^n(1 - \frac{1}{pi}) ϕ(x)=xi=1n(1pi1)

int phi(int x)
{
    int res = x;
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
        {
            res = res / i * (i - 1);
            while (x % i == 0) x /= i;
        }
    if (x > 1) res = res / x * (x - 1);

    return res;
}
9.线性筛求欧拉函数
int primes[N], cnt;     // primes[]存储所有素数
int euler[N];           // 存储每个数的欧拉函数
bool st[N];         // st[x]存储x是否被筛掉
void get_eulers(int n)
{
    euler[1] = 1;
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            euler[i] = i - 1; 
        }
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            int t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0)
            {
                euler[t] = euler[i] * primes[j];
                break;
            }
            euler[t] = euler[i] * (primes[j] - 1);
        }
    }
}
10.快速幂
//求a^k mod p
typedef long long LL;
int qmi(int a, int k, int p)
{
    int res = 1 % p;
    while(k)
    {	
        if(k & 1) res = (LL)res * a % p;
        k >>= 1;
        a = (LL)a * a % p;
    }
    return res;
}c
11.费马小定理

如 果 p 是 一 个 质 数 , 而 整 数 a 不 是 p 的 倍 数 , 则 有 a p − 1 ≡ 1 ( m o d m ) 如果p是一个质数,而整数a不是p的倍数,则有a^{p-1}\equiv1\pmod{m} papap11(modm)

12.逆元

b ∗ x ≡ 1 ( m o d p ) b 存 在 乘 法 逆 元 的 充 要 条 件 是 b 与 模 数 p 互 质 。 当 模 数 m 为 质 数 时 , b p − 2 即 为 b 的 乘 法 逆 元 b 是 p 的 倍 数 的 时 候 , 显 然 这 个 式 子 无 解 b*x\equiv1\pmod{p}\\ b存在乘法逆元的充要条件是b与模数p互质。当模数m为质数时,b^{p- 2}即为b的乘法逆元\\ b是p的倍数的时候,显然这个式子无解 bx1(modp)bbpmbp2bbp

13.裴蜀定理

对 于 任 意 正 整 数 a , b , 一 定 存 在 非 零 整 数 x , y , 使 得 a x + b y = g c d ( a , b ) 对于任意正整数a,b,一定存在非零整数x,y,使得ax+by=gcd(a,b) a,b,x,y,使ax+by=gcd(a,b)

14.扩展欧几里德
//求x,y使得ax + by = gcd(a,b)
int exgcd(int a, int b, int &x, int &y)
{
    if(!b)
    {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= (a / b) * x;
   return d;
}

可 用 来 求 线 性 同 余 方 程 a x ≡ b ( m o d m ) 可用来求线性同余方程ax\equiv{b}\pmod{m} 线axb(modm)

#include
using namespace std;
typedef long long LL;
int exgcd(int a, int b, int &x, int &y) {
   if(!b) {
       x = 1, y = 0;
       return a;
   }
   int d = exgcd(b, a % b, y, x);
   y -= a / b * x;
   return d;
}
int main() {
    int n;
    scanf("%d", &n);
    while(n --) {
        int a, b, m;
        scanf("%d%d%d", &a, &b, &m);
        int x, y;
        int d = exgcd(a, m, x, y);
        if(b % d) puts("impossible");
        else printf("%d\n", (LL) x * (b / d) % m);
    }
    return 0;
}
16.高斯消元
// a[N][N]是增广矩阵
int gauss()
{
    int c, r;
    for (c = 0, r = 0; c < n; c ++ )
    {
        int t = r;
        for (int i = r; i < n; i ++ )   // 找到绝对值最大的行
            if (fabs(a[i][c]) > fabs(a[t][c]))
                t = i;
        if (fabs(a[t][c]) < eps) continue;
        for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]);      // 将绝对值最大的行换到最顶端
        for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];      // 将当前上的首位变成1
        for (int i = r + 1; i < n; i ++ )       // 用当前行将下面所有的列消成0
            if (fabs(a[i][c]) > eps)
                for (int j = n; j >= c; j -- )
                    a[i][j] -= a[r][j] * a[i][c];
        r ++ ;
    }
    if (r < n)
    {
        for (int i = r; i < n; i ++ )
            if (fabs(a[i][n]) > eps)
                return 2; // 无解
        return 1; // 有无穷多组解
    }
    for (int i = n - 1; i >= 0; i -- )
        for (int j = i + 1; j < n; j ++ )
            a[i][n] -= a[i][j] * a[j][n];
    return 0; // 有唯一解
}

二、组合数学

1.递归法求组合数

n 组 询 问 , 每 组 询 问 包 括 一 组 a 和 b , 1 ≤ n ≤ 10000 , 1 ≤ b ≤ a ≤ 2000 O ( n 2 ) n组询问,每组询问包括一组a和b,1\leq{n}\leq{10000},1\leq{b}\leq{a}\leq{2000}\quad O(n^2) nab,1n10000,1ba2000O(n2)

const int mod = 1e9 + 7;
// c[a][b] 表示从a个苹果中选b个的方案数
for (int i = 0; i < N; i ++ )
    for (int j = 0; j <= i; j ++ )
        if (!j) c[i][j] = 1;
        else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
2.通过预处理逆元的方式求组合数

n 组 询 问 , 每 组 询 问 包 括 一 组 a 和 b , 1 ≤ n ≤ 10000 , 1 ≤ b ≤ a ≤ 1 0 5 O ( n l o g n ) n组询问,每组询问包括一组a和b,1\leq{n}\leq{10000},1\leq{b}\leq{a}\leq{10^5}\quad O(nlogn) nab,1n10000,1ba105O(nlogn)

首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N]
如果取模的数是质数,可以用费马小定理求逆元
int qmi(int a, int k, int p)    // 快速幂模板
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

// 预处理阶乘的余数和阶乘逆元的余数
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++ )
{
    fact[i] = (LL)fact[i - 1] * i % mod;
    infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}
3.Lucas定理求组合数

n 组 询 问 , 每 组 询 问 包 括 一 组 a 和 b , 1 ≤ b ≤ a ≤ 1 0 18 , 1 ≤ p ≤ 1 0 5 log ⁡ N p ∗ p ∗ log ⁡ p C a b ≡ C a   m o d   p b   m o d   p ∗ C a / p b / p ( m o d p ) n组询问, 每组询问包括一组a和b,1\leq{b}\leq{a}\leq10^{18},1\leq{p}\leq10^5\quad \log_N^p*p*\log{p} \\C^b_a\equiv C^{b\,mod\,p}_{a\,mod\,p}*C^{b/p}_{a/p}\pmod{p} nab,1ba1018,1p105logNpplogpCabCamodpbmodpCa/pb/p(modp)

若p是质数,则对于任意整数 1 <= m <= n,有:
    C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)

int qmi(int a, int k)       // 快速幂模板
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}
int C(int a, int b)     // 通过定理求组合数C(a, b)
{
    int res = 1;
    for (int i = 1, j = a; i <= b; i ++, j -- )
    {
        res = (LL)res * j % p;
        res = (LL)res * qmi(i, p - 2) % p;
    }
    return res;
}
int lucas(LL a, LL b)
{
    if (a < p && b < p) return C(a, b);
    return (LL)C(a % p, b % p) * lucas(a / p, b / p) % p;
}
4.分解质因数求组合数

求 组 合 数 的 真 实 值 , 而 非 m o d 一 个 数 的 值 时 使 用 , 需 要 用 到 高 精 度 求组合数的真实值,而非mod一个数的值时使用,需要用到高精度 mod使

当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用:
    1. 筛法求出范围内的所有质数
    2. 通过 C(a, b) = a! / b! / (a - b)! 这个公式求出每个质因子的次数。 n! 中p的次数是 n / p + n / p^2 + n / p^3 + ...
    3. 用高精度乘法将所有质因子相乘

int primes[N], cnt;     // 存储所有质数
int sum[N];     // 存储每个质数的次数
bool st[N];     // 存储每个数是否已被筛掉
void get_primes(int n)      // 线性筛法求素数
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}
int get(int n, int p)       // 求n!中的次数
{
    int res = 0;
    while (n)
    {
        res += n / p;
        n /= p;
    }
    return res;
}
vector mul(vector a, int b)       // 高精度乘低精度模板
{
    vector c;
    int t = 0;
    for (int i = 0; i < a.size(); i ++ )
    {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }

    while (t)
    {
        c.push_back(t % 10);
        t /= 10;
    }

    return c;
}
get_primes(a);  // 预处理范围内的所有质数
for (int i = 0; i < cnt; i ++ )     // 求每个质因数的次数
{
    int p = primes[i];
    sum[i] = get(a, p) - get(b, p) - get(a - b, p);
}
vector res;
res.push_back(1);
for (int i = 0; i < cnt; i ++ )     // 用高精度乘法将所有质因子相乘
    for (int j = 0; j < sum[i]; j ++ )
        res = mul(res, primes[i]);
5.卡特兰数

给 定 n 个 0 和 n 个 1 , 它 们 将 按 照 某 种 顺 序 排 成 长 度 为 2 n 的 序 列 , 求 它 们 能 排 列 成 的 所 有 序 列 中 , 能 够 满 足 任 意 前 缀 序 列 中 0 的 个 数 都 不 少 于 1 的 个 数 的 序 列 有 多 少 个 。 输 出 的 答 案 对 1 0 9 + 7 取 模 , 1 ≤ n ≤ 10 5 。 C a t ( n ) = C ( 2 n , n ) / ( n + 1 ) 给定 n 个0和n个1,它们将按照某种顺序排成长度为2n的序列,求它们能排列成的所有序列中,\\能够满足任意前缀序列中0的个数都不少于1的个数的序列有多少个。输出的答案对10^9+7取模,1\leq{n}\leq{10}^5。\\Cat(n) = C(2n,n)/(n+1) n0n12n01109+7,1n105Cat(n)=C(2n,n)/(n+1)

#include
using namespace std;
const int mod = 1e9 + 7;
typedef long long LL;
int qmi(int a, int k, int p)
{
    int res = 1;
    while(k)
    {
        if(k & 1) res = (LL) res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}
int main() 
{
    int n;
    cin >> n;
    int a = 2 * n, b = n;
    int res = 1;
    for(int i = a; i > a - b; i --) res = (LL)res * i % mod;
    for(int i = 1; i <= b; i ++) res = (LL)res * qmi(i, mod - 2, mod) % mod;
    res = (LL)res * qmi(n + 1, mod - 2, mod) % mod;
    cout << res << endl; 
    return 0;
}
6.容斥原理

公 式 1 : ∣ S 1 ∪ S 2 ∪ S 3 ∣ = ∣ S 1 ∣ + ∣ S 2 ∣ + ∣ S 3 ∣ − ∣ S 1 ∩ S 2 ∣ − ∣ S 1 ∩ S 3 ∣ − ∣ S 2 ∩ S 3 ∣ + ∣ S 1 ∩ S 2 ∩ S 3 ∣ 公 式 2 : ∣ S 1 ∪ S 2 ∪ S 3 ∪ S 4 ∣ = ∣ S 1 ∣ + ∣ S 2 ∣ + ∣ S 3 ∣ + ∣ S 4 ∣ − ∣ S 1 ∩ S 2 ∣ − ∣ S 1 ∩ S 3 ∣ − ∣ S 1 ∩ S 4 ∣ − ∣ S 2 ∩ S 3 ∣ − ∣ S 2 ∩ S 4 ∣ − ∣ S 3 ∩ S 4 ∣ + ∣ S 1 ∩ S 2 ∩ S 3 ∣ + ∣ S 1 ∩ S 2 ∩ S 4 ∣ + ∣ S 1 ∩ S 3 ∩ S 4 ∣ + ∣ S 2 ∩ S 3 ∩ S 4 ∣ − ∣ S 1 ∩ S 2 ∩ S 3 ∩ S 4 ∣ 公 式 1 , 公 式 2 − > 一 般 公 式 ∣ S 1 ∪ S 2 ∪ S 3 ∪ . . . ∪ S n ∣ = ∑ i ∣ S i ∣ − ∑ i , j ∣ S i ∩ S j ∣ + ∑ i , j , k ∣ S i ∩ S j ∩ S k ∣ − . . . 公式1:|S_1\cup S_2\cup S_3|=|S_1|+|S_2|+|S_3|-|S_1\cap S_2|-|S_1\cap S_3|-|S_2\cap S_3|+|S_1\cap S_2\cap S_3|\\ 公式2:|S_1\cup S_2\cup S_3\cup S_4|=|S_1|+|S_2|+|S_3|+|S_4|-|S_1\cap S_2|-|S_1\cap S3|-|S1\cap S4|\\-|S_2\cap S3|-|S_2 \cap S_4|-|S_3\cap S_4|+|S_1\cap S_2\cap S3|+|S_1\cap S_2\cap S_4|+|S_1\cap S_3\cap S_4|\\+|S_2\cap S_3\cap S_4| -|S_1\cap S_2 \cap S_3 \cap S_4|\\ 公式1,公式2->一般公式\\|S_1\cup S_2\cup S_3\cup...\cup S_n|=\sum_i|S_i|-\sum_{i,j}|S_i\cap S_j|+\sum_{i,j,k}|S_i\cap S_j\cap S_k|-... 1S1S2S3=S1+S2+S3S1S2S1S3S2S3+S1S2S32S1S2S3S4=S1+S2+S3+S4S1S2S1S3S1S4S2S3S2S4S3S4+S1S2S3+S1S2S4+S1S3S4+S2S3S4S1S2S3S412>S1S2S3...Sn=iSii,jSiSj+i,j,kSiSjSk...

7.博弈论
7.1NIM游戏

给定N堆物品,第i堆物品有Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。我们把这种游戏称为NIM博弈。把游戏过程中面临的状态称为局面。整局游戏第一个行动的称为先手,第二个行动的称为后手。若在某一局面下无论采取何种行动,都会输掉游戏,则称该局面必败。
所谓采取最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都只考虑理想情况,即两人均无失误,都采取最优策略行动时游戏的结果。NIM博弈不存在平局,只有先手必胜和先手必败两种情况。

定理: NIM博弈先手必胜,当且仅当 A1 ^ A2 ^ … ^ An != 0

while(n --)
    {
        int x;
        scanf("%d", &x);
        res ^= x;
    }
    if(res) puts("Yes");
    else puts("No");
7.2Mex运算

设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即:
mex(S) = min{x}, x属于自然数,且x不属于S

7.3SG函数

在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1, y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即:
SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})
特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)

7.4有向图游戏

给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。
任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。

7.5定理

有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0; 有向图游戏的某个局面必败,当且仅当该局面对应的SG函数值等于0.

7.6有向图游戏的和

设G1, G2, …, Gm 是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏Gi,并在Gi上行动一步。G被称为有向图游戏G1, G2, …, Gm的和。
有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和,即:
SG(G) = SG(G1) ^ SG(G2) ^ … ^ SG(Gm)

例:集合-Nim游戏

给定n堆石子以及一个由k个不同正整数构成的数字集合S。现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合SS,最后无法进行操作的人视为失败。问如果两人都采用最优策略,先手是否必胜。如果先手方必胜,则输出“Yes”; 否则, 输出“No”.

#include
using namespace std;
const int N = 110, M = 10010;
int n, m;
int s[N], f[M];
int sg(int x)
{
    if(f[x] != -1) return f[x];
    unordered_set S;
    for(int i = 0; i < m; i ++)
    {
        int sum = s[i];
        if(x >= sum) S.insert(sg(x - sum));
    }
    for(int i = 0; ;i ++)
        if(!S.count(i))
            return f[x] = i;
}
int main()
{
    cin >> m;
    for(int i = 0; i < m; i ++) cin >> s[i];
    cin >> n;
    memset(f, -1, sizeof f);
    int res = 0;
    for(int i = 0; i < n; i++)
    {
        int x;
        cin >> x;
        res ^= sg(x);
    }
    if(res) puts("Yes");
    else puts("No");
    return 0;
}

三、计算几何

1.基础的点、向量、点乘积、叉乘积
#include
using namespace std;
const int INF=0x3f3f3f3f;
typedef long long ll;
struct point //建立点
{
	double x=0,y=0;
};
struct v 
{
	point st,end;
};
double dotProduct(v *v1,v *v2)  //点乘
{
	v q,w;
	double result=0;
	q.st.x=0;
	q.st.y=0;
	q.end.x=v1->end.x-v1->st.x;  //v1->st.x等同于*(v1).st.x
	q.end.y=v1->end.y-v1->st.y;
	w.st.x=0;
	w.st.y=0;
	w.end.x=v2->end.x-v2->st.x;
	w.end.y=v2->end.y-v2->st.y;
	result=q.end.x*w.end.x+q.end.y*w.end.y;
	return result;

}
double crossProduct(v* v1,v* v2)  //叉乘
{
	v q,w;
	double result;
	q.st.x=0;
	q.st.y=0;
	q.end.x=v1->end.x-v1->st.x;
	q.end.y=v1->end.y-v1->st.y;
	w.st.x=0;
	w.st.y=0;
	w.end.x=v2->end.x-v2->st.x;
	w.end.y=v2->end.y-v2->st.y;
	result=q.end.x*w.end.y-w.end.x*q.end.y;
	return result;

}
int main(){
	ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
	double a1,b1,a2,b2;
	cin>>a1>>b1>>a2>>b2;
	v v1,v2;
	v1.end.x=a1;
	v1.end.y=b1;
	v2.end.x=a2;
	v2.end.y=b2;
	cout<

叉 乘 的 一 个 非 常 重 要 的 性 质 是 可 以 通 过 它 的 符 号 来 判 断 两 向 量 相 互 之 间 的 顺 逆 时 针 关 系 : 若 P ⃗ ∗ Q ⃗ > 0 , 则 P 在 Q 的 顺 时 针 方 向 ; 若 P ⃗ ∗ Q ⃗ < 0 , 则 P 在 Q 的 逆 时 针 方 向 ; 若 P ⃗ ∗ Q ⃗ = 0 , 则 P 与 Q 共 线 , 但 也 可 能 反 向 。 叉乘的一个非常重要的性质是可以通过它的符号来判断两向量相互之间的顺逆时针关系:\\ 若\vec{P}*\vec{Q}>0,则P在Q的顺时针方向;\\ 若\vec{P}*\vec{Q}<0,则P在Q的逆时针方向;\\ 若\vec{P}*\vec{Q}=0,则P与Q共线,但也可能反向。 P Q >0,PQ;P Q <0,PQ;P Q =0,PQ线

2.判断点是否在线段上

设 点 Q 及 线 段 P 1 , P 2 , 判 断 点 Q 是 否 在 线 段 P 1 , P 2 上 包 括 两 条 依 据 : ( 1 ) ( Q − P 1 ) ∗ ( P 2 − P 1 ) = 0 ( 2 ) Q 在 以 P 1 , P 2 为 对 角 定 点 的 矩 形 内 前 者 保 证 在 直 线 P 1 , P 2 上 , 后 者 保 证 在 线 段 内 设点Q及线段P_1,P_2,判断点Q是否在线段P_1,P_2上包括两条依据:\\ (1)(Q-P_1)*(P_2-P_1)=0\\ (2)Q在以P_1,P_2为对角定点的矩形内\\ 前者保证在直线P_1,P_2上,后者保证在线段内 Q线P1,P2,Q线P1,P2(1)(QP1)(P2P1)=0(2)QP1,P2线P1,P2线

bool onsegment(point p1,point p2,point q)
{
	if ((q.x-p1.x)*(p2.y-p1.y)==(p2.x-p1.x)*(q.y-p1.y)&&
	min(p1.x,p2.x)<=q.x&&q.x<=max(p1.x,p2.x)&&
	min(p1.y,p2.y)<=q.y&&q.y<=max(p1.y,p2.y))
		return true;
	else
		return false;
}
3.判断点在三角形的内外
struct tri
{
	point a,b,c;
};
bool intri(tri t,point p)
{
	v ab,ac,pa,pb,pc;
	ab.st=t.a;
	ab.end=t.b;
	ac.st=t.a;
	ac.end=t.c; 
	pa.st=p;
	pa.end=t.a;
	pb.st=p;
	pb.end=t.b;
	pc.st=p;
	pc.end=t.c;
	double Sabc=fabs(crossProduct(&ab,&ac));
	double Spab=fabs(crossProduct(&pa,&pb));
	double Spac=fabs(crossProduct(&pc,&pa));
	double Spbc=fabs(crossProduct(&pb,&pc));
	//cout<
4.判断点在多边形的内外
4.1扫描法

从多边形内的点Q引出射线,若射线与多边形没有交点,则点Q在多边形外。若有奇数个交点,则在多边形内。若为偶数点,则在多边形外。

#include
#include
#include
#include
using namespace std;
const int INF=0x3f3f3f3f;
typedef long long ll;
#define eps 1.0e-5
struct point
{
	double x;
	double y;
} po[111];  //几个点,记得改 
ll n,m;
bool online(point p1,point p,point p2)
{
	if( p.x<=max(p1.x,p2.x) && p.x>=min(p1.x,p2.x) && p.y<=max(p1.y,p2.y) && p.y>=min(p1.y,p2.y) )
	{
		if ( fabs(((p.x-p1.x)*(p2.y-p1.y) - (p.y-p1.y)*(p2.x-p1.x)))<=eps )
			return true;
	}
	return false;
}
bool inside(point p)
{
	ll cnt = 0;
	double xinter;
	point p1,p2;
	p1 = po[0];
	for(int i=1; i<=n; i++)
	{
		p2 = po[i%n];
		if( online(p1,p,p2) )	return true;
		if( p.x<=max(p1.x,p2.x)  && p.y<=max(p1.y,p2.y) && p.y>min(p1.y,p2.y) )
		{
			if(p1.y!=p2.y)
			{
				xinter = (p.y-p1.y)*(p1.x-p2.x)/(p1.y-p2.y) + p1.x;
				if(p1.x==p2.x || p.x<=xinter)	cnt++;
			}
		}
		p1 = p2;
	}
	if(cnt%2==0)	return false;
	else			return true;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0); 
	ll kase = 0;
	point p;
	while(cin>>n)
	{
		if (n==0) break;
		cin>>m;
		if(kase)	cout<<"\n";
	
		for(int i=0; i>po[i].x>>po[i].y;
		cout<<"Problem "<<++kase<<":\n";
		for(int i=0; i>p.x>>p.y;
			if( inside(p) )		cout<<"Within\n";
			else				cout<<"Outside\n";
		}
	}
	return 0;
}
4.2叉乘判别法(只适用于凸多边形)
4.3角度和的判断法(适用于任意多边形)

对于平面多边形而言,连接多边形内点与多边形所有顶点所形成的所有角的和要求在精度范围内应该等于360°。

5.判断两线段是否相交
#include
using namespace std;
const int INF=0x3f3f3f3f;
typedef long long ll;
struct point
{
	double x=0,y=0;
};
struct v
{
	point st,end;
};
double mul(point p1,point p2,point p0)
{
	return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}
ll across(v v1,v v2)
{
	if (max(v1.st.x,v1.end.x)>=min(v2.st.x,v2.end.x)&&
	        max(v2.st.x,v2.end.x)>=min(v1.st.x,v1.end.x)&&
	        max(v1.st.y,v1.end.y)>=min(v2.st.y,v2.end.y)&&
	        mul(v2.st,v1.end,v1.st)*mul(v1.end,v2.end,v1.st)>0&&  //修改这里与下面的>=改变端点相交、线段重合 
	        mul(v1.st,v2.end,v2.st)*mul(v2.end,v1.end,v2.st)>0)
		return 1;
	return 0;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	v v1,v2;
	cin>>v1.st.x>>v1.st.y>>v1.end.x>>v1.end.y;
	cin>>v2.st.x>>v2.st.y>>v2.end.x>>v2.end.y;
	cout<

四、基础算法

1.前缀和
1.1一维前缀和
S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]
1.2二维前缀和
S[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
2.差分
2.1一维差分
给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
2.2二维差分
给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
3.位运算
求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n
4.双指针
for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;
    // 具体问题的逻辑
}
常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
5.离散化
vector alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素
// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
    int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1; // 映射到1, 2, ...n
}
6.区间合并
// 将所有存在交集的区间合并
void merge(vector &segs)
{
    vector res;
    sort(segs.begin(), segs.end());
    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        if (ed < seg.first)
        {
            if (st != -2e9) res.push_back({st, ed});
            st = seg.first, ed = seg.second;
        }
        else ed = max(ed, seg.second);
    if (st != -2e9) res.push_back({st, ed});
    segs = res;
}

五、数据结构

六、字符串

1.字符串Hash
例:

给定一个长度为n的字符串,再给定m个询问,每个询问包含四个整数l1,r_1,l_2,r_2, 请你判断[l1,r1]和[l2,r2]这两个区间所包含的字符串子串是否完全相同。字符串中只包含大小写英文字母和数字。第一行包含整数n和m,表示字符串长度和询问次数。第二行包含一个长度为n的字符串,字符串中只包含大小写英文字母和数字。接下来m行,每行包含四个整数l1,r1,l2,r2, 表示一次询问所涉及的两个区间。注意,字符串的位置从1开始编号。

#include
using namespace std;
const int INF=0x3f3f3f3f;
typedef long long ll;
typedef unsigned long long ull;
const int N=1e5+5,P=131;
ull h[N],p[N];
int n,m;
string str;
ull get(ll l,ll r)
{
	return h[r]-h[l-1]*p[r-l+1];
}
int main(){
	ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    p[0]=1;
    cin>>n>>m;
    cin>>str;
	for (ll i=1;i<=n;i++)
	{
		h[i]=h[i-1]*P+str[i-1];
		p[i]=p[i-1]*P;
	}
	while(m--)
	{
		ll l1,r1,l2,r2;
		cin>>l1>>r1>>l2>>r2;
		if (get(l1,r1)==get(l2,r2))
			cout<<"Yes\n";
		else
			cout<<"No\n";
	}
	return 0;
}
2.KMP
#include 
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N = 100010;
int ne[N];
int n, m;
string p, s;
int main()
{
	cin >> n >> p >> m >> s;
	//ll n=p.size(),m=s.size();
	//i是代匹配位置,j是以匹配长度,j - 1是以匹配的最后一个位置
	//与自身匹配i从1开始
	for(int i = 1, j = 0; i < n; i ++)
	{
		while(j && p[i] != p[j]) j = ne[j - 1];
		if(p[i] == p[j]) j ++;
		ne[i] = j;
	}
	//与长字符串匹配i从0开始
	for(int i = 0, j = 0; i < m; i ++)
	{
		while(j && s[i] != p[j]) j = ne[j - 1];
		if(s[i] == p[j])
		{
			j ++;
			if(j == n)
			{
				cout << i - n + 1 << " ";
				j = ne[n - 1];
			}
		}
	}
	return 0;
}
2.Trie字典树
#include
using namespace std;
const int INF=0x3f3f3f3f;
typedef long long ll;
const int N=1e5+5;
ll tr[N][26],cnt[N],idx;//如果根据题目来改变26的大小
void insert(string s)//插入
{
	ll p=0;
	for (ll i=0; i>n)
	{
		idx=0;
		memset(tr[0],0,sizeof(tr[0]));
		while(n--)
		{

			string q,s;
			cin>>q>>s;
			if (q=="I")
			{
				insert(s);
			}
			else
			{
				cout<
3.AC自动机
#include
#include
#include
#include
#include
#include
#include
using namespace std;
struct Tree//字典树 
{
     int fail;//失配指针
     int vis[26];//子节点的位置
     int end;//标记有几个单词以这个节点结尾 
}AC[1000000];//Trie树
int cnt=0;//Trie的指针 
inline void Build(string s)
{
        int l=s.length();
        int now=0;//字典树的当前指针 
        for(int i=0;i Q;//队列 
        for(int i=0;i<26;++i)//第二层的fail指针提前处理一下
        {
               if(AC[0].vis[i]!=0)
               {
                   AC[AC[0].vis[i]].fail=0;//指向根节点
                   Q.push(AC[0].vis[i]);//压入队列 
               }
        }
        while(!Q.empty())//BFS求fail指针 
        {
              int u=Q.front();
              Q.pop();
              for(int i=0;i<26;++i)//枚举所有子节点
              {
                        if(AC[u].vis[i]!=0)//存在这个子节点
                      {
                                AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i];
                                    //子节点的fail指针指向当前节点的
                                  //fail指针所指向的节点的相同子节点 
                                Q.push(AC[u].vis[i]);//压入队列 
                      }
                      else//不存在这个子节点 
                      AC[u].vis[i]=AC[AC[u].fail].vis[i];
                      //当前节点的这个子节点指向当
                      //前节点fail指针的这个子节点 
              }
        }
}
int AC_Query(string s)//AC自动机匹配
{
        int l=s.length();
        int now=0,ans=0;
        for(int i=0;i>n;
     for(int i=1;i<=n;++i)
     {
             cin>>s;
             Build(s);
     }
     AC[0].fail=0;//结束标志 
     Get_fail();//求出失配指针
     cin>>s;//文本串 
     cout<

七、重要语法

1.memset

int
”较“的原则:加法不爆。
极大值:0x7f
较大值:0x3f
较小值:0xc0
极小值:0x80

long long
”较“的原则:加法不爆。
极大值:0x7f
较大值:0x3f
较小值:0xc0
极小值:0x80

float
”较“的原则:保证一定位精度。
7f以上一直到be都是-0 (实际上是一个很小的>-1.0的负数)
极大值:0x7f
较大值:0x4f
较小值:0xce
极小值:0xfe

double
”较“的原则:保证一定位精度。
极大值:0x7f
较大值:0x43
较小值:0xc2
极小值:0xfe

八、STL

1.unorder_map
#include   
#include   
#include 
#include   
using namespace std;  
int main()  
{  
	//注意:C++11才开始支持括号初始化
    unordered_map myMap={{ 5, "张大" },{ 6, "李五" }};//使用{}赋值
    myMap[2] = "李四";  //使用[ ]进行单个插入,若已存在键值2,则赋值修改,若无则插入。
    myMap.insert(pair(3, "陈二"));//使用insert和pair插入
	//遍历输出+迭代器的使用
	auto iter = myMap.begin();//auto自动识别为迭代器类型unordered_map::iterator
	while (iter!= myMap.end())
	{  
	    cout << iter->first << "," << iter->second << endl;  
	    ++iter;  
	}  
	//查找元素并输出+迭代器的使用
	auto iterator = myMap.find(2);//find()返回一个指向2的迭代器
	if (iterator != myMap.end())
	    cout << endl<< iterator->first << "," << iterator->second << endl;  
	system("pause");  
	return 0;  
}  

你可能感兴趣的:(acm竞赛,算法,c/c++)