此办法好易掌握。(0那么起没发出什么东西能替代复数且解决精度问题吧。

逆元,一般用于求\(\frac{a}{b} (\bmod
{p})\)。
这种事物,我顾都是深傻的,原来一直未打算去上学,可实际非常残暴,
以致大频繁考还怪消极,我下定狠心学一套2333。

1.5树套树

NTT

每当FFT中,我们需要使用复数,复数虽然好神奇,但是她呢时有发生友好之局限性——需要为此double类型计算,精度太没有

那么有没有来什么事物能替代复数且解决精度问题也?

斯事物,叫原根

竟逆元的方

  • 开展欧几里得

夫法充分善理解,而且对于单个查找效率似乎为还不错,比后面要介绍的
高效幂的艺术大部分只要赶早(尤其对\(\bmod
{p}\)比较大的当儿)。
斯就算是使用拓欧求解 线性同余方程\(a*x
\equiv c (\bmod {b})\)
的\(c=1\)的情状。我们就算得转正为\(a*x + b*y = 1\)
求解这个方程的解除。

代码比较简单:

void exgcd (ll a, ll b, ll &x, ll &y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return ;
    }
    exgcd (b, a % b, x, y);
    ll tmp = x;
    x = y;
    y = tmp - a / b * y;    
}
        ll x, y;
    exgcd (i, p, x, y);
    x = (x % p + p) % p;
    printf ("%d\n", x);
  • 快速幂

以此做法要使 费马小定理

若\(p\)为素数, class=”math inline”>\(a\)为刚刚整数,且 class=”math inline”>\(a\)、 class=”math inline”>\(p\)互质。
则有\(a^{p-1} \equiv 1 (\bmod
{p})\)。

此我们虽好窥见它们是姿势右边刚好也1。

之所以我们就算足以放入原式,就足以落:

\(a*x\equiv 1 (\bmod {b})\)

\(a*x\equiv a^{p-1} (\bmod {b})\)

\(x \equiv a^{p-2} (\bmod {b})\)

就此我们得就此快幂来算出 \(a^{p-2} (\bmod
{b})\)的值,这个数就是其的逆元了

代码也不行简短:

ll fpm(ll x, ll power, ll mod) {
    x %= mod;
    ll ans = 1;
    while (power) {
        if (power & 1) ans = (ans * x) % mod;
        x = (x * x) % mod;
        power >>= 1;
    }
    return ans;
}
printf ("%lld\n", fpm(i, p-2, p) );
  • 线性算法

用以求一连串数字对一个\(\bmod
p\)的逆元。洛谷P3811
唯其如此用这种方法,别的算法都于这些要求一律弄错要慢。

率先我们来一个,\(1^{-1}\equiv 1(\bmod
p)\)
然后设\(p=k*i+r,r<i,1<i<p\),再将此姿势放到\(\bmod {p}\)意义下就算会见赢得:
\(k*i+r \equiv 0 (\bmod p)\)

接下来趁直达\(i^{-1}\),\(r^{-1}\)就足以拿走:

\(k*r^{-1}+i^{-1}\equiv 0 (\bmod
p)\)

\(i^{-1}\equiv -k*r^{-1} (\bmod
p)\)

\(i^{-1}\equiv -\lfloor \frac{p}{i}
\rfloor*(p \bmod i)^{-1} (\bmod p)\)

遂,我们尽管好自前方推出时底逆元了。代码也尽管有数实践:

    a[i] = - (p / i) * a[p % i];
    a[i] = (a[i] % p + p) % p;
  • 阶乘逆元\(O(n)\)求

证明:
\(inv[i+1]=\frac{1}{(i+1)!}\)

\(inv[i+1]*(i+1)=\frac{1}{i!}=inv[i]\)

故而我们得以求出\(n!\)的逆元,然后逆推,就得求出\(1…n!\)所有的逆元了。

递交推式为
\(inv[i+1]*(i+1)=inv[i]\)

2.图论

实现

NTT求卷积代码:

的确于FFT快了很多

#include<cstdio>
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1<<21, stdin), p1 == p2) ? EOF : *p1++)
#define swap(x,y) x ^= y, y ^= x, x ^= y
#define LL long long 
const int MAXN = 3 * 1e6 + 10, P = 998244353, G = 3, Gi = 332748118; 
char buf[1<<21], *p1 = buf, *p2 = buf;
inline int read() { 
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int N, M, limit = 1, L, r[MAXN];
LL a[MAXN], b[MAXN];
inline LL fastpow(LL a, LL k) {
    LL base = 1;
    while(k) {
        if(k & 1) base = (base * a ) % P;
        a = (a * a) % P;
        k >>= 1;
    }
    return base % P;
}
inline void NTT(LL *A, int type) {
    for(int i = 0; i < limit; i++) 
        if(i < r[i]) swap(A[i], A[r[i]]);
    for(int mid = 1; mid < limit; mid <<= 1) {  
        LL Wn = fastpow( type == 1 ? G : Gi , (P - 1) / (mid << 1));
        for(int j = 0; j < limit; j += (mid << 1)) {
            LL w = 1;
            for(int k = 0; k < mid; k++, w = (w * Wn) % P) {
                 int x = A[j + k], y = w * A[j + k + mid] % P;
                 A[j + k] = (x + y) % P,
                 A[j + k + mid] = (x - y + P) % P;
            }
        }
    }
}
int main() {
    N = read(); M = read();
    for(int i = 0; i <= N; i++) a[i] = (read() + P) % P;
    for(int i = 0; i <= M; i++) b[i] = (read() + P) % P;
    while(limit <= N + M) limit <<= 1, L++;
    for(int i = 0; i < limit; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (L - 1));  
    NTT(a, 1);NTT(b, 1);    
    for(int i = 0; i < limit; i++) a[i] = (a[i] * b[i]) % P;
    NTT(a, -1); 
    LL inv = fastpow(limit, P - 2);
    for(int i = 0; i <= N + M; i++)
        printf("%d ", (a[i] * inv) % P);
    return 0;
}

逆元定义

若\(a*x\equiv1 (\bmod
{b})\),且\(a\)与 class=”math inline”>\(b\)互质,那么我们不怕能够定义:
\(x\)为 class=”math inline”>\(a\)的逆元,记为 class=”math inline”>\(a^{-1}\),所以我们为可称 class=”math inline”>\(x\)为 class=”math inline”>\(a\)的倒数,
上列计算都当$\bmod b $意义下。

因而对\(\frac{a}{b} (\bmod
{p})\),我们即便足以请来 class=”math inline”>\(b\)在 class=”math inline”>\(\bmod {p}\)
下之逆元,然后趁着及\(a\),再 class=”math inline”>\(\bmod {p}\),就是是乘法逆元的值了。

2.1.2 强连通分量

kosaraju算法.

void dfs(int v) {
  vis[v] = true;
  for (int i = 0; i < G[v].size(); i++) {
    if (!vis[G[v][i]])
      dfs(G[v][i]);
  }
  vs.push_back(v);
}
void rdfs(int v, int k) {
  vis[v] = true;
  cnt[v] = k;
  for (int i = 0; i < rG[v].size(); i++) {
    if (!vis[rG[v][i]])
      rdfs(rG[v][i], k);
  }
  vs.push_back(v);
  sc[k].push_back(v);
}
int scc() {
  memset(vis, 0, sizeof(vis));
  vs.clear();
  for (int v = 1; v <= n; v++) {
    if (!vis[v])
      dfs(v);
  }
  memset(vis, 0, sizeof(vis));
  int k = 0;
  for (int i = vs.size() - 1; i >= 0; i--) {
    if (!vis[vs[i]]) {
      rdfs(vs[i], k++);
    }
  }
  return k;
}

原根

1.3.1不足为奇线段树

其一线段树版本来自bzoj1798,代表了一样种最基础之线条树类型,支持区间修改,多重复号下传等等操作。

//bzoj1798
void build(int k, int l, int r) {
  t[k].l = l;
  t[k].r = r;
  if (r == l) {
    t[k].tag = 1;
    t[k].add = 0;
    scanf("%lld", &t[k].sum);
    t[k].sum %= p;
    return;
  }
  int mid = (l + r) >> 1;
  build(k << 1, l, mid);
  build(k << 1 | 1, mid + 1, r);
  t[k].sum = (t[k << 1].sum + t[k << 1 | 1].sum) % p;
  t[k].add = 0;
  t[k].tag = 1;
}
void pushdown(int k) {
  if (t[k].add == 0 && t[k].tag == 1)
    return;
  ll ad = t[k].add, tag = t[k].tag;
  ad %= p, tag %= p;
  int l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
  t[k << 1].tag = (t[k << 1].tag * tag) % p;
  t[k << 1].add = ((t[k << 1].add * tag) % p + ad) % p;
  t[k << 1].sum = (((t[k << 1].sum * tag) % p + (ad * (mid - l + 1) % p)%p)%p) % p;
  t[k << 1 | 1].tag = (t[k << 1 | 1].tag * tag) % p;
  t[k << 1 | 1].add = ((t[k << 1 | 1].add * tag) % p + ad) % p;
  t[k << 1 | 1].sum = (((t[k << 1|1].sum * tag) % p + (ad * (r-mid) % p)%p)%p) % p;
  t[k].add = 0;
  t[k].tag = 1;
  return;
}
void update(int k) { t[k].sum = (t[k << 1].sum%p + t[k << 1 | 1].sum%p) % p; }
void add(int k, int x, int y, ll val) {
  int l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
  if (x <= l && r <= y) {
    t[k].add = (t[k].add + val) % p;
    t[k].sum = (t[k].sum + (val * (r - l + 1) % p) % p) % p;
    return;
  }
  pushdown(k);
  if (x <= mid)
    add(k << 1, x, y, val);
  if (y > mid)
    add(k << 1 | 1, x, y, val);
  update(k);
}
void mul(int k, int x, int y, ll val) {
  int l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
  if (x <= l && r <= y) {
    t[k].add = (t[k].add * val) % p;
    t[k].tag = (t[k].tag * val) % p;
    t[k].sum = (t[k].sum * val) % p;
    return;
  }
  pushdown(k);
  if (x <= mid)
    mul(k << 1, x, y, val);
  if (y > mid)
    mul(k << 1 | 1, x, y, val);
  update(k);
}
ll query(int k, int x, int y) {
  int l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
  if (x <= l && r <= y) {
    return t[k].sum%p;
  }
  pushdown(k);
  ll ans = 0;
  if (x <= mid)
    ans = (ans + query(k << 1, x, y)) % p;
  if (y > mid)
    ans = (ans + query(k << 1 | 1, x, y)) % p;
  update(k);
  return ans%p;
}

原根

原根的概念

设\(p\)是刚整数, class=”math inline”>\(a\)是整数,若 class=”math inline”>\(\delta_p(a)\)等于 class=”math inline”>\(\phi(p)\),则称 class=”math inline”>\(a\)为模 class=”math inline”>\(p\)的一个原根

\(\delta_7(3)=6=\phi(7)\),因此\(3\)是模\(7\)的一个原根

留神原根的个数是不唯的

设模数\(p\)有原根,那么它必将生\(\phi(\phi(p))\)个原根

原根存在的根本尺度也\(m =
2,4,p^a,2p^{a}\),其中\(p\)为奇素数\(a
\ge 1\)

例如

原根有一个坏关键之定律:

  • 若\(P\)为素数,假而一个勤\(g\)是\(P\)的原根,那么\(g^i \mod P
    (1<g<P,0<i<P)\)的结果个别点儿免与

不要问我干什么,因为自己吗不明白。。

考虑原根为什么能替代单位到底进行演算,(这一部分足过了)

原因非常简单,因为她拥有与单位清相同的特性

当FFT中,我们用到了单位到底的季久性质,而原根也满足这四长达性质

这般咱们最终得以得一个定论

\[\omega_n \equiv g^\frac{p-1}{n} \mod
p\]

然后把FFT中的\(\omega_n\)都替换掉就哼了

\(p\)建议取\(998244353\),它的原根为\(3\)。

哪些告任意一个质数的原根呢?

对此质数\(p\),质因子分解\(p−1\),若\(g^{\frac{p-1}{p_i}} \neq 1 \pmod
p\)恒成立,\(g\)为\(p\)的原根

3.4.5博弈论

  • Nim游戏
  • SG函数

定义\[SG(x)=mex(S)\],其中\(S\)是\(x\)的后状态的\(SG\)函数集合,\(mex(S)\)表示不在\(S\)内之无比小非负整数。

  • SG定理

结合打与之\(SG\)函数等于各子游戏\(SG\)函数的\(Nim\)和。

若\(a,p\)互素,且\(p>1\),

对于\(a^n \equiv 1
\pmod{p}\)最小的\(n\),我们叫\(a\)模\(p\)的阶,记做\(\delta_p(a)\)

例如:

\(\delta_7(2)=3\),

\(2^1 \equiv 2 \pmod{7}\)

\(2^2 \equiv 4 \pmod{7}\)

\(2^3 \equiv 1 \pmod{7}\)

3.1.2.4线性筛法求解积性函数
  • 积性函数的首要是如何告\(f(p^k)\)。
  • 观察线性筛法中之步调,筛掉n的同时还取了他的极小之质因数\(p\),我们期待能够亮\(p\)在\(n\)中之次数,这样就能以\(f(n)=f(p^k)f(\frac{n}{p^k})\)求出\(f(n)\)。
  • 令\(n=pm\),由于\(p\)是\(n\)的极其小质因数,若\(p^2|n\),则\(p|m\),并且\(p\)也是\(m\)的极度小质因数。这样以筛法的以,记录每个合数最小质因数的次数,就会算是有新筛去合数最小质因数的次数。
  • 可这样还不够,我们还要能很快求解\(f(p^k)\),这时一般就使结合\(f\)函数的性质来设想。
  • 比如欧拉函数\(\varphi\),\(\varphi(p^k)=(p-1)p^{k-1}\),因此开展筛法求\(\varphi(p*m)\)时,如果\(p|m\),那么\(p*m\)中\(p\)的次数不呢1,所以我们好起\(m\)中解释产生\(p\),那么\(\varphi(p*m) = \varphi(m) *
    p\),否则\(\varphi(p * m)
    =\varphi(m) * (p-1)\)。

  • 复比如说默比乌斯函数\(\mu\),只有当\(k=1\)时\(\mu(p^k)=-1\),否则\(\mu(p^k)=0\),和欧拉函数一样根据\(m\)是否被\(p\)整除判断。

    void Linear_Shaker(int N) {
    phi[1] = mu[1] = 1;
    for (int i = 2; i <= N; i++) {

    if (!phi[i]) {
      phi[i] = i - 1;
      mu[i] = -1;
      prime[cnt++] = i;
    }
    for (int j = 0; j < cnt; j++) {
      if (prime[j] * i > N)
        break;
      if (i % prime[j] == 0) {
        phi[i * prime[j]] = phi[i] * prime[j];
        mu[i * prime[j]] = 0;
        break;
      } else {
        phi[i * prime[j]] = phi[i] * (prime[j] - 1);
        mu[i * prime[j]] = -mu[i];
      }
    }
    

    }
    for (int i = 2; i <= N; i++) {

    phi[i] += phi[i - 1];
    mu[i] += mu[i - 1];
    

    }
    }

1.11 后缀数组

void getsa(int sa[maxn], int rank[maxn], int Sa[maxn], int Rank[maxn]) {
  for (int i = 1; i <= n; i++)
    v[rank[sa[i]]] = i;
  for (int i = n; i >= 1; i--)
    if (sa[i] > k)
      Sa[v[rank[sa[i] - k]]--] = sa[i] - k;
  for (int i = n - k + 1; i <= n; i++)
    Sa[v[rank[i]]--] = i;
  for (int i = 1; i <= n; i++)
    Rank[Sa[i]] = Rank[Sa[i - 1]] + (rank[Sa[i - 1]] != rank[Sa[i]] ||
                                     rank[Sa[i - 1] + k] != rank[Sa[i] + k]);
}
void getheight(int sa[maxn], int rank[maxn]) {
  int i, k = 0;
  for (i = 1; i <= n; height[rank[i++]] = k) {
    if (k)
      k--;
    int j = sa[rank[i] - 1];
    while (a[i + k] == a[j + k])
      k++;
  }
}
void da() {
  p = 0, q = 1, k = 1;
  for (int i = 1; i <= n; i++)
    v[a[i]]++;
  for (int i = 1; i <= 2; i++)
    v[i] += v[i - 1];
  for (int i = 1; i <= n; i++)
    sa[p][v[a[i]]--] = i;
  for (int i = 1; i <= n; i++)
    rank[p][sa[p][i]] =
        rank[p][sa[p][i - 1]] + (a[sa[p][i - 1]] != a[sa[p][i]]);
  while (k < n) {
    getsa(sa[p], rank[p], sa[q], rank[q]);
    p ^= 1;
    q ^= 1;
    k <<= 1;
  }
  getheight(sa[p], rank[p]);
}

2.3.1 预备知识

  • 流网络\(G=(V,E)\)是一个有向图,
    其中各条边\(<u, v> \in
    E\)均为来同无因容量\(c(u, v)
    \geqslant 0\), 规定: 若\(<u,v> \not \in E\), 则\(c(u,v)=0\). 网络中发出星星点点单特殊点\(s\)和\(t\).
  • 网的流是一个实值函数\(f\):\(V \times V \rightarrow R\),
    且满足三独特性: 容量限制, 反对称性, 流守恒性.
  • 绝可怜的流动是依赖该网络中流值最深的流.
  • 遗留网络由好包容更多流动的边构成.
  • 增广路径\(p\)为残留网络上\(s\)到\(t\)的同样条简单路径.
  • 流网络\(G=(V, E)\)的割\([S, T]\)将点集划分也\(S\)和\(T\)两部分, 使得\(s \in S\ and\ t \in T\).
    符号\([S, T]={<u,v>|<u,v>
    \in E, u \in S, v \in T}\), 通过割的净流为\(f(S,T)\), 容量定义也\(c(S,T)\).
  • 一个大网的无比小割就是网络被容量最小的割.
3.1.4.1概述

而能够透过狄利克雷卷积构造一个再度好计算前缀和的函数,且用于卷积的其余一个函数也爱计算,则足以简化计算过程。

设\(f(n)\)为一个数论函数,需要算\(S(n)=\sum_{i=1}^n f(i)\)。

据悉函数\(f(n)\)的性质,构造一个\(S(n)\)关于\(S(\lfloor \frac ni
\rfloor)\)的递给推式,如下例:

找到一個相当的数论函数\(g(x)\)
\[ \sum_{i=1}^n\sum_{d|i}f(d)g(\frac
id)=\sum_{i=1}^ng(i)S(\lfloor\frac ni\rfloor) \]
足获取递推式
\[
g(1)S(n)=\sum_{i=1}^n(f*g)(i)-\sum_{i=2}^ng(i)S(\lfloor
\frac{n}{i}\rfloor) \]
在递推计算\(S(n)\)的进程遭到,需要让算的\(S(\lfloor \frac ni \rfloor)\)只有\(O(\sqrt n)\)种。

1.10 AC自动机

KMP在Trie上的扩展.

void insert(char s[101]) {
  int now = 1, c;
  for (int i = 0; i < strlen(s); i++) {
    c = s[i] - 'A' + 1;
    if (a[now][c])
      now = a[now][c];
    else
      now = a[now][c] = ++sz;
  }
  leaf[now] = 1;
}
void ac() {
  std::queue<int> q;
  q.push(1);
  point[1] = 0;
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    for (int i = 1; i <= 26; i++) {
      if (!a[u][i])
        continue;
      int k = point[u];
      while (!a[k][i])
        k = point[k];
      point[a[u][i]] = a[k][i];
      if (leaf[a[k][i]])
        leaf[a[u][i]] = 1;
      q.push(a[u][i]);
    }
  }
}

3.4别样数学工具

3.1数论

2.2.1 SPFA

虽然是NOIp(professional)知识, 但是出于当看选中非常常用,
还是写一下.

最为短路算法也会以分层图被考察.

spfa为可以应用在DP中之转移.

void spfa() {
  memset(dist, 0x3f, sizeof(dist));
  dist[s][0] = 0;
  queue<state> q;
  q.push((state){s, 0});
  memset(inq, 0, sizeof(inq));
  inq[s][0] = 1;
  while (!q.empty()) {
    state u = q.front();
    q.pop();
    inq[u.pos][u.k] = 0;
    for (int i = 0; i < G[u.pos].size(); i++) {
      edge &e = G[u.pos][i];
      if (dist[e.to][u.k] > dist[u.pos][u.k] + e.value) {
        dist[e.to][u.k] = dist[u.pos][u.k] + e.value;
        if (!inq[e.to][u.k]) {
          q.push((state){e.to, u.k});
          inq[e.to][u.k] = 1;
        }
      }
      if (u.k < k && dist[e.to][u.k + 1] > dist[u.pos][u.k]) {
        dist[e.to][u.k + 1] = dist[u.pos][u.k];
        if (!inq[e.to][u.k + 1]) {
          q.push((state){e.to, u.k + 1});
          inq[e.to][u.k + 1] = 1;
        }
      }
    }
  }
}

spfa可以为此来判负环. 所谓负环就是环上边权和为负的环.
一般以dfs版本spfa判负环.

double dist[maxn];
inline void spfa(int x) {
  int i;
  vis[x] = false;
  for (i = 0; i < rg[x].size(); i++) {
    edge &e = rg[x][i];
    if (dist[e.to] > dist[x] + e.value)
      if (!vis[e.to]) {
        flag = true;
        break;
      } else {
        dist[e.to] = dist[x] + e.value;
        spfa(e.to);
      }
  }
  vis[x] = true;
}
bool check(double lambda) {
  for (int i = 1; i <= n; i++) {
    rg[i].clear();
    for (int j = 0; j < G[i].size(); j++) {
      rg[i].push_back((edge){G[i][j].to, (double)G[i][j].value - lambda});
    }
  }
  memset(vis, 1, sizeof(vis));
  memset(dist, 0, sizeof(dist));
  flag = false;
  for (int i = 1; i <= n; i++) {
    spfa(i);
    if (flag)
      return true;
  }
  return false;
}
3.2.1.2排列组合
  • 隔板法与插空法

  • n元素集合的循环r排列的多寡是\(\frac{P_n^r}r\)

  • 大多重集合全排列\[\frac{n!}{\prod_i
    n_i!}\]

  • 多重复集聚的整合,无限重复数,设S是生k种类型对象的多样集合,r组合的个数为\[C_{r+k-1}^r=C_{r+k-1}^{k-1}\]。

  • \(Lucas\)定理(p为素数) :
    \[ C_n^m \equiv C_{n / p}^{m/p}
    \times C_{n \ mod\ p}^{m\ mod\ p} \pmod p \]

    int C(int n, int m, int P) {

    if (n < m) return 0;
    return (ll)fac[n] * inv(fac[n-m], P) % P * inv(fac[m], P)%P;
    

    }
    int lucas(int n, int m, int P) {

    if(!n && !m) return 1;
    return (ll)lucas(n/P, m/P, P) * C(n%P, m%P, P) % P;
    

    }

1.14 分块

管给一个例题吧.

受得一个数列,您需支持一下零星栽操作:1.深受[l,r]同加一个数.
2.叩问[l,r]惨遭产生些许数字高于或顶v.

将数据分为\(\sqrt
n\)一份,那么对每一个查询,我们且足以把此查询分为\(\sqrt n\)个区间,修改的时候吗是\(O(\sqrt
n)\)的级别,所以总的复杂度就是\(O(\sqrt n log \sqrt n)\)

具体地,对于各级一样片,我们且存储排序前和排序后底班,这样我们不怕解决了这书。

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
int n, q, m, block;
const int maxn = 1000001;
int a[maxn], b[maxn], pos[maxn], add[maxn];
using std::sort;
using std::min;
inline int read() {}
inline void reset(int x) {
  int l = (x - 1) * block + 1, r = min(x * block, n);
  for (int i = l; i <= r; i++)
    b[i] = a[i];
  sort(b + l, b + r + 1);
}
inline int find(int x, int v) {
  int l = (x - 1) * block + 1, r = min(x * block, n);
  int last = r;
  while (l <= r) {
    int mid = (l + r) >> 1;
    if (b[mid] < v)
      l = mid + 1;
    else
      r = mid - 1;
  }
  return last - l + 1;
}
inline void update(int x, int y, int v) {
  if (pos[x] == pos[y]) {
    for (int i = x; i <= y; i++)
      a[i] = a[i] + v;
  } else {
    for (int i = x; i <= pos[x] * block; i++)
      a[i] = a[i] + v;
    for (int i = (pos[y] - 1) * block + 1; i <= y; i++)
      a[i] = a[i] + v;
  }
  reset(pos[x]);
  reset(pos[y]);
  for (int i = pos[x] + 1; i < pos[y]; i++)
    add[i] += v;
}
inline int query(int x, int y, int v) {
  int sum = 0;
  if (pos[x] == pos[y]) {
    for (int i = x; i <= y; i++)
      if (a[i] + add[pos[i]] >= v)
        sum++;
  } else {
    for (int i = x; i <= pos[x] * block; i++)
      if (a[i] + add[pos[i]] >= v)
        sum++;
    for (int i = (pos[y] - 1) * block + 1; i <= y; i++)
      if (a[i] + add[pos[i]] >= v)
        sum++;
    for (int i = pos[x] + 1; i < pos[y]; i++)
      sum += find(i, v - add[i]);
  }
  return sum;
}
int main() {
#ifndef ONLINE_JUDGE
  freopen("input", "r", stdin);
#endif
  n = read(), q = read();
  if (n >= 500000)
    block = 3676;
  else if (n >= 5000) {
    block = 209;
  } else
    block = int(sqrt(n));
  for (int i = 1; i <= n; i++) {
    a[i] = read();
    pos[i] = (i - 1) / block + 1;
    b[i] = a[i];
  }
  if (n % block)
    m = n / block + 1;
  else
    m = n / block;
  for (int i = 1; i <= m; i++)
    reset(i);
  for (int i = 1; i <= q; i++) {
    char ch[5];
    int x, y, v;
    scanf("%s", ch);
    x = read(), y = read(), v = read();
    if (ch[0] == 'M')
      update(x, y, v);
    else
      printf("%d\n", query(x, y, v));
  }
}

3.1.6高斯消元

Gauss消元法就是采用新当行列式变换把原先矩阵转化为上三角矩阵然后回套求解。给一定一个矩阵以后,我们着眼每一个变量,找到她的系数最充分之平等实践,然后因当时同样履去解其他的实践。

double a[N][N]
void Gauss(){
    for(int i=1;i<=n;i++){
        int r=i;
        for(int j=i+1;j<=n;j++)
            if(abs(a[j][i])>abs(a[r][i])) r=j;
        if(r!=i) for(int j=1;j<=n+1;j++) swap(a[i][j],a[r][j]);

        for(int j=i+1;j<=n;j++){
            double t=a[j][i]/a[i][i];
            for(int k=i;k<=n+1;k++) a[j][k]-=a[i][k]*t;
        }
    }
    for(int i=n;i>=1;i--){
        for(int j=n;j>i;j--) a[i][n+1]-=a[j][n+1]*a[i][j];
        a[i][n+1]/=a[i][i];
    }
}

对此xor运算,我们好运用同样的不二法门消元。

除此以外,xor的言辞可以动用bitset压位为加速求解。

3.4.7.2速傅立叶变换

设想将多项式\(A_1(x)=a_0+a_2x^2+a_4x^4+\cdots+a_{n-2}x^{\frac
n2 -1}\)

\[A_2(x)=a_1+a_3x+a_5x^2+\cdots+a_{n-1}x^{\frac
n2 -1}\]

则有\[A(x)=A_1(x)+xA_2(x)\]

有\[A(\omega_n^k)=A_1(\omega_n^{2k})+\omega_n^kA_2(\omega_n^{2k})\]

有\[A(\omega_n^{k+\frac
n2})=A_1{\omega_\frac n2^k-\omega_n^kA_2(\omega_\frac n2 ^
k)}\].

注意到,当\(k\)取遍\([0,\frac n2 -1]\)时,\(k\)和\(k+\frac n2\)取遍了\([0,n-1]\),也就是说,如果既解\(A_1(x)\)和\(A_2(x)\)在\(\omega_{n/2}^0,\omega_{n/2}^1,\cdots,\omega_{n/2}^{n/2-1}\)处的点值,就可以以\(O(n)\)的时光内求得\(A(x)\)在\(\omega_n^0,\omega_n^1,\cdots,\omega_n^{n-1}\)处之取值。而有关
\(A_1(x)\) 和 \(A_2(x)\)
的题材还是对立于原来问题规模压缩了一半之支行问题,分治的边界为一个时反复件\(a_0\).

欠算法的复杂度为\(O(nlogn)\).

3.4.2快速幂

inline ll pow(ll a, ll b, ll p) {
  ll x = 1;
  while (b) {
    if (b & 1)
      x = mul(x, a);
    a = mul(a, a);
    b >>= 1;
  }
  return x;
}

3.3常表现结论和技术

3.1.4.2默比乌斯函数前缀和

利用\(\mu * I =
e\)的性质,可以有:
\[ S(n)=1-\sum_{i=2}^nS(\lfloor\frac
ni\rfloor) \]

1.3.3符号永久化

同一种奇怪的姿态,又如李超线段树。
被节点打下的符不开展下传,而是一味在需要的时候进行下传,这就是所谓永久化标记。
图片 1

struct line {
  double k, b;
  int id;
  double getf(int x) { return k * x + b; };
};
bool cmp(line a, line b, int x) {
  if (!a.id)
    return 1;
  return a.getf(x) != b.getf(x) ? a.getf(x) < b.getf(x) : a.id < b.id;
}
const int maxn = 50010;
line t[maxn << 2];
line query(int k, int l, int r, int x) {
  if (l == r)
    return t[k];
  int mid = (l + r) >> 1;
  line tmp;
  if (x <= mid)
    tmp = query(k << 1, l, mid, x);
  else
    tmp = query(k << 1 | 1, mid + 1, r, x);
  return cmp(t[k], tmp, x) ? tmp : t[k];
}
void insert(int k, int l, int r, line x) {
  if (!t[k].id)
    t[k] = x;
  if (cmp(t[k], x, l))
    std::swap(t[k], x);
  if (l == r || t[k].k == x.k)
    return;
  int mid = (l + r) >> 1;
  double X = (t[k].b - x.b) / (x.k - t[k].k);
  if (X < l || X > r)
    return;
  if (X <= mid)
    insert(k << 1, l, mid, t[k]), t[k] = x;
  else
    insert(k << 1 | 1, mid + 1, r, x);
}
void Insert(int k, int l, int r, int x, int y, line v) {
  if (x <= l && r <= y) {
    insert(k, l, r, v);
    return;
  }
  int mid = (l + r) >> 1;
  if (x <= mid)
    Insert(k << 1, l, mid, x, y, v);
  if (y > mid)
    Insert(k << 1 | 1, mid + 1, r, x, y, v);
}

1.2.3亚维树状数组

一直扩展就好了。非常的直观和显眼。

//bzoj3132
void change(int id, int x, int y, int val) {
  for (int i = x; i <= n; i += i & -i) {
    for (int j = y; j <= m; j += j & -j) {
      c[id][i][j] += val;
    }
  }
}
int qu(int id, int x, int y) {
  int ans = 0;
  for (int i = x; i > 0; i -= i & -i) {
    for (int j = y; j > 0; j -= j & -j) {
      ans += c[id][i][j];
    }
  }
  return ans;
}

2.1.1 双并过渡分量

  • 定理: 在管往连图G的DFS树中,
    非根节点u是G的割顶当且仅当u存在一個子节点v,
    使得v及其子孙都未曾反朝度连回u的祖先.
  • 假设low(u)为u及其子孙所能连回之无比早的祖辈的pre值, 则定理中的原则虽是:
  • 节点u存在一个子节点v, 使得low(v) >= pre(u).
  • 于一个连通图, 如果任意两碰在至少少久”点未另行”的路线,
    就说这个图是点-双衔接的, 这个要求等价于任意两长条边都以跟一个简易环中,
    即内部无割顶. 类似之概念边-双并通. 对于同张无向图,
    点-双连通的极大子图称为双连通分量.
  • 不等双连通分量最多就来一个公共点, 且它必将是切割顶.
    任意割顶都是简单个不同双连通分量的公点.

    stack S;
    int dfs(int u, int fa) {
    int lowu = pre[u] = ++dfs_clock;
    int child = 0;
    for(int i = 0; i < G[u].size(); i++) {

    int v = G[u][i];
    Edge e = (Edge){u, v};
    if(!pre[v]) {
      S.push(e);
      child++;
      int lowv = dfs(v, u);
      lowu = min(lowu, lowv);
      if(lowv >= pre[u]) {
        iscut[u] = true;
        bcc_cnt++;
        bcc[bcc_cnt].clear();
        for(;;) {
          Edge x = S.top(); S.pop();
          if(bccno[x.u] != bcc_cnt) {bcc[bcc_cnt].push_back(x.u); bccno[x.u] = bcc_cnt;}
          if(bccno[x.v] != bcc_cnt) {bcc[bcc_cnt].push_back(x.v); bccno[x.v] = bcc_cnt;}
          if(x.u == u && x.v == v) break;
        }
      }
    }
    else if(pre[v] < pre[u] && v != fa) {
      S.push(e);
      lowu = min(lowu, pre[v]);
    }
    

    }
    if(fa < 0 && child == 1) iscut[u] = 0; return lowu; }

  • 限-双连过渡分量可以运用还简明的方式要出, 分两独步骤,
    先做同样坏dfs标记出所有的桥梁, 然后再也做相同次dfs找有边-双并过渡分量.
    因为限-双连过渡分量是不曾国有节点的,
    所以只要以其次糟dfs的时保证不通过桥即可.

3.1.2.1线性筛素数

率先为出线性筛素数的先后。

void get_su(int n) {
  tot = 0;
  for(int i = 2; i <= n; i++) {
    if(!check[i]) prime[tot++] = i;
    for(int j = 0; j < tot; j++) {
      if(i * prime[j] > n) break;
      check[i * prime[j]] = true;
      if(i % prime[j] == 0) break;
    }
  }
}

足证明的凡,每个合数都单会叫外的顶小质因累熬去,这段代码的辰复杂度是\(\Theta (n)\)的,也即是所谓线性筛。

证明:设合数 class=”math inline”>\(n\)最小的质因数为 class=”math inline”>\(p\),它的另一个过 class=”math inline”>\(p\)的质因数为 class=”math inline”>\(p^{‘}\),另 class=”math inline”>\(n = pm=p^{‘}m^{‘}\)。
观察地方的次片段,可以窥见 class=”math inline”>\(j\)循环到质因数 class=”math inline”>\(p\)时合数n第一破让记(若循环到 class=”math inline”>\(p\)之前早已跳出循环,说明 class=”math inline”>\(n\)有重复粗的质因数),若为深受 class=”math inline”>\(p^{‘}\)标记,则是于即时前面(因为 class=”math inline”>\(m^{‘}<m\)),考虑 class=”math inline”>\(i\)循环到 class=”math inline”>\(m^{‘}\),注意到 class=”math inline”>\(n=pm=p^{‘}m^{‘}\)且 class=”math inline”>\(p,p^{‘}\)为歧之质数,因此 class=”math inline”>\(p|m^{‘}\),所以当j循环到质数p后了,不会见循环到 class=”math inline”>\(p^{‘}\),这就是印证 class=”math inline”>\(n\)不会被 class=”math inline”>\(p^{‘}\)筛去。

2.3.2 最特别流动最小割定理与线性规划

第一我们而读者都产生矣线性规划之基本知识.

最老流动问题之线性规划描述:
$$
\begin{alignat}{2}

\max\quad &f_{ts} &{}& \tag{LP1} \label{eqn – lp}\

\mbox{s.t.}\quad

&f_{u,v}\leqslant c_{u,v}, &\quad& (u,v)\in E\

&\sum_{v} f_{uv} = \sum_v f_{vu}, &{}& u \in V\
& f_{uv} \geqslant 0, &{}& (u,v) \in E \cup{(t,s)}

\end{alignat}
\[ 最小割问题之线性规划描述: \]
\begin{alignat}{2}

\min\quad &\sum_{(u,v) \in E}c_{uv}d_{uv} &{}& \tag{LP2} \

\mbox{s.t.}\quad

&d_{u,v}-p_u+p_v &\geqslant 0, &\quad& (u,v)\in E\
&p_s-p_t &\geqslant 1\
&p_u, d_{uv} \in {0, 1}

\end{alignat}
\($ 令\)p_u=[u \in S]$, \(d_{uv}=\max\{p_u-p_v, 0\}\).

设想最充分流动的夹: 记由容量限制有的变量为\(d_{uv}\), 由点\(u\)的流量守稳产生的变量为\(p_u\), 那么对问题虽是:
$$
\begin{alignat}{2}

\min\quad &\sum_{(u,v) \in E}c_{uv}d_{uv} &{}& \tag{LP3} \

\mbox{s.t.}\quad

&d_{u,v}-p_u+p_v &\geqslant 0, &\quad& (u,v)\in E\
&p_s-p_t &\geqslant 1\
&d_{uv} &\geqslant 0, &{}&(u,v)\in E

\end{alignat}
\($ 我们得出结论:
(最充分流动最小割定理)给一定一个源为\)s\(,
汇为\)t\(的网络, 则\)s,t\(的最好可怜流等于\)s,t$的最为小割.

3.4.7迅速傅立叶变换

3.2.3置办换群与\(Polya\)定理

  • \(Burnside\)引理(\(Z_k\):\(k\)不动置换类,\(c_1(a)\):一阶循环的个数:
    \[l=\frac1{|G|}\sum_{k=1}^n|Z_k|=\frac1{|G|}\sum_{j=1}^gc_1(a_j)\]
  • 安换群G作用于有限集合χ上,用k种颜色涂染集合χ中的因素,则χ在G作用下齐价类的多少也(\(m(f)\)为置换\(f\)的循环节):\[N=\frac 1{|G|}\sum_{f \in
    G}k^{m(f)}\]
3.1.4.1欧拉函数要前缀和

利用\(\varphi *
I=id\)的性质,可以有:
\[
S(n)=\sum_{i=1}^ni-\sum_{i=2}^nS(\lfloor \frac ni\rfloor)
\]

1.2树状数组

1.4.1 Splay伸展树

图片 2
平种最常用之BBST。

int ch[maxn][2], fa[maxn];
int size[maxn], data[maxn], sum[maxn], la[maxn], ra[maxn], ma[maxn], cov[maxn],
    a[maxn];
bool rev[maxn];
int n, m, sz, rt;
std::stack<int> st;
void update(int x) {
  if (!x)
    return;
  la[x] = std::max(la[l(x)], sum[l(x)] + data[x] + std::max(0, la[r(x)]));
  ra[x] = std::max(ra[r(x)], sum[r(x)] + data[x] + std::max(0, ra[l(x)]));
  ma[x] = std::max(std::max(ma[l(x)], ma[r(x)]),
                   data[x] + std::max(0, ra[l(x)]) + std::max(0, la[r(x)]));
  sum[x] = sum[l(x)] + sum[r(x)] + data[x];
  size[x] = size[l(x)] + size[r(x)] + 1;
}
void reverse(int x) {
  if (!x)
    return;
  std::swap(ch[x][0], ch[x][1]);
  std::swap(la[x], ra[x]);
  rev[x] ^= 1;
}
void recover(int x, int v) {
  if (!x)
    return;
  data[x] = cov[x] = v;
  sum[x] = size[x] * v;
  la[x] = ra[x] = ma[x] = std::max(v, sum[x]);
}
void pushdown(int x) {
  if (!x)
    return;
  if (rev[x]) {
    reverse(ch[x][0]);
    reverse(ch[x][1]);
    rev[x] = 0;
  }
  if (cov[x] != -inf) {
    recover(ch[x][0], cov[x]);
    recover(ch[x][1], cov[x]);
    cov[x] = -inf;
  }
}
void zig(int x) {
  int y = fa[x], z = fa[y], l = (ch[y][1] == x), r = l ^ 1;
  fa[ch[y][l] = ch[x][r]] = y;
  fa[ch[x][r] = y] = x;
  fa[x] = z;
  if (z)
    ch[z][ch[z][1] == y] = x;
  update(y);
  update(x);
}
void splay(int x, int aim = 0) {
  for (int y; (y = fa[x]) != aim; zig(x))
    if (fa[y] != aim)
      zig((ch[fa[y]][0] == y) == (ch[y][0] == x) ? y : x);
  if (aim == 0)
    rt = x;
  update(x);
}
int pick() {
  if (!st.empty()) {
    int x = st.top();
    st.pop();
    return x;
  } else
    return ++sz;
}
int setup(int x) {
  int t = pick();
  data[t] = a[x];
  cov[t] = -inf;
  rev[t] = false;
  sum[t] = 0;
  la[t] = ra[t] = ma[t] = -inf;
  size[t] = 1;
  return t;
}
int build(int l, int r) {
  int mid = (l + r) >> 1, left = 0, right = 0;
  if (l < mid)
    left = build(l, mid - 1);
  int t = setup(mid);
  if (r > mid)
    right = build(mid + 1, r);
  if (left) {
    ch[t][0] = left, fa[left] = t;
  } else
    size[ch[t][0]] = 0;
  if (right) {
    ch[t][1] = right, fa[right] = t;
  } else
    size[ch[t][1]] = 0;
  update(t);
  return t;
}
int find(int k) {
  int x = rt, ans;
  while (x) {
    pushdown(x);
    if (k == size[ch[x][0]] + 1)
      return ans = x;
    else if (k > size[ch[x][0]] + 1) {
      k -= size[ch[x][0]] + 1;
      x = ch[x][1];
    } else
      x = ch[x][0];
  }
  return -1;
}
void del(int &x) {
  if (!x)
    return;
  st.push(x);
  fa[x] = 0;
  del(ch[x][0]);
  del(ch[x][1]);
  la[x] = ma[x] = ra[x] = -inf;
  x = 0;
}
void print(int x) {
  if (!x)
    return;
  if (ch[x][0])
    print(ch[x][0]);
  std::cout << data[x] << ' ';
  if (ch[x][1])
    print(ch[x][1]);
}

2.1贪图的连通性

3.4.6概率与数学期望

  • 全概率公式\[P(A)=P(A|B_1)*P(B_1)+P(A|B_2)*P(B_2)+\cdots+P(A|B_n)*P(B_n)\]
  • 数学期望

GRAPHIC

3.4.7.3傅立叶逆变换

设\((y_0,y_1,\cdots,y_{n-1})\)为\((a_0,\cdots,a_{n-1})\)的快傅立叶变换.
考虑任何一个向量\((c_0,\cdots,c_{n-1})\)满足

\[c_k=\sum_{i=0}^{n-1}y_i(\omega_n^{-k})^i\].

不怕多项式\(B(x)=y_0+y_1x+\cdots+y_{n-1}x^{n-1}\)在\(\omega_n^0,\cdots,\omega_n^{-(n-1)}\)处之点值表示.

可取(证明略)

\[a_i=\frac 1n c_i\].

从而,使用单位到底的倒数代替单位清,做一样差类似快速傅里叶变换的历程,再以结果每个数除为\(n\),即为傅里叶逆变换的结果。

3.1.2.5线性筛逆元

令\(f(i)\)为\(i\)在\(mod\
p\)意义下之逆元。显然这个函数是积性函数,我们可以用线性筛求。但是其实没那么麻烦。

我们设\(p = ki+r\),那么\(ki+r \equiv 0 (mod\
p)\),两度又随着\(i^{-1}r^{-1}\),有\(kr^{-1}+i^{-1}\equiv 0\),那么\(i^{-1} \equiv -kr^{-1}=-\lfloor \frac {p}{i}
\rfloor * (p \ mod\ i)^{-1}\),这样便好递推了。

void getinv(int n) {
  inv[1] = 1;
  for(int i = 2; i <= x; i++)
    inv[i] = (long long)(p - p/i)*inv[p % i] % p;
}

有矣逆元,我们即便可以预先处理阶乘的逆元

\[n!^{-1} \equiv \prod_{k=1}^n k^{-1}\
mod \ p\]

3.3.5有些恐怕会见为此到的定律

3.3.1裴蜀定理

若a,b是整数,且(a,b)=d,那么对于随意的平头x,y,ax+by都定是d的倍数,特别地,一定在整数x,y,使ax+by=d成立。

它们的一个着重度是:a,b互质的充要条件是是整数x,y使ax+by=1.

3.3.2底和顶

  • 假使总是且干燥增的函数\(f(x)\)满足当\(f(x)\)为整数时不过出\(x\)为整数,则\[\lfloor f(x) \rfloor = \lfloor
    f(\lfloor x \rfloor) \rfloor\]和\(\lceil f(x) \rceil = \lceil f(\lceil
    x\rceil) \rceil\)
  • \[\lfloor \frac {\lfloor\frac{x}{a}
    \rfloor}{b}\rfloor = \lfloor \frac{x}{ab}\rfloor\]
  • 对于\(i\),\(\lfloor \frac{n}{\lfloor
    \frac{n}{i}\rfloor}\rfloor\)是与\(i\)被\(n\)除并下取整取值相同的相同段落距离的右端点

3.1.2线性筛与积性函数

3.4.7.1为主概念

飞傅立叶变换(FFT)用于求解多项式的卷积.

  • 单位清:单位圆的\(n\)等分点为极端,作\(n\)个向量,所得的幅角为正且最小的向量对应之复数为\(\omega_n\),称为\(n\)次单位根.
    \[\omega_n^k=cosk\frac{2\pi}{n}+isin\
    k\frac{2\pi}n\]
  • 单位到底的性能:\[\omega_{2n}^{2k}=\omega_n^k\]
  • 单位清之性:\[\omega_{n}^{k+\frac
    nk}=-\omega _n^k\]

1.8 Link-Cut Tree

对于树上的操作,我们本早就生矣树链剖分可以拍卖这些题目。然而树链剖分不支持动态维护树上的拓扑结构。所以我们需要Link-Cut
Tree(lct)来化解这种动态树问题。顺带一提的是,动态树也是Tarjan发明的。

首先我们介绍一个概念:Preferred
path(实边),其他的度还是虚边。我们运用splay来实时地维护这长长的路子。

lct的基本操作是access。access操作可以管虚边变为实边,通过变更splay的拓扑结构来保护实边。

产生矣之数据结构,我们逐个来设想少独操作。

对此链接两单节点,我们需要首先将x节点变为他所在树的一干二净节点,然后径直令fa[x]
= y即可。

争换根呢?稍微思考一下得以窥见,我们一直把打根本及他的路径反转即可。

对此第二种植操作,我们直接断开拓扑关系即可。

除此以外实现的时刻要留心,splay的绝望节点的爸爸是他的高达一个节点。所以zig和splay的写法应该非常留意。

inline bool isroot(int x) { return ch[fa[x]][0] != x && ch[fa[x]][1] != x; }
void pushdown(int k) {
  if (rev[k]) {
    rev[k] = 0;
    rev[ch[k][0]] ^= 1;
    rev[ch[k][1]] ^= 1;
    std::swap(ch[k][0], ch[k][1]);
  }
}
void zig(int x) {
  int y = fa[x], z = fa[y], l = (ch[y][1] == x), r = l ^ 1;
  if (!isroot(y))
    ch[z][ch[z][1] == y] = x;
  fa[ch[y][l] = ch[x][r]] = y;
  fa[ch[x][r] = y] = x;
  fa[x] = z;
}
void splay(int x) {
  stack<int> st;
  st.push(x);
  for (int i = x; !isroot(i); i = fa[i])
    st.push(fa[i]);
  while (!st.empty()) {
    pushdown(st.top());
    st.pop();
  }
  for (int y = fa[x]; !isroot(x); zig(x), y = fa[x])
    if (!isroot(y))
      zig((ch[fa[y]][0] == y) == (ch[y][0] == x) ? y : x);
}
void access(int x) {
  int t = 0;
  while (x) {
    splay(x);
    ch[x][1] = t;
    t = x;
    x = fa[x];
  }
}
void rever(int x) {
  access(x);
  splay(x);
  rev[x] ^= 1;
}
void link(int x, int y) {
  rever(x);
  fa[x] = y;
  splay(x);
}
void cut(int x, int y) {
  rever(x);
  access(y);
  splay(y);
  ch[y][0] = fa[x] = 0;
}
int find(int x) {
  access(x);
  splay(x);
  int y = x;
  while (ch[y][0])
    y = ch[y][0];
  return y;
}

3.2.4容斥原理

\[
|\cup_{i=1}^nA_i|=\sum_{i=1}^nA_i-\sum_{i,j:i\not=j}|A_i \cap
A_j|+\sum_{i,j,k:i\not=j\not=k}|A_i \cap A_j \cap A_k|-\cdots
\pm |A_1 \cap \cdots \cap A_n| \]

void calc(){
    for(int i = 1; i < (1 << ct); i++) {
        //do sth
        for(int j = 0; j < ct; j++) {
            if(i & (1 << j)) {
                cnt++;
                //do sth 
            }
        }
        if(cnt & 1) ans += tmp;
        else ans -= tmp;
    }
}

3.1.5华剩余定理

中华剩余定理给出了以下的平处女线性同余方程组有解的判定条件:
\[ \left\{ \begin{array}{c} x \equiv
a_1 \pmod {m_1}\\ x \equiv a_2 \pmod {m_2}\\ \vdots \\ x
\equiv a_n \pmod {m_n} \end{array} \right. \]
中原剩余定理指出,如果模数两点滴互质,那么方程组有解,并且通解可以组织得到:

  1. 设\(M = \prod_{i=1}^n
    m_i\),并设\(M_i=\frac{M}{m_i}\)。
  2. 设\(t_i=M_i^{-1} \pmod
    {m_i}\)。
  3. 这就是说通解\(x = \sum_{i=1}^n
    a_it_iM_i\)。

3.2.1成计数与二项式定理

2.3.4.1 基本建模
  • 多单源点和汇点(超级源点, 超级汇点)
  • 凭向图: 拆成稀条边
  • 终端容量限制: 拆点
  • 勿交的简单长达路径: 拆点
  • 上下界网络流:图片 3
  • 上下界费用流:图片 4
  • 图有发生变化: 重复利用之前的结果

  • 容量增加, 直接走不过老流动

  • 容量减少, 如果\(f(e) \leq
    c(e)-1\), 那么非用动, 否则退流

    for (int i = 1; i <= n; i++) {
      int now = cc[i].id;
      int u = now, v = now + n;
      bfs(u);
      if (dist[v] != -1)
        continue;
      rec[tot++] = now;
      dinic(t, v);
      dinic(u, s);
      edges[(now - 1) * 2].cap = edges[(now - 1) * 2 + 1].cap = 0;
    }
    
  • 容量为负数: 适当变形

  • 资费也负数的景况:

  • 消负圈

  • 经过当的变形. 比如说, 如果每次增广所用底边数都是一律之(记做m),
    那么把持有边的费还增长常数\(k\), 然后从最缺少路程减去\(mk\)就获得了原图最短路的尺寸
  • 图片 5

3.2重组数学

1.数据结构

3.4.4逆元

冲费马小定理(p是质数),

int inv(int x, int P){return pow(x, P-2, P);}

要动扩展欧几里德:

int inv(int x, int P) {
  int d, a, b;
  gcd(x, P, d, a, b);
  return d == 1 ? (a+P)%P : -1;
}

1.15 莫队算法

苟您懂得了[L,R]的答案。你可于O(1)的时间下得到[L,R-1]和[L,R+1]和[L-1,R]和[L+1,R]的答案的说话。就得使用莫队算法。
于莫队算法本身倍感就是是武力。只是预先了解了拥有的摸底。可以成立的组织测算每个询问的次第以这个来降低复杂度。要明了我们好不容易寿终正寝[L,R]的答案后如今要算[L’,R’]的答案。由于足于O(1)的辰下得[L,R-1]和[L,R+1]和[L-1,R]和[L+1,R]的答案.所以计算[L’,R’]的答案花的时间吗|L-L’|+|R-R’|。如果将了解[L,R]作平面上的点a(L,R).询问[L’,R’]关押开点b(L’,R’)的口舌。那么时间支出就为寡接触的曼哈顿距离。所以对于每个询问看做一个触及。我们而依照自然顺序计算每个值。那支就也曼哈顿相差的和。要计算到每个点。那么路径至少是同一株树。所以问题就改为了请其次维平面的极度小曼哈顿距离生成树。
至于二维平面最小曼哈顿距离生成树。感兴趣的足参照胡泽聪大佬的立首文章

如此这般要本着树边计算同一浅就是ok了。可以印证日复杂度为\(O(n∗\sqrt n)\).

但这种办法编程复杂度稍微大了一些。所以来一个于优雅的替代品。那就是事先对班分块。然后于有所询问按照L所在块的轻重缓急排序。如果同样又遵照R排序。然后按排序后底逐条计算。为什么如此测算就得退复杂度呢。

一致、i与i+1在同一块内,r单调递增,所以r是O(n)的。由于起n^0.5块,所以这同一片段时间复杂度是n^1.5。
其次、i与i+1跨越一块,r最多变化n,由于起n^0.5块,所以就同样有时刻复杂度是n^1.5
其三、i与i+1在平块内时转不超过n^0.5,跨越一块也未见面超越2*n^0.5,不妨看成是n^0.5。由于发生n个数,所以时复杂度是n^1.5
乃便变成了Θ(n1.5)

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
const int maxn = 50010;
#define ll long long
ll num[maxn], up[maxn], dw[maxn], ans, aa, bb, cc;
int col[maxn], pos[maxn];
struct qnode {
  int l, r, id;
} qu[maxn];
bool cmp(qnode a, qnode b) {
  if (pos[a.l] == pos[b.l])
    return a.r < b.r;
  else
    return pos[a.l] < pos[b.l];
}
ll gcd(ll x, ll y) {
  ll tp;
  while ((tp = x % y)) {
    x = y;
    y = tp;
  }
  return y;
}
void update(int x, int d) {
  ans -= num[col[x]] * num[col[x]];
  num[col[x]] += d;
  ans += num[col[x]] * num[col[x]];
}
int main() {
  int n, m, bk, pl, pr, id;
#ifndef ONLINE_JUDGE
  freopen("input", "r", stdin);
#endif
  scanf("%d %d", &n, &m);
  memset(num, 0, sizeof(num));
  bk = ceil(sqrt(1.0 * n));
  for (int i = 1; i <= n; i++) {
    scanf("%d", &col[i]);
    pos[i] = (i - 1) / bk;
  }
  for (int i = 0; i < m; i++) {
    scanf("%d %d", &qu[i].l, &qu[i].r);
    qu[i].id = i;
  }
  std::sort(qu, qu + m, cmp);
  pl = 1, pr = 0;
  ans = 0;
  for (int i = 0; i < m; i++) {
    id = qu[i].id;
    if (qu[i].l == qu[i].r) {
      up[id] = 0, dw[id] = 1;
      continue;
    }
    if (pr < qu[i].r) {
      for (int j = pr + 1; j <= qu[i].r; j++)
        update(j, 1);
    } else {
      for (int j = pr; j > qu[i].r; j--)
        update(j, -1);
    }
    pr = qu[i].r;
    if (pl < qu[i].l) {
      for (int j = pl; j < qu[i].l; j++)
        update(j, -1);
    } else {
      for (int j = pl - 1; j >= qu[i].l; j--)
        update(j, 1);
    }
    pl = qu[i].l;
    aa = ans - qu[i].r + qu[i].l - 1;
    bb = (ll)(qu[i].r - qu[i].l + 1) * (qu[i].r - qu[i].l);
    cc = gcd(aa, bb);
    aa /= cc, bb /= cc;
    up[id] = aa, dw[id] = bb;
  }
  for (int i = 0; i < m; i++)
    printf("%lld/%lld\n", up[i], dw[i]);
}

### 1.16 整体二区划&CDQ分治

3.3.5.4皮克定理

\[ S=n+\frac s2-1 \]

(其中\(n\)表示多边形内部的罗列,\(s\)表示多边形边界上的罗列,\(S\)表示多边形的面积)

3.1.3.1新当积性函数\(\mu\)

\(\mu\)就是容斥系数。
\[ \mu(n) = \begin{cases} 0, & \text{if
$\exists x^2|n$} \\ (-1)^k&n=\prod_\limits{i=1}^{k}p_i \\
\end{cases} \]
\(\mu\)函数也是一个积性函数。

下面的公式可以从容斥的角度理解。
\[ \sum_{d|n}\mu(d)=[n=1] \]

3.1.4积性函数求和跟杜教筛

2.4二分图

  • 其次划分图, 指顶点可以分成两只非交的几独\(U\)和\(V\)(\(U\
    and\ V\)皆为独立集), 使得以跟一个集内的终端不相邻的图.
  • 无论向图G为次细分图\(\Leftrightarrow\)G至少有点儿个终端,
    且所有回路的长度都为偶数\(\Leftrightarrow\)没有奇圈
  • 顶特别限度独立集的基数等于最充分独立集的基数
  • 尽要命独立集的基数和极端可怜匹配基数的与, 等于顶点数目.
  • 于连通的老二私分图: 最小顶点覆盖集的基数等于最充分匹配的基数
  • Hall定理是一个用来判定二瓜分图是否具有无比老匹配的定律。
    率先对第二分叉图G=(X∪Y,E),点集被分成了XX和YY两组成部分。
    是不是持有极其充分匹配,首先一个极致基本的尺码就是是|X|=|Y||X|=|Y|。
    Hall定理则以这基础及吃有了一个双重胜似之格。
    第一对一个点集T⊆X,定义Γ(T)Γ(T)如下:
    Γ(T)={v∣u→v∈E,u∈T,v∈Y}
    Γ(T)={v∣u→v∈E,u∈T,v∈Y}
    就算表示TT中所有点能够一直抵达的YY中的触及的汇聚。
    图片 6
    上图中,Γ({1,3})={4,5,6}Γ({1,3})={4,5,6}。
    那么Hall条件虽用于判断一个二分图是否存在不过特别匹配。Hall条件如下:
    于随意的点集T⊆X,均在:
    |T|≤|Γ(T)|
    |T|≤|Γ(T)|
    这就是说是二分叉图定有不过老匹配。
3.1.2.2积性函数
  • 设想一个定义域为\(\mathbb{N}^{+}\)的函数\(f\)(数论函数),对于自由两单互质的正整数\(a,b\),均满足

\[f(ab) =
f(a)f(b)\],则函数f被称积性函数。

  • 如对随意两独正整数\(a,b\),都有\(f(ab)=f(a)f(b)\),那么就是为号称完全积性函数。

爱见到,对于任意积性函数,\(f(1)=1\)。

  • 考虑一个浮1的正整数\(N\),设\(N
    = \prod p_{i}^{a_i}\),那么

\[f(N)=f(\prod p_i^{a_i})=\prod
f(p_i^{a_i})\],如果\(f\)还满足了积性,那么

\[f(N)=\prod f(p_i)^{a_i}\]

  • 如果\(f\)是一个随机的函数,它使和式\(g(m) =
    \sum_{d|m}f(d)\)为积性函数,那么\(f\)也得是积性函数。
  • 积性函数的Dirichlet前缀和为是积性函数。这个定律是上面定理的反命题。
  • 零星个积性函数的Dirichlet卷积为是积性函数。

2.2.2 动态最小生成树

不过小生成树算是挺宽泛的试验点.

关于最小生成树, 我们发出以下结论:

  • 于连通图中之人身自由一个环 C
    :如果C中产生边e的权值大于该环中肆意一个别的度的权值,那么这界限不见面是极度小生成树中之边.
  • 每当相同帧连通加权无为图备受,给得任意的切分,它的横切边中权值最小的底限必然属于图的最小生成树。
  • 要是图的富有最小权值的尽头仅出一样久,那么这长长的边包含在随机一个无限小生成树中。
  • 不良小生成树: 树上倍增+lca
  • 片只点中的极致可怜且最小路径一定当最好小生成森林及(水管局长)
  • 欧几里德最小生成树
    动态最小生成树: 使用Link-Cut Tree维护.

  • 矩阵树定理(Matrix-Tree)

    脚我们介绍一种新的办法——Matrix-Tree定理(Kirchhoff矩阵-树定理)。Matrix-Tree定理是解决生成树计数问题最有力的兵之一。它首先给1847年深受Kirchhoff证明。在介绍定理之前,我们第一明确几个概念:
    1、G的度数矩阵 class=”math inline”>\(D[G]\)是一个 class=”math inline”>\(n \times
    n\)的矩阵,并且满足:当\(i\not
    = j\)时, class=”math inline”>\(d_{ij}=0\);当 class=”math inline”>\(i=j\)时, class=”math inline”>\(d_{ij}\)等于 class=”math inline”>\(v_i\)的度数。
    2、G的邻接矩阵 class=”math inline”>\(A[G]\)也是一个 class=”math inline”>\(n \times n\)的矩阵,
    并且满足:如果\(v_i\)、 class=”math inline”>\(v_j\)之间有边直接相接,则 class=”math inline”>\(a_{ij}\)=1,否则为0。
    咱们定义 class=”math inline”>\(G\)的Kirchhoff矩阵(也称之为拉普拉斯算子)C[G]为C[G]=D[G]-A[G],则Matrix-Tree定理可以描述为:G的具备不同的生成树的个数等其Kirchhoff矩阵C[G]其它一个n-1阶主子式的行列式的绝对值。所谓n-1阶主子式,就是对于r(1≤r≤n),将C[G]的第r行、第r列又失去丢后得到的初矩阵,用Cr[G]表示。

  • kruskal 算法: 贪心地挑各个一样漫漫边

3.1.7大步小步BSGS算法

死步小步算法用于缓解:

已知\(A,B,C\),求\(x\)使得, 其中\(C\ is\ a\ prime\).
\[ A^x\equiv B\pmod C \]
我们令\(x=i\times m-\frac{j}{m}=\lceil
\sqrt C \rceil, i \in [1,m], j \in [0,m]\)

这就是说原式就成为了\[A^{im}=A^j\times
B\] .我们先枚举\(j\),把\(A^j
\times B\)加入哈希表,然后枚举\(i\),在表明中搜索\(A^{im}\),如果找到了,就找到了一个免除。时间复杂度为\(\Theta (\sqrt n)\)。

ll BSGS(ll A, ll B, ll C) {
    mp.clear();
    if(A % C == 0) return -2;
    ll m = ceil(sqrt(C));
    ll ans;
    for(int i = 0; i <= m; i++) {
        if(i == 0) {
            ans = B % C;
            mp[ans] = i;
            continue;
        }
        ans = (ans * A) % C;
        mp[ans] = i;
    }
    ll t = pow(A, m, C); 
    ans = t;
    for(int i = 1; i <= m; i++) {
        if(i != 1)ans = ans * t % C;
        if(mp.count(ans)) {
            int ret = i * m % C - mp[ans] % C;
            return (ret % C + C)%C;
        }
    }
    return -2;
} 
3.3.3.1老三特别定律
  • 分配律 \[\sum_{k \in K} c a_k = c
    \sum_{k \in K} a_k\]
  • 结合律\[\sum_{k \in K}(a_k +
    b_k)=\sum_{k \in K}a_k+\sum_{k \in K}b_k\]
  • 交换律\[\sum_{k \in
    K}a_k=\sum_{p(k) \in K}
    a_{p(k)}\]里p(k)是n的一个排列
  • 麻痹大意的交换律:若对每一个平头\(n\),都正好存在一个整数\(k\)使得\(p(k)=n\),那么交换律同样成立。

    ##### 3.3.3.2求解技巧

  • 扰动法,用于计算一个和式,其构思是打一个不明不白的和式开始,并记他也\(S_n\):\[S_n=\sum_{0 \leqslant k \leqslant n}
    a_k\],然后,通过将他的末段一码和率先起分离出来,用半栽办法重写\(S_{n+1}\),这样我们就是落了一个关于\(S_n\)的方程,就得赢得那个查封形式了。

  • 一个广大的置换
    \[\sum_{d|n}f(d)=\sum_{d|n}f(\frac{n}{d})\]

    ##### 3.3.3.3大抵重和式

  • 交换求和顺序:

\[
\sum_j\sum_ka_{j,k}[P(j,k)]=\sum_{P(j,k)}a_{j,k}=\sum_k\sum_ja_{j,k}[P(j,k)]
\]

  • 诚如分配律:\[\sum_{j \in J, k \in
    K}a_jb_k=(\sum_{j \in J}a_j)(\sum_{k \in K}b_k)\]

  • \(Rocky\ Road\)
    \[ \sum_{j \in J}\sum_{k \in
    K(j)}a_{j,k}=\sum_{k \in K^{‘}}\sum_{j \in J^{‘}}a_{j,k}
    \]

\[ [j \in J][k \in K(j)]=[k \in
K^{‘}][j \in J^{‘}(k)] \]

其实,这样的因数分解总是可能的:我们得使\(J=K^{‘}\)是负有整数的联谊,而\(K(j)\)和\(J^{‘}(K)\)是暨操控二又和礼仪的特性\(P(j,k)\)相对应的集纳。下面是一个特别有效之解说。

\[[1\leqslant j \leqslant n][j
\leqslant k \leqslant n] = [1 \leqslant j \leqslant k \leqslant
n] = [1 \leqslant k \leqslant n][1 \leqslant j \leqslant
k]\]

  • 一个广泛的分解
    \[
    \sum_{d|n}\sum_{k|d}=\sum_{k|m}\sum_{d|\frac{m}{k}}
    \]

  • 一个技巧

如我们发出一个饱含\(k+f(j)\)的二重和式,用\(k-f(j)\)替换\(k\)并对\(j\)求与比好。

3.2.2周边数列

  • 错位排列 \[D_n = (n-1) * (D_{n-1} +
    D_{n-2})\]
  • Catanlan数
    \[C(n) = \sum (C(n-I) *
    C(I))\]
    计算公式:
    \[C(n) =
    \frac{C(2n,n)}{n+1}\]
    应用:
    满足递推关系的都只是代表成catalan数,比如:
    发出库顺序,二叉树类个数,门票问题,格子问题(不穿对角线),括号配对问题等等。

2.3.3 最酷流算法

1.1许符串哈希

啊继缀计算一个哈希值,满足\(H(i)=H(i+1)x+s[i]\)(其中\(0 \leq i < n, H(n) = 0\)),例如
\[H(4)=s[4]\]
\[H(3)=s[4]x+s[3]\]
一般地,\(H(i)=s[n-1]x^{n-1-i}+s[n-2]x^{n-2-i}+…+s[i+1]x+s[i]\)
于同一段落长度为L的子串s[i]~s[i+L-1],定义他的哈希值\(Hash(i, L) = H(i)-H(i+L)x^L\)。
预处理H和\(x^L\)。注意到hash值很非常,我们管他有unsigned
long long中,这样就算落实了自然溢起,可以大大削弱多少常数。

2.3.4.4 费用流建模

2.3网络流

3.3.5.1费马小定理

\[ a^{p-1}\equiv1\pmod p \]

条件:\(p\ is\ prime\ and\
(a,p)=1\)

3.1.1扩展欧几里德算法

先是我们来欧几里德算法:

\[gcd(a, b) = gcd(a\ mod\ b,
b)\]

恢宏欧几里德算法解决了这么的题目:

\[ ax + by = gcd(a,b)\]

我们事先观察一种植独特之状况:

当\(b=0\)时,我们直接可以有解:
\[ \begin{eqnarray} \left\{
\begin{array}{lll} x = 1 \\ y = 0 \end{array} \right.
\end{eqnarray} \]
一般地,我们令\(c = a\ mod \
b\),递归地清除下面的方程:

\[bx^{‘}+cy^{‘}=gcd(b,c)\]

根据欧几里德算法,有

\[bx^{‘}+cy^{‘}=gcd(a,b)\]

根据\(mod\)的定义我们得以产生

\[c = a –
b\lfloor\frac{a}{b}\rfloor\]

携原式

\[bx^{‘}+(a –
b\lfloor\frac{a}{b}\rfloor)y^{‘}=gcd(a,b)\]

以反映和\(a,b\)的关系

\[ay^{‘}+b(x^{‘}-\lfloor\frac{a}{b}\rfloor
y^{‘})=gcd(a,b)\]

于是这样就算形成了追思。

此算法的思考体现在了下的次里。

void gcd(int a, int b, int &d, int &x, int &y) {
  if(!b) {d = a; x = 1; y = 0; }
  else { gcd(b, a%b, d, y, x); y -= x * (a/b); }
}

3.4.3重复相减损术

先是步:任意给得两单刚整数;判断其是否都是偶数。若是,则据此2约简;若不是虽然执行第二步。

亚步:以比充分的数减较小的高频,接着将所得之不同和比小的一再比,并因命减多少数。继续是操作,直到所得的减数和见仁见智等为止。

尽管率先步着盖少的多只2以及第二步中等数的乘积就是所要之最大公约数。

1.4 平衡树

DATA STRUCTURE

1.12 后缀自动机

图片 7

struct Suffix_Automaton {
  ll fa[maxn], trans[maxn][26], len[maxn], right[maxn];
  ll last, root, sz;
  bool flag[maxn];
  void init() {
    memset(flag, 0, sizeof(flag));
    sz = 0;
    last = root = ++sz;
  }
  void insert(ll x) {
    ll p = last, np = last = ++sz;
    len[np] = len[p] + 1;
    flag[np] = 1;
    right[np] = right[p] + 1;
    for (; !trans[p][x]; p = fa[p])
      trans[p][x] = np;
    if (p == 0)
      fa[np] = root;
    else {
      ll q = trans[p][x];
      if (len[q] == len[p] + 1) {
        fa[np] = q;
      } else {
        ll nq = ++sz;
        fa[nq] = fa[q];
        memcpy(trans[nq], trans[q], sizeof(trans[q]));
        len[nq] = len[p] + 1;
        fa[q] = fa[np] = nq;
        for (; trans[p][x] == q; p = fa[p])
          trans[p][x] = nq;
      }
    }
  }
} sam;

3.3.4频遵循问题之求解技巧

  • \(\{\lfloor \frac{n}{i} \rfloor|i
    \in [1,n]\}\)只有\(O(\sqrt
    n)\)种取值。所以可以采取是结论降低复杂度。

如,在bzoj2301中,我们最后解出了\[f(n,
m)=\sum_{1 \leqslant d \leqslant min(n, m)}\mu(d)\lfloor \frac
{n}{d} \rfloor \lfloor \frac {m}{d}
\rfloor\]咱们虽得采用杜教筛计算出默比乌斯函数的前方缀和,计算出商与除以i相同之顶多延伸至哪,下同样潮直接跨越了及时同样段子就是吓了。下面是这个开的相同截先后。

int calc(int n, int m) {
    int ret = 0, last;
    if(n > m) std::swap(n, m);
    for(int i = 1; i <= n; i = last + 1) { //i就相当于原式中的d
        last = min(n / (n/i), m / (m/i));  //last计算了商与除以i相同的最多延伸到哪里,不难证明这样计算的正确性
        ret += (n / i) * (m / i) * (sum[last] - sum[i-1]);
    }
    return ret;
}

2.5 其他常用结论

  • 于无设有孤立点的图,|最可怜匹配|+|最小边覆盖| = |V|

  • 顶深独立成团 + 最小顶点覆盖 = V

  • 对此第二细分图:|最特别匹配| = |最小顶点覆盖|

  • 平面圖的頂點個數、邊數和面的個數之間有一個缘歐拉命名的公式:图片 8

内部,V是頂點的数,E是邊的數目,F是对之數目,C是整合圖形的連通部分的數目。當圖是單連通圖的時候,公式簡化為:
图片 9

  • 旁一个平面图的针对偶图仍然是平面图
3.2.1.1二项式定理

\[ (a+b)^n=\sum_{i=0}^n C_n^i
a^{i}b^{n-i} \]

其中\(C_n^m\)为二项式系数,满足几乎单结论:
\[ C_n^i=C_n^{n-i} \]

\[ C_{n+1}^m=C_n^m+C_n^{m-1} \]

\[ \sum_{i=0}^nC_n^i=2^n \]

\[ C_n^k=\frac{k}{n}C_{n-1}^{k-1}
\]

3.3.3和式

2.3.4.2 最特别流建模
  • 同棋盘有关的题材可以考虑最要命流动
3.3.5.3威尔逊定理

\[ (p-1)!\equiv-1\pmod p \Leftrightarrow
p\ is\ prime \]

2.3.3.1 Dinic算法
int dist[maxn], iter[maxn];
inline void bfs(int s) {
  memset(dist, -1, sizeof(dist));
  dist[s] = 0;
  queue<int> q;
  q.push(s);
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    for (int i = 0; i < G[u].size(); i++) {
      edge &e = edges[G[u][i]];
      if (e.cap > 0 && dist[e.to] == -1) {
        dist[e.to] = dist[u] + 1;
        q.push(e.to);
      }
    }
  }
}
inline int dfs(int s, int t, int flow) {
  if (s == t)
    return flow;
  for (int &i = iter[s]; i < G[s].size(); i++) {
    edge &e = edges[G[s][i]];
    if (e.cap > 0 && dist[e.to] > dist[s]) {
      int d = dfs(e.to, t, min(e.cap, flow));
      if (d > 0) {
        e.cap -= d;
        edges[G[s][i] ^ 1].cap += d;
        return d;
      }
    }
  }
  return 0;
}
inline int dinic(int s, int t) {
  int flow = 0;
  while (1) {
    bfs(s);
    if (dist[t] == -1)
      return flow;
    memset(iter, 0, sizeof(iter));
    int d;
    while (d = dfs(s, t, inf))
      flow += d;
  }
  return flow;
}

1.2.2相助区间修改的树状数组

设若对于区间\([a,
b]\)增加k,令g[i]为加了k以后之数组。
\[g[i] = s[i] (i < a)\]
\[g[i] = s[i] + i*k – k(a-1) (i >=
a 且 i <= b)\]
\[g[i] = s[i] + b*k – k(a-1) (i >
b)\]
从而我们举办两单树状数组。

2.3.4 建模方法

2.3.3.2 费用流

泛指一种与支出相关的流算法.EK算法比较常用

bool spfa(ll &flow, ll &cost) {
  for (int i = 0; i <= n + 1; i++) {
    dist[i] = -inf;
  }
  memset(inq, 0, sizeof(inq));
  dist[s] = 0, inq[s] = 1, pre[s] = 0, fi[s] = inf;
  queue<int> q;
  q.push(s);
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    inq[u] = 0;
    for (int i = 0; i < G[u].size(); i++) {
      edge &e = E[G[u][i]];
      if (e.cap > e.flow && dist[e.to] < dist[u] + e.cost) {
        dist[e.to] = dist[u] + e.cost;
        pre[e.to] = G[u][i];
        fi[e.to] = min(fi[u], e.cap - e.flow);
        if (!inq[e.to]) {
          q.push(e.to);
          inq[e.to] = 1;
        }
      }
    }
  }
  if (dist[t] <= -inf)
    return false;
  if (cost + dist[t] * fi[t] < 0) { 
    ll temp = cost / (-dist[t]); //temp:还能够增加的流
    flow += temp;
    return false;
  }
  flow += fi[t];
  cost += dist[t] * fi[t];
  int u = t;
  while (u != s) {
    E[pre[u]].flow += fi[t];
    E[pre[u] ^ 1].flow -= fi[t];
    u = E[pre[u]].from;
  }
  return true;
}
ll mcmf(int s, int t) {
  ll flow = 0;
  ll cost = 0;
  while (spfa(flow, cost))
    ;
  return flow;
}

1.9 KMP

对于字符串S的前i个字符构成的子串,既是她的后缀又是其的前缀的字符串中(它自身除了),最丰富之长度记作next[i]

多用于DP

for (int i = 2; i <= m; i++) {
    while (j > 0 && ch[j + 1] != ch[i])
      j = p[j];
    if (ch[j + 1] == ch[i])
      j++;
    p[i] = j;
  }

3.1.3默比乌斯反演与狄利克雷卷积

3.4.1快速乘

inline ll mul(ll a, ll b) {
  ll x = 0;
  while (b) {
    if (b & 1)
      x = (x + a) % p;
    a = (a << 1) % p;
    b >>= 1;
  }
  return x;
}
3.1.2.3欧拉函数\(\varphi\)
  • \(\varphi(n)\)表示\(1..n\)中与\(n\)互质的整数独数。
  • 咱有欧拉定理:

\[n^{\varphi(m)}\equiv 1 \pmod m\ \ \
\ n\perp m\]

得使用这个定律计算逆元。

  • 如果\(m\)是一个素数幂,则好计算\(\varphi(m)\),因为有$n \perp p^{k}
    \Leftrightarrow p \nmid n $ 。在\(\{0,1,…,p^k-1\}\)中的\(p\)的倍数是\(\{0, p, 2p, …,
    p^k-p\}\),从而有\(p^{k-1}\)个,剩下的计入\(\varphi(p^k)\)

\[\varphi(p^k) =
p^k-p^{k-1}=(p-1)p^{k-1}\]

  • 鉴于方的演绎我们不难得出欧拉函数一般的表示:

\[\varphi(m) =
\prod_{p|m}(p^{m_p}-p^{m_p-1}) = m
\prod_{p|m}(1-\frac{1}{p})=\prod(p-1)p^{m_p-1}\]

  • 运用Mobius反演,不难得出\(\sum_{d|n}\varphi(d) = n\)。
  • 当\(n>1\)时,\(1..n\)中与\(n\)互质的平头和也\(\frac{n\varphi(n)}{2}\)
  • 降幂大法\[A^B\ mod\ C=A^{B\ mod\
    \varphi(C)+\varphi(C)}\ mod\ C\]

1.2.1平常树状数组

树状数组好写好调,能为此树状数组的早晚尽量使采取。
树状数组从1从头。

int lowbit(int x) { return x & -x; }
int sum(int x) {
  int ans = 0;
  while (x) {
    ans += bit[x];
    x -= lowbit(x);
  }
  return ans;
}
void add(int i, int x) {
  while (i <= n) {
    bit[i] += x;
    i += lowbit(i);
  }
}

MATHEMATICS

3.数学知识

2.2 最短路与顶小生成树

2.3.4.3 最小割建模
  • 用容量也\(\infty\)的界限表示冲突
  • 由个别触及关系之角度展开最小割建模
3.3.5.2欧拉定理

\[ a^{\varphi(p)}\equiv 1\pmod p
\]

条件:\(a,p \in \mathbb{Z^+}, (a,
p)=1\)

1.5.1 线段树套splay

卿需写一种多少结构(可参照题目标题),来维护一个平稳数列,其中需要提供以下操作:

  1. 查询k于间隔内之行
  2. 查询区间内排名呢k的值
  3. 改某平各类值达的数值
  4. 查询k以区间内的前人(前驱定义为小于x,且最好充分之累)
  5. 查询k以区间内之后继(后继定义也大于x,且极小的往往)

    #include
    #include
    #include
    using namespace std;
    const int maxn = 4e6 + 5;
    const int inf = 1e9;
    int ans, n, m, opt, l, r, k, pos, sz, Max;
    int a[maxn], fa[maxn], ch[maxn][2], size[maxn], cnt[maxn], data[maxn], rt[maxn];
    inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {

    if (ch == '-')
      f = -1;
    ch = getchar();
    

    }
    while (isdigit(ch)) {

    x = x * 10 + ch - '0';
    ch = getchar();
    

    }
    return x * f;
    }
    struct Splay {
    void clear(int x) {

    fa[x] = ch[x][0] = ch[x][1] = size[x] = cnt[x] = data[x] = 0;
    

    }
    void update(int x) {

    if (x) {
      size[x] = cnt[x];
      if (ch[x][0])
        size[x] += size[ch[x][0]];
      if (ch[x][1])
        size[x] += size[ch[x][1]];
    }
    

    }
    void zig(int x) {

    int y = fa[x], z = fa[y], l = (ch[y][1] == x), r = l ^ 1;
    fa[ch[y][l] = ch[x][r]] = y;
    fa[ch[x][r] = y] = x;
    fa[x] = z;
    if (z)
      ch[z][ch[z][1] == y] = x;
    update(y);
    update(x);
    

    }
    void splay(int i, int x, int aim = 0) {

    for (int y; (y = fa[x]) != aim; zig(x))
      if (fa[y] != aim)
        zig((ch[fa[y]][0] == y) == (ch[y][0] == x) ? y : x);
    if (aim == 0)
      rt[i] = x;
    

    }
    void insert(int i, int v) {

    int x = rt[i], y = 0;
    if (x == 0) {
      rt[i] = x = ++sz;
      fa[x] = ch[x][0] = ch[x][1] = 0;
      size[x] = cnt[x] = 1;
      data[x] = v;
      return;
    }
    while (1) {
      if (data[x] == v) {
        cnt[x]++;
        update(y);
        splay(i, x);
        return;
      }
      y = x;
      x = ch[x][v > data[x]];
      if (x == 0) {
        ++sz;
        fa[sz] = y;
        ch[sz][0] = ch[sz][1] = 0;
        size[sz] = cnt[sz] = 1;
        data[sz] = v;
        ch[y][v > data[y]] = sz;
        update(y);
        splay(i, sz);
        rt[i] = sz;
        return;
      }
    }
    

    }
    void find(int i, int v) {

    int x = rt[i];
    while (1) {
      if (data[x] == v) {
        splay(i, x);
        return;
      } else
        x = ch[x][v > data[x]];
    }
    

    }
    int pre(int i) {

    int x = ch[rt[i]][0];
    while (ch[x][1])
      x = ch[x][1];
    return x;
    

    }
    int succ(int i) {

    int x = ch[rt[i]][1];
    while (ch[x][0])
      x = ch[x][0];
    return x;
    

    }
    void del(int i) {

    int x = rt[i];
    if (cnt[x] > 1) {
      cnt[x]--;
      return;
    }
    if (!ch[x][0] && !ch[x][1]) {
      clear(rt[i]);
      rt[i] = 0;
      return;
    }
    if (!ch[x][0]) {
      int oldroot = x;
      rt[i] = ch[x][1];
      fa[rt[i]] = 0;
      clear(oldroot);
      return;
    }
    if (!ch[x][1]) {
      int oldroot = x;
      rt[i] = ch[x][0];
      fa[rt[i]] = 0;
      clear(oldroot);
      return;
    }
    int y = pre(i), oldroot = x;
    splay(i, y);
    rt[i] = y;
    ch[rt[i]][1] = ch[oldroot][1];
    fa[ch[oldroot][1]] = rt[i];
    clear(oldroot);
    update(rt[i]);
    return;
    

    }
    int rank(int i, int v) {

    int x = rt[i], ans = 0;
    while (1) {
      if (!x)
        return ans;
      if (data[x] == v)
        return ((ch[x][0]) ? size[ch[x][0]] : 0) + ans;
      else if (data[x] < v) {
        ans += ((ch[x][0]) ? size[ch[x][0]] : 0) + cnt[x];
        x = ch[x][1];
      } else if (data[x] > v) {
        x = ch[x][0];
      }
    }
    

    }
    int find_pre(int i, int v) {

    int x = rt[i];
    while (x) {
      if (data[x] < v) {
        if (ans < data[x])
          ans = data[x];
        x = ch[x][1];
      } else
        x = ch[x][0];
    }
    return ans;
    

    }
    int find_succ(int i, int v) {

    int x = rt[i];
    while (x) {
      if (v < data[x]) {
        if (ans > data[x])
          ans = data[x];
        x = ch[x][0];
      } else
        x = ch[x][1];
    }
    return ans;
    

    }
    } sp;
    void insert(int k, int l, int r, int x, int v) {
    int mid = (l + r) >> 1;
    sp.insert(k, v);
    if (l == r)

    return;
    

    if (x <= mid)

    insert(k << 1, l, mid, x, v);
    

    else

    insert(k << 1 | 1, mid + 1, r, x, v);
    

    }
    void askrank(int k, int l, int r, int x, int y, int val) {
    int mid = (l + r) >> 1;
    if (x <= l && r <= y) {

    ans += sp.rank(k, val);
    return;
    

    }
    if (x <= mid)

    askrank(k << 1, l, mid, x, y, val);
    

    if (mid + 1 <= y)

    askrank(k << 1 | 1, mid + 1, r, x, y, val);
    

    }
    void change(int k, int l, int r, int pos, int val) {
    int mid = (l + r) >> 1;
    sp.find(k, a[pos]);
    sp.del(k);
    sp.insert(k, val);
    if (l == r)

    return;
    

    if (pos <= mid)

    change(k << 1, l, mid, pos, val);
    

    else

    change(k << 1 | 1, mid + 1, r, pos, val);
    

    }
    void askpre(int k, int l, int r, int x, int y, int val) {
    int mid = (l + r) >> 1;
    if (x <= l && r <= y) {

    ans = max(ans, sp.find_pre(k, val));
    return;
    

    }
    if (x <= mid)

    askpre(k << 1, l, mid, x, y, val);
    

    if (mid + 1 <= y)

    askpre(k << 1 | 1, mid + 1, r, x, y, val);
    

    }
    void asksucc(int k, int l, int r, int x, int y, int val) {
    int mid = (l + r) >> 1;
    if (x <= l && r <= y) {

    ans = min(ans, sp.find_succ(k, val));
    return;
    

    }
    if (x <= mid)

    asksucc(k << 1, l, mid, x, y, val);
    

    if (mid + 1 <= y)

    asksucc(k << 1 | 1, mid + 1, r, x, y, val);
    

    }
    int main() {
    #ifdef D
    freopen(“input”, “r”, stdin);
    #endif
    n = read(), m = read();
    for (int i = 1; i <= n; i++)

    a[i] = read(), Max = max(Max, a[i]), insert(1, 1, n, i, a[i]);
    

    for (int i = 1; i <= m; i++) {

    opt = read();
    if (opt == 1) {
      l = read(), r = read(), k = read();
      ans = 0;
      askrank(1, 1, n, l, r, k);
      printf("%d\n", ans + 1);
    } else if (opt == 2) {
      l = read(), r = read(), k = read();
      int head = 0, tail = Max + 1;
      while (head != tail) {
        int mid = (head + tail) >> 1;
        ans = 0;
        askrank(1, 1, n, l, r, mid);
        if (ans < k)
          head = mid + 1;
        else
          tail = mid;
      }
      printf("%d\n", head - 1);
    } else if (opt == 3) {
      pos = read();
      k = read();
      change(1, 1, n, pos, k);
      a[pos] = k;
      Max = std::max(Max, k);
    } else if (opt == 4) {
      l = read();
      r = read();
      k = read();
      ans = 0;
      askpre(1, 1, n, l, r, k);
      printf("%d\n", ans);
    } else if (opt == 5) {
      l = read();
      r = read();
      k = read();
      ans = inf;
      asksucc(1, 1, n, l, r, k);
      printf("%d\n", ans);
    }
    

    }

1.6点分治

void getroot(int x, int fa) {
  size[x] = 1;
  f[x] = 0;
  for (int i = 0; i < G[x].size(); i++) {
    edge &e = G[x][i];
    if (!vis[e.to] && e.to != fa) {
      getroot(e.to, x);
      size[x] += size[e.to];
      f[x] = max(f[x], size[e.to]);
    }
  }
  f[x] = max(f[x], sum - size[x]);
  if (f[x] < f[rt])
    rt = x;
}
void getdeep(int x, int fa) {
  cnt[deep[x]]++;
  for (int i = 0; i < G[x].size(); i++) {
    edge &e = G[x][i];
    if (!vis[e.to] && e.to != fa) {
      deep[e.to] = (deep[x] + e.weigh) % 3;
      getdeep(e.to, x);
    }
  }
}
int cal(int x, int now) {
  cnt[0] = cnt[1] = cnt[2] = 0;
  deep[x] = now;
  getdeep(x, 0);
  return cnt[1] * cnt[2] * 2 + cnt[0] * cnt[0];
}
void work(int x) {
  ans += cal(x, 0); //统计不同子树通过重心的个数
  vis[x] = 1;
#ifndef ONLINE_JUDGE
  printf("In root %d: %d\n", rt, ans);
#endif
  for (int i = 0; i < G[x].size(); i++) {
    edge &e = G[x][i];
    if (!vis[e.to]) {
      ans -= cal(e.to, e.weigh); //去除在同一个子树中被重复统计的
      rt = 0;
      sum = size[e.to];
      getroot(e.to, 0);
      work(rt); // Decrease and Conquer
    }
  }
}

1.3线段树

3.1.4.3模板
ll get_p(int x) { return (x <= m) ? phi[x] : p[n / x]; };
ll get_q(int x) { return (x <= m) ? mu[x] : q[n / x]; };
void solve(ll x) {
  if (x <= m)
    return;
  int i, last = 1, t = n / x;
  if (vis[t])
    return;
  vis[t] = 1;
  p[t] = ((x + 1) * x) >> 1;
  q[t] = 1;
  while (last < x) {
    i = last + 1;
    last = x / (x / i);
    solve(x / i);
    p[t] -= get_p(x / i) * (last - i + 1);
    q[t] -= get_q(x / i) * (last - i + 1);
  }
}
//注:本代码为了避免数组过大,p[]和q[]记录的是分母的值。
3.7.4.4代码实现

FFT有些许种常见的代码实现:
递归版本及迭代版本,一般来讲递归效率很不同,但出于自家生菜,一轱辘省选就先行下递归版本骗分.迭代版本后会更新.

const double PI = acos(-1);
bool inversed = false;
inline std::complex<double> omega(const int n, const int k) {
  if (!inversed)
    return std::complex<double>(cos(2 * PI / n * k), sin(2 * PI / n * k));
  else
    return std::conj(
        std::complex<double>(cos(2 * PI / n * k), sin(2 * PI / n * k)));
}
void fft(std::complex<double> *a, std::complex<double> *ans, int n) {
  if (n == 1) {
    ans[0] = a[0];
    return;
  }
  std::complex<double> buf[maxn];
  int m = n >> 1;
  for (int i = 0; i < m; i++) {
    buf[i] = a[i * 2];
    buf[i + m] = a[i * 2 + 1];
  }
  std::complex<double> tmp[maxn];
  fft(buf, tmp, m);
  fft(buf + m, tmp + m, m);
  for (int i = 0; i < m; i++) {
    std::complex<double> x = omega(n, i);
    ans[i] = tmp[i] + x * tmp[i + m];
    ans[i + m] = tmp[i] - x * tmp[i + m];
  }
}
void solve() {
  //sth
  while (N < n + 1)
    N <<= 1;
  N <<= 1;
  fft(a, ans1, N);
  fft(b, ans2, N);
  std::complex<double> ans3[maxn];
  for (int i = 0; i < N; i++)
    ans3[i] = ans1[i] * ans2[i];
  std::complex<double> tmp[maxn];
  inversed = true;
  fft(ans3, tmp, N);
  for (int i = 0; i < N; i++)
    c[i] = tmp[i].real() / N;
  //sth
}

$\mathtt{COPYRIGHT}© \mathtt{2017,KONJAC,MIT LICENSE} $

2.3.4.5 流量平衡考虑
3.1.3.2默比乌斯反演

第一为起Mobius反演的公式:

\[ F(n)=\sum_{d|n}f(d) \Rightarrow
f(n)=\sum_{d|n}\mu(\frac{n}{d})F(d) \]
发出零星栽常见的印证,一种植是采取Dirichlet卷积,一种是使用初当方式。

证明:
\[ \sum_{d|n}\mu(d)F(\frac{n}{d}) =
\sum_{d|n}\mu(\frac{n}{d})F(d)=\sum_{d|n}\mu(\frac{n}{d})\sum_{k|d}f(k)\\=\sum_{d|n}\sum_{k|d}\mu(\frac{n}{d})f(k)=\sum_{k|n}\sum_{d|\frac{n}{k}}\mu(\frac{n}{kd})f(k)\\
=\sum_{k|n}\sum_{d|\frac{n}{k}}\mu(d)f(k)=\sum_{k|n}[\frac{n}{k}
= 1]f(k)=f(n) \]
默比乌斯反演的其他一样种植样式:
\[ F(n)=\sum_{n|d}f(d)\Rightarrow
f(n)=\sum_{n|d}\mu(\frac{d}{n})F(d) \]
以此姿势的认证及上式大同小异,我当此地描绘一下关键步骤
\[
\sum_{n|d}\sum_{d|k}\mu(\frac{d}{n})f(k)=\sum_{n|k}\sum_{d|\frac{k}{n}}\mu(d)f(k)=f(n)
\]
对此片函数\(f(n)\),如果我们很为难直接呼吁来他的价值,而易于求出倍数和要为累及\(F(n)\),那么我们可由此默比乌斯反演来求得\(f(n)\)的值

1.7培育链剖分

void dfs1(int x) {
  vis[x] = size[x] = 1;
  for (int i = 1; i <= 17; i++) {
    if (deep[x] < (1 << i))
      break;
    fa[x][i] = fa[fa[x][i - 1]][i - 1];
  }
  for (int i = head[x]; i; i = e[i].next) {
    if (!vis[e[i].to]) {
      deep[e[i].to] = deep[x] + 1;
      fa[e[i].to][0] = x;
      dfs1(e[i].to);
      size[x] += size[e[i].to];
    }
  }
}
void dfs2(int x, int chain) {
  pl[x] = ++sz;
  que[sz] = x;
  belong[x] = chain;
  int k = 0;
  for (int i = head[x]; i; i = e[i].next)
    if (deep[e[i].to] > deep[x] && size[k] < size[e[i].to])
      k = e[i].to;
  if (!k)
    return;
  dfs2(k, chain);
  for (int i = head[x]; i; i = e[i].next)
    if (e[i].to != k && deep[e[i].to] > deep[x])
      dfs2(e[i].to, e[i].to);
}
void update(int k) {}
void build(int k, int l, int r) {
  t[k].l = l, t[k].r = r, t[k].s = 1, t[k].tag = -1;
  if (l == r) {
    t[k].lc = t[k].rc = value[que[l]];
    return;
  }
  int mid = (l + r) >> 1;
  build(k << 1, l, mid);
  build(k << 1 | 1, mid + 1, r);
  update(k);
}
int lca(int x, int y) {
  if (deep[x] < deep[y])
    std::swap(x, y);
  int t = deep[x] - deep[y];
  for (int i = 0; i <= 17; i++) {
    if (t & (1 << i))
      x = fa[x][i];
  }
  for (int i = 17; i >= 0; i--) {
    if (fa[x][i] != fa[y][i]) {
      x = fa[x][i];
      y = fa[y][i];
    }
  }
  if (x == y)
    return x;
  else
    return fa[x][0];
}
void pushdown(int k) {}
int query(int k, int x, int y) {}//线段树操作
int getcolor(int k, int pos) {}//线段树操作
int solvesum(int x, int f) {
  int sum = 0;
  while (belong[x] != belong[f]) {
    sum += query(1, pl[belong[x]], pl[x]);
    if (getcolor(1, pl[belong[x]]) == getcolor(1, pl[fa[belong[x]][0]]))
      sum--;
    x = fa[belong[x]][0];
  }
  sum += query(1, pl[f], pl[x]);
  return sum;
}
void change(int k, int x, int y, int c) {}//线段树操作
void solvechange(int x, int f, int c) {
  while (belong[x] != belong[f]) {
    change(1, pl[belong[x]], pl[x], c);
    x = fa[belong[x]][0];
  }
  change(1, pl[f], pl[x], c);
}
void solve() {
  dfs1(1);
  dfs2(1, 1);
  build(1, 1, n);
  for (int i = 1; i <= m; i++) {
    char ch[10];
    scanf("%s", ch);
    if (ch[0] == 'Q') {
      int a, b;
      scanf("%d %d", &a, &b);
      int t = lca(a, b);
      printf("%d\n", solvesum(a, t) + solvesum(b, t) - 1);
    } else {
      int a, b, c;
      scanf("%d %d %d", &a, &b, &c);
      int t = lca(a, b);
      solvechange(a, t, c);
      solvechange(b, t, c);
    }
  }
}

1.3.2而持久化线段树

无异于栽而持久化数据结构。
持续一样的节点,只是将修改的双重链接,节省空间。
好爆空间。
每每跟权值线段树和次分答案结合。

int rt[maxn], lc[maxm], rc[maxm], sum[maxm];
void update(int l, int r, int x, int &y, int v) {
  y = ++sz;
  sum[y] = sum[x] + 1;
  if (l == r)
    return;
  lc[y] = lc[x];
  rc[y] = rc[x];
  int mid = (l + r) >> 1;
  if (v <= mid)
    update(l, mid, lc[x], lc[y], v);
  else
    update(mid + 1, r, rc[x], rc[y], v);
}

1.13 Manacher

void manacher() {
  int mx = 1, id = 1;
  for (int i = n; i; i--)
    str[i * 2] = '#', str[i * 2 - 1] = str[i];
  n <<= 1;
  for (int i = 1; i <= n; i++) {
    p[i] = std::min(p[id * 2 - i], mx - i);
    while (i - p[i] > 0 && str[i - p[i]] == str[i + p[i]]) {
      int al = (i - p[i]) / 2 + 1;
      int ar = (i + p[i] + 1) / 2;
      // printf("%d %d\n", al, ar);
      sam.query(al, ar);
      p[i]++;
    }
    if (i + p[i] > mx)
      mx = i + p[i], id = i;
  }
}
3.1.3.3狄利克雷卷积

概念两独数论函数\(f(x)\),\(g(x)\)的\(Dirichlet\)卷积
\[ (f*g)(n)=\sum_{d|n}f(d)g(\frac nd)
\]
Dirichlet卷积满足交换律,结合律,分配律,单位初次。

使狄利克雷卷积不难证明默比乌斯反演。

相关文章