准备 Head 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include  <bits/stdc++.h>  #define  SZ(x) (int)(x).size() #define  ALL(x) (x).begin(), (x).end() #define  PB push_back #define  EB emplace_back #define  MP make_pair #define  FI first #define  SE second using  namespace  std;typedef  double  DB;typedef  long  double  LD;typedef  long  long  LL;typedef  unsigned  long  long  ULL;typedef  pair<int , int > PII;typedef  vector<int > VI;typedef  vector<LL> VLL;typedef  vector<PII> VPII;
 
快速读入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 template <typename  T>bool  read (T &t)  {    static  char  ch;     int  f=1 ;     while (ch!=EOF&&!isdigit (ch)) {         if (ch=='-' ) f=-1 ;         ch=getchar ();     }     if (ch==EOF) return  false ;     for (t=0 ;isdigit (ch);ch=getchar ()) t=t*10 +ch-'0' ;     t*=f;     return  true ; } template <typename  T>void  print (T t)   {    static  int  stk[70 ],top;     if (t==0 ) {putchar ('0' );return ;}     if (t<0 ) {t=-t;putchar ('-' );}     while (t) stk[++top]=t%10 ,t/=10 ;     while (top) putchar (stk[top--]+'0' ); } 
 
对拍 Windows 1 2 3 4 5 6 7 8 @echo  off :loop     rand.exe>data.in      std.exe<data.in >std.out     my.exe<data.in >my.out     fc my.out std.out if  not  errorlevel  1  goto  looppause 
 
Linux 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 cs=1 while  true do     cs=$(($cs +1 ))     echo  "Case $cs :"      ./data>data.in     ./my<data.in>my.out     ./std<data.in>std.out     if  diff my.out std.out; then          echo  "AC\n"      else          echo  "WA\n"          exit  0     fi  done 
 
字符串 最小表示法 找出与一个字符串循环同构的串中字典序最小的串 
考虑对于一对字符串 $A,B$,它们在原字符串 $S$ 中的起始位置分别为 $i,j$,且它们的前 $k$ 个字符均相同,即:
$A[i\cdots i+k-1]=B[j\cdots j+k-1]$
不妨先考虑 $A[i+k]>B[j+k]$ 的情况
我们发现起始位置下标 $l$ 满足 $i\le l\le i+k$ 的字符串均不能成为答案。因为对于任意一个字符串 $A_{i+p}$(表示以 $i+p$ 为起始位置的字符串)一定存在字符串 $B_{j+p}$ 比它更优
所以我们比较时可以跳过下标 $l \in  [i,i+k]$,直接比较 $A_{i+k+1}$
时间复杂度: $O(n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int  get_min (const  string &s)   {    int  k = 0 , i = 0 , j = 1 , len = SZ (s);     while  (k < len && i < len && j < len) {         if  (s[(i + k) % len] == s[(j + k) % len]) {             ++k;         } else  {             s[(i + k) % len] > s[(j + k) % len] ? i = i + k + 1  : j = j + k + 1 ;             if  (i == j) {                 ++i;             }             k = 0 ;         }     }     return  min (i, j); } 
 
KMP 
字符串下标从 $0$ 开始,前 $i$ 位的的最小循环节长度为 $i-next_i$,若 $i\mid (i-next_i)$,则前 $i$ 位 的循环周期为 $i/(i-next_i)$。 
 
1 2 3 4 5 6 7 8 9 10 11 void  get_next (const  string &t)   {     int  i = 0 , j = -1 ;     nxt[0 ] = -1 ;     while  (i < SZ (t)) {         if  (j == -1  || t[i] == t[j]) {             nxt[++i] = ++j;         } else  {             j = nxt[j];         }     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 void  get_next (const  string &t)   {    int  i = 0 , j = -1 ;     nxt[0 ] = -1 ;     while  (i < SZ (t)) {         if  (j == -1  || t[i] == t[j]) {             ++i, ++j;             if  (t[i] != t[j]) {                 nxt[i] = j;             }             else  {                 nxt[i] = nxt[j];             }         } else  {             j = nxt[j];         }     } } void  kmp (const  string &s, const  string &t)   {    get_next (t);     int  i = 0 , j = 0 ;     while  (i < SZ (s)) {         if  (j == -1  || s[i] == t[j]) {             ++i, ++j;             if  (j == SZ (t)) {                 cout << i - j + 1  << '\n' ;                 j = nxt[j];              }         } else  {             j = nxt[j];         }     } } 
 
扩展 KMP 
求 $S$ 的每一个后缀与 $T$ 的最长公共前缀 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int  nxt[N]; int  extend[N]; void  get_next (const  string &t)   {    int  l = 0 , r = 0 ;      nxt[0 ] = SZ (t);     for  (int  i = 1 ; i < SZ (t); i++) {         if  (i > r || i + nxt[i - l] >= r) {             if  (i > r) {                 r = i;             }             while  (r < SZ (t) && t[r] == t[r - i]) {                 ++r;              }             nxt[i] = r - i;             l = i;         } else  {             nxt[i] = nxt[i - l];         }     } } void  get_extend (const  string &s, const  string &t)   {    get_next (t);     int  l = 0 , r = 0 ;     for  (int  i = 0 ; i < SZ (s); i++) {         if  (i > r || i + nxt[i - l] >= r) {             if  (i > r) {                 r = i;             }             while  (r < SZ (s) && r - i < SZ (t) && s[r] == t[r - i]) {                 ++r;             }             extend[i] = r - i;             l = i;         } else  {             extend[i] = nxt[i - l];         }     } } 
 
Manacher 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 char  t[N<<1 ];int  len[N<<1 ];int  manacher (const  string &s)   {    int  k = 0 , mid = 0 , r = 0 , res = 0 ;     t[0 ] = '$' ;     for  (auto  c : s) {         t[++k] = '#' ;         t[++k] = c;     }     t[++k] = '#' ;     for  (int  i = 1 ; i <= k; i++) {         len[i] = i < r ? min (r - i, len[2  * mid - i]) : 1 ;         while  (i - len[i] >= 1  && i + len[i] <= k && t[i - len[i]] == t[i + len[i]]) {             ++len[i];         }         if  (len[i] + i > r) {             r = len[i] + i;             mid = i;             res = max (res, len[i]);          }     }     return  res - 1 ;  } 
 
Trie 树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int  tr[N][26 ], tot[N], sz;void  insert (const  string &s)   {    int  u = 0 ;     for  (auto  c : s) {         int  v = c - 'a' ;         if  (!tr[u][v]) {             tr[u][v] = ++sz;         }         u = tr[u][v];     }     tot[u]++; } int  find (const  string &s)   {    int  u = 0 ;     for  (auto  c : s) {         int  v = c - 'a' ;         if  (!tr[u][v]) {             return  0 ;         }         u = tr[u][v];     }     return  tot[u];  } 
 
AC 自动机 
多组数据字符集大小不一样时,初始化按最大字符集大小处理 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 struct  AC  {    int  tr[N][26 ],fail[N],sz;      void  init ()   {         for (int  i=0 ;i<=sz;i++) {             for (int  j=0 ;j<26 ;j++) tr[i][j]=0 ;         	fail[i]=0 ;         }         sz=0 ;     }     void  insert (const  string &s)   {         int  u=0 ;         for (auto  c:s) {             int  v=c-'a' ;             if (!tr[u][v]) tr[u][v]=++sz;             u=tr[u][v];         }     }     void  build ()   {         queue<int > q;         for (int  v=0 ;v<26 ;v++) if (tr[0 ][v]) q.push (tr[0 ][v]);         while (SZ (q)) {             int  u=q.front ();             q.pop ();             for (int  v=0 ;v<26 ;v++) {                 if (tr[u][v]) fail[tr[u][v]]=tr[fail[u]][v],q.push (tr[u][v]);                 else  tr[u][v]=tr[fail[u]][v];             }         }     } } ac; 
 
Hash 字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const  LL HASH_MOD[2 ]={1000000007 ,1000000009 };const  LL HASH_BASE=13331 ;LL ha[2 ][N],power[2 ][N]; void  hash_init (const  string &s)   {    power[0 ][0 ]=power[1 ][0 ]=1 ;     for (int  i=1 ;i<=SZ (s);i++) {          ha[0 ][i]=(ha[0 ][i-1 ]*HASH_BASE%HASH_MOD[0 ]+s[i-1 ])%HASH_MOD[0 ];         ha[1 ][i]=(ha[1 ][i-1 ]*HASH_BASE%HASH_MOD[1 ]+s[i-1 ])%HASH_MOD[1 ];         power[0 ][i]=power[0 ][i-1 ]*HASH_BASE%HASH_MOD[0 ];         power[1 ][i]=power[1 ][i-1 ]*HASH_BASE%HASH_MOD[1 ]; } pair<LL,LL> get_hash (int  l,int  r)   {    return  MP (         (ha[0 ][r]+HASH_MOD[0 ]-ha[0 ][l-1 ]*power[0 ][r-l+1 ]%HASH_MOD[0 ])%HASH_MOD[0 ],         (ha[1 ][r]+HASH_MOD[1 ]-ha[1 ][l-1 ]*power[1 ][r-l+1 ]%HASH_MOD[1 ])%HASH_MOD[1 ]     ); } 
 
后缀数组 
两子串最长公共前缀:$lcp(sa_i,sa_j)=\min{height_{i+1\dots j}}$
 
两个子串的大小关系:
假设需要比较的是 $A=S_{a\dots b}$ 和 $B=S_{c\dots d}$ 的大小关系
若 $lcp(a,c)\ge \min(|A|,|B|)$,$A<b \Longleftrightarrow |A|<|B|$
否则,$A<b \Longleftrightarrow rk_a<rk_b$
 
不同子串的数目:$\frac{n(n+1)}{2}-\sum_{i=2}^{n}height_i$
 
合并多个字符串中间用没有出现过的字符隔开
 
多组数据时,注意初始化 $s_{n+1}=0$
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 int  n,s[N],sa[N],id[N],rk[N],oldrk[N<<1 ],cnt[N],height[N];void  DA ()   {    int  m=200 ;      for (int  i=1 ;i<=m;i++) cnt[i]=0 ;     for (int  i=1 ;i<=n;i++) ++cnt[rk[i]=s[i]];     for (int  i=1 ;i<=m;i++) cnt[i]+=cnt[i-1 ];     for (int  i=n;i>=1 ;i--) sa[cnt[rk[i]]--]=i;     for (int  w=1 ,p;w<n;w<<=1 ,m=p) {         p=0 ;         for (int  i=n;i>n-w;i--) id[++p]=i;         for (int  i=1 ;i<=n;i++) if (sa[i]>w) id[++p]=sa[i]-w;         for (int  i=1 ;i<=m;i++) cnt[i]=0 ;         for (int  i=1 ;i<=n;i++) ++cnt[rk[id[i]]];         for (int  i=1 ;i<=m;i++) cnt[i]+=cnt[i-1 ];         for (int  i=n;i>=1 ;i--) sa[cnt[rk[id[i]]]--]=id[i];         for (int  i=1 ;i<=n;i++) oldrk[i]=rk[i];         p=0 ;         for (int  i=1 ;i<=n;i++)             rk[sa[i]]=(oldrk[sa[i]]==oldrk[sa[i-1 ]]&&oldrk[sa[i]+w]==oldrk[sa[i-1 ]+w])?p:++p;     } } void  get_height ()   {    for (int  i=1 ,t=0 ;i<=n;i++) {         if (t) t--;         while (s[i+t]==s[sa[rk[i]-1 ]+t]) t++;         height[rk[i]]=t;     } } bool  check (int  k)   {     int  mn=0 ,mx=0 ;     for (int  i=1 ;i<=n;i++) {         if (height[i]<k) mn=sa[i],mx=sa[i];         else  {             mn=min (mn,sa[i]);             mx=max (mx,sa[i]);             if (mx-mn>=k) return  true ;         }     }     return  false ; } 
 
后缀自动机 
字符集过大,使用 map
 
不同子串个数:$\sum_{i=1}^{sz}(len_i-len_{link_i})$
可以在线计算,每插入一个字符的贡献是:$len_{last}-len_{link_{last}}$
 
不同子串的总长度:$\sum_{i=1}^{sz}(\frac{len_i(len_i+1)}{2}-\frac{len_{link_i}(len_{link_i}+1)}{2})$
可以在线计算,每插入一个字符的贡献是:$\frac{len_{last}(len_{last}+1)}{2}-\frac{len_{link_{last}}(len_{link_{last}}+1)}{2}$
 
出现次数:将不是复制创建的非初始状态的 $cnt$ 初始化为 $1$,再让 $cnt_{link_v}+=cnt_v$
 
字典序第 $k$ 小子串:根据 $nxt$ 处理子树大小,若求的是去重后的第 $k$ 小,让子树大小初始值为 $1$,否则为该状态出现次数,然后 dfs 求出第 $k$ 小
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 int  n,sz,last,len[N<<1 ],link[N<<1 ],nxt[N<<1 ][26 ],tot[N<<1 ]; void  sam_init ()   {	link[0 ]=-1 ; } void  sam_insert (int  x)   {    int  cur=++sz,p=last;     len[cur]=len[last]+1 ;     tot[cur]=1 ;     while (p!=-1 &&!nxt[p][x]) nxt[p][x]=cur,p=link[p];     if (p==-1 ) link[cur]=0 ;     else  {         int  q=nxt[p][x];         if (len[p]+1 ==len[q]) link[cur]=q;         else  {             int  t=++sz;             len[t]=len[p]+1 ;             for (int  i=0 ;i<26 ;i++) nxt[t][i]=nxt[q][i];             link[t]=link[q];             while (p!=-1 &&nxt[p][x]==q) nxt[p][x]=t,p=link[p];             link[q]=link[cur]=t;         }     }     last=cur; } int  c[N],a[N<<1 ]; void  topo (int  t)   {    for (int  i=1 ;i<=n;i++) c[i]=0 ;     for (int  i=1 ;i<=sz;i++) ++c[len[i]];     for (int  i=1 ;i<=n;i++) c[i]+=c[i-1 ];     for (int  i=sz;i>=1 ;i--) a[c[len[i]]--]=i;     for (int  i=sz;i>=1 ;i--) tot[link[a[i]]]+=tot[a[i]]; } string lcs (const  string &S,const  string &T)   {     n=SZ (S);     sam_init ();     for (int  i=0 ;i<n;i++) sam_insert (S[i]);     int  v=0 ,l=0 ,mx=0 ,mxpos=0 ;     for (int  i=0 ;i<SZ (T);i++) {         while (v&&!nxt[v].count (T[i])) v=link[v],l=len[v];         if (nxt[v].count (T[i])) v=nxt[v][T[i]],++l;         if (l>mx) mx=l,mxpos=i;     }     return  T.substr (mxpos-mx+1 ,mx); } 
 
广义后缀自动机 
多个字符串的最长公共子串长度:记录每个状态是否在某个字符串中存在,通过 link 转移 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 int  n,sz,len[N*M*2 ],nxt[N*M*2 ][26 ],link[N*M*2 ];void  trie_insert (const  string &s)   {    n=max (n,SZ (s));     int  rt=0 ;     for (auto  c:s) {         int  now=c-'a' ;         if (!nxt[rt][now]) nxt[rt][now]=++sz;         rt=nxt[rt][now];     } } int  gsa_insert (int  last,int  x)   {    int  cur=nxt[last][x],p=link[last];     len[cur]=len[last]+1 ;     while (p!=-1 &&!nxt[p][x]) nxt[p][x]=cur,p=link[p];     if (p==-1 ) {link[cur]=0 ;return  cur;}     int  q=nxt[p][x];     if (len[p]+1 ==len[q]) {link[cur]=q;return  cur;}     int  t=++sz;     len[t]=len[p]+1 ;     for (int  i=0 ;i<26 ;i++) nxt[t][i]=len[nxt[q][i]]!=0 ?nxt[q][i]:0 ;     link[t]=link[q];     while (p!=-1 &&nxt[p][x]==q) nxt[p][x]=t,p=link[p];     link[q]=link[cur]=t;     return  cur; } void  gsa_build ()   {    link[0 ]=-1 ;     queue<PII> q;     for (int  i=0 ;i<26 ;i++) if (nxt[0 ][i]) q.emplace (0 ,i);     while (SZ (q)) {         auto  u=q.front ();         q.pop ();         auto  last=gsa_insert (u.FI,u.SE);         for (int  i=0 ;i<26 ;i++) if (nxt[last][i]) q.emplace (last,i);     } } int  c[N],a[N*M*2 ];void  gsa_topo ()   {    for (int  i=1 ;i<=n;i++) c[i]=0 ;     for (int  i=1 ;i<=sz;i++) ++c[len[i]];     for (int  i=1 ;i<=n;i++) c[i]+=c[i-1 ];     for (int  i=sz;i>=1 ;i--) a[c[len[i]]--]=i; } 
 
动态规划 背包问题 01 背包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int  n,m,dp[W],w[N],v[N];for (int  i=1 ;i<=n;i++)    for (int  j=m;j>=w[i];j--)         dp[j]=max (dp[j],dp[j-w[i]]+v[i]); dp[0 ]=1 ; for (int  i=1 ;i<=n;i++)    for (int  j=m;j>=w[i];j--)         dp[j]+=dp[j-w[i]]; for (int  i=1 ;i<=n;i++)    for (int  j=w[i];j<=m;j++)         dp[j]-=dp[j-w[i]]; 
 
完全背包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int  n,m,dp[W],w[N],v[N];for (int  i=1 ;i<=n;i++)    for (int  j=w[i];j<=m;j++)         dp[j]=max (dp[j],dp[j-w[i]]+v[i]); dp[0 ]=1 ; for (int  i=1 ;i<=n;i++)    for (int  j=w[i];j<=m;j++)         dp[j]+=dp[j-w[i]]; for (int  i=1 ;i<n+1 ;i++)    for (int  j=m;j>=w[i];j--)         dp[j]-=dp[j-w[i]]; 
 
多重背包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int  n,m,v,w,s,dp[M],pre[M],q[M];for (int  i=1 ;i<=n;i++) {    if (m/v<s) s=m/v;     memcpy (pre,dp,sizeof  dp);     for (int  j=0 ;j<v;j++) {         int  l=1 ,r=0 ;         for (int  k=0 ;k*v+j<=m;k++) {             int  t=dp[k*v+j]-k*w;             while (l<=r&&k-q[l]>s) l++;             while (l<=r&&pre[q[r]*v+j]-q[r]*w<=t) r--;             q[++r]=k;             dp[k*v+j]=pre[q[l]*v+j]-q[l]*w+k*w;         }     } } 
 
分组背包 1 2 3 4 5 6 7 int  n,m,c[N],v[N][N],w[N][N],dp[N];for (int  i=1 ;i<=n;i++)    for (int  j=m;j>=0 ;j--)         for (int  k=1 ;k<=c[i];k++)             if (j-v[i][k]>=0 )                 dp[j]=max (dp[j],dp[j-v[i][k]]+w[i][k]); 
 
区间 DP 有 $n$ 堆石子排成一排每堆石子有一定的质量,现在要将这 $n$ 堆石子合并成为一堆,每次只能合并相邻的两堆,求最小代价
1 2 3 4 5 6 7 int  dp[N][N],sum[N];memset (dp,0x3f ,sizeof  dp);for (int  i=1 ;i<=n;i++) dp[i][i]=0 ;for (int  l=2 ;l<=n;l++)    for (int  i=1 ;j=i+l-1 ;j<=n;i++,j++)         for (int  k=i;k<=j;k++)             dp[i][j]=min (dp[i][j],dp[i][k]+dp[k+1 ][j]+sum[j]-sum[i-1 ]); 
 
状压 DP 
1 2 for (int  i=1 ;i<1 <<n;i++) cnt[i]=cnt[i&(i-1 )]+1 ;
 
1 2 for (int  i=0 ;i<1 <<n;i++)    for (int  j=i;j;j=(j-1 )&i) 
 
数位 DP 
斜率优化 
将状态转移方程化成 $b=-kx+y$ 形式
 
假如经过判断,我们需要维护的是一个下凸包,那么我们需要找的最优决策点为 $k_{l}\le k_x < k_{r}$ 的最小的 $x$
 
$x$ 非严格递增时,求斜率时若 $x$ 相等,加上 return y[a]<=y[b]?LDBL_MAX:LDBL_MIN
 
比较斜率时,出队条件最好加上等于,防止分母出锅
 
根据题意判断队列初始化时是否要加入决策点 $0$
 
$x$ 单调,$k$ 单调:用单调队列维护凸包
 
$x$ 单调,$k$ 非单调:用单调栈维护凸包,每次二分找最佳决策点
 
 
数据结构 并查集 1 2 3 4 5 6 7 8 9 10 11 void  init ()   {    for (int  i=1 ;i<=n;i++) fa[i]=i; } int  find (int  x)   {    return  x==fa[x]?x:fa[x]=find (fa[x]); } void  merge (int  x,int  y)   {    int  tx=find (x),ty=find (y);     if (tx==ty) return ;     fa[tx]=ty; } 
 
1 2 3 4 5 6 7 8 9 10 11 int  find (int  x)   {    if (x==fa[x]) return  x;     int  root=find (fa[x]);     d[x]=    ;      return  fa[x]=root; } void  merge (int  x,int  y)   {    int  tx=find (x),ty=find (y);     fa[tx]=ty;     d[tx]=   ;  } 
 
扩展域:将一个节点拆成为多个节点
如:判断 $x,y$ 奇偶性
可将节点分为奇数域和偶数域:odd=x,even=x+n;
若 $x$ 和 $y$ 奇偶性相同:get(odd)=get(odd),get(even)=get(even);
若 $x$ 和 $y$ 奇偶性不同:get(odd)=get(even),get(even)=get(odd);
ST 表 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int  a[N], mx[25 ][N];int  hightbit (int  x)   {    return  31  - __builtin_clz(x); } void  init (int  n)   {    for  (int  i = 1 ; i <= n; i++) {         mx[0 ][i]=a[i];     }     int  t = hightbit (n);     for  (int  i = 1 ; i <= t; i++) {         for (int  j = 1 ; j + (1  << i) - 1  <= n; j++) {             mx[i][j] = max (mx[i - 1 ][j], mx[i - 1 ][j + (1  << (i - 1 ))]);         }     } } int  query (int  l, int  r)   {    int  k = hightbit (r - l + 1 );     return  max (mx[k][l], mx[k][r - (1  << k) + 1 ]); } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int  a[N][N],mx[N][N][10 ][10 ];int  hightbit (int  x)   {return  31 -__builtin_clz(x);}void  init (int  n,int  m)   {    int  t1=hightbit (n);     int  t2=hightbit (m);     for (int  i=0 ;i<=t1;i++)         for (int  j=0 ;j<=t2;j++)             for (int  k=1 ;k<=n-(1 <<i)+1 ;k++)                 for (int  l=1 ;l<=m-(1 <<j)+1 ;l++) {                     if (!i&&!j) mx[k][l][i][j]=a[k][l];                     else  if (!i) mx[k][l][i][j]=max (mx[k][l][i][j-1 ],mx[k][l+(1 <<(j-1 ))][i][j-1 ]);                     else  mx[k][l][i][j]=max (mx[k][l][i-1 ][j],mx[k+(1 <<(i-1 ))][l][i-1 ][j]);                 } } int  query (int  x1,int  y1,int  x2,int  y2)   {    int  t1=hightbit (x2-x1+1 );     int  t2=hightbit (y2-y1+1 );     return  max ({mx[x1][y1][t1][t2],mx[x2-(1 <<t1)+1 ][y1][t1][t2],                 mx[x1][y2-(1 <<t2)+1 ][t1][t2],mx[x2-(1 <<t1)+1 ][y2-(1 <<t2)+1 ][t1][t2]}); } 
 
树状数组 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int  n,b[N];void  update (int  x,int  v)   {    for (int  i=x;i<=n;i+=i&-i) b[i]+=v; } int  query (int  x)   {    int  ret=0 ;     for (int  i=x;i;i-=i&-i) ret+=b[i];     return  ret; } int  query (int  l,int  r)   {    int  ret=0 ;     for (int  i=r;i;i-=i&-i) ret+=b[i];     for (int  i=l-1 ;i;i-=i&-i) ret-=b[i];     return  ret; } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int  n,b[N],c[N];void  update (int  l,int  r,int  v)   {    for (int  i=l;i<=n;i+=i&-i) b[i]+=v,c[i]+=l*v;     for (int  i=r+1 ;i<=n;i+=i&-i) b[i]-=v,c[i]-=(r+1 )*v; } int  query (int  x)   {    int  ret=0 ;     for (int  i=x;i;i-=i&-i) ret+=(x+1 )*b[i]-c[i];     return  ret; } int  query (int  l,int  r)   {    int  ret=0 ;     for (int  i=r;i;i-=i&-i) ret+=(r+1 )*b[i]-c[i];     for (int  i=l-1 ;i;i-=i&-i) ret-=l*b[i]-c[i];     return  ret; } 
 
线段树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 int  n,a[N],ls[N<<2 ],rs[N<<2 ],w[N<<2 ],add[N<<2 ];void  build (int  p,int  l,int  r)   {    ls[p]=l,rs[p]=r;     if (l==r) {w[p]=a[l];return ;}     int  mid=l+r>>1 ;     build (p<<1 ,l,mid);     build (p<<1 |1 ,mid+1 ,r);     w[p]=w[p<<1 ]+w[p<<1 |1 ]; } void  push_down (int  p)   {    if (add[p]) {         w[p<<1 ]+=(rs[p<<1 ]-ls[p<<1 ]+1 )*add[p];         w[p<<1 |1 ]+=(rs[p<<1 |1 ]-ls[p<<1 |1 ]+1 )*add[p];         add[p<<1 ]+=add[p],add[p<<1 |1 ]+=add[p];         add[p]=0 ;     } } void  update (int  p,int  l,int  r,int  v)   {    if (ls[p]>=l&&rs[p]<=r) {         w[p]+=(rs[p]-ls[p]+1 )*v;         add[p]+=v;         return ;     }     push_down (p);     int  mid=ls[p]+rs[p]>>1 ;     if (l<=mid) update (p<<1 ,l,r,v);     if (r>mid) update (p<<1 |1 ,l,r,v);     w[p]=w[p<<1 ]+w[p<<1 |1 ]; } int  query (int  p,int  l,int  r)   {    if (ls[p]>=l&&rs[p]<=r) return  w[p];     push_down (p);     int  mid=ls[p]+rs[p]>>1 ;     if (r<=mid) return  query (p<<1 ,l,r);     if (l>mid) return  query (p<<1 |1 ,l,r);     return  query (p<<1 ,l,r)+query (p<<1 |1 ,l,r); } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int  n,a[N],ls[N<<2 ],rs[N<<2 ],w[N<<2 ],add[N<<2 ];void  build (int  p,int  l,int  r)   {    ls[p]=l,rs[p]=r;     if (l==r) {w[p]=a[l];return ;}     int  mid=l+r>>1 ;     build (p<<1 ,l,mid);     build (p<<1 |1 ,mid+1 ,r);     w[p]=w[p<<1 ]+w[p<<1 |1 ]; } void  update (int  p,int  l,int  r,int  v)   {    w[p]+=(min (rs[p],r)-max (ls[p],l)+1 )*v;     if (ls[p]>=l&&rs[p]<=r) {         add[p]+=v;         return ;     }     int  mid=ls[p]+rs[p]>>1 ;     if (l<=mid) update (p<<1 ,l,r,v);     if (r>mid) update (p<<1 |1 ,l,r,v); } int  query (int  p,int  l,int  r)   {    if (ls[p]>=l&&rs[p]<=r) return  w[p];     int  mid=ls[p]+rs[p]>>1 ;     int  ret=(r-l+1 )*add[p];     if (r<=mid) return  ret+query (p<<1 ,l,r);     if (l>mid) return  ret+query (p<<1 |1 ,l,r);     return  ret+query (p<<1 ,l,mid)+query (p<<1 |1 ,mid+1 ,r); } 
 
可持久化线段树 
区间修改,有两种方法,lazy 标记下传和标记永久化,前者空间需开大。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include  <cstdio>  using  namespace  std;const  int  N=1e5 +5 ;int  n,m,a[N];int  ls[N<<5 ],rs[N<<5 ],sum[N<<5 ],rt[N],cnt;void  update (int  &p,int  q,int  L,int  R,int  x)   {    p=++cnt;     ls[p]=ls[q],rs[p]=rs[q],sum[p]=sum[q]+1 ;     if (L==R) return ;     int  mid=L+R>>1 ;     if (x<=mid) update (ls[p],ls[q],L,mid,x);     else  update (rs[p],rs[q],mid+1 ,R,x); } int  query (int  p,int  q,int  L,int  R,int  k)   {    if (L==R) return  L;     int  mid=L+R>>1 ;     int  lsum=sum[ls[q]]-sum[ls[p]];     if (k<=lsum) return  query (ls[p],ls[q],L,mid,k);     else  return  query (rs[p],rs[q],mid+1 ,R,k-lsum); } int  main ()   {    scanf ("%d%d" ,&n,&m);     for (int  i=1 ;i<=n;i++) scanf ("%d" ,&a[i]);     for (int  i=1 ;i<=n;i++) update (rt[i],rt[i-1 ],-1e9 ,1e9 ,a[i]);     for (int  i=1 ;i<=m;i++) {         int  l,r,k;         scanf ("%d%d%d" ,&l,&r,&k);         printf ("%d\n" ,query (rt[l-1 ],rt[r],-1e9 ,1e9 ,k));     }     return  0 ; } 
 
平衡树 Splay 树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 struct  Splay  {    int  rt,cnt,ch[N][2 ],sz[N],tot[N],val[N],fa[N];     bool  get (int  x)   {return  x==ch[fa[x]][1 ];}     void  clear (int  x)   {ch[x][0 ]=ch[x][1 ]=fa[x]=val[x]=sz[x]=tot[x]=0 ;}     void  push_up (int  x)   {sz[x]=sz[ch[x][0 ]]+sz[ch[x][1 ]]+tot[x];}     void  rotate (int  x)   {         int  y=fa[x],z=fa[y],chk=get (x);         ch[y][chk]=ch[x][chk^1 ];         if (ch[x][chk^1 ]) fa[ch[x][chk^1 ]]=y;         ch[x][chk^1 ]=y;         fa[y]=x,fa[x]=z;         if (z) ch[z][y==ch[z][1 ]]=x;         push_up (y);     }     void  splay (int  x)   {         for (int  f=fa[x];f=fa[x],f;rotate (x)) if (fa[f])             rotate (get (x)==get (f)?f:x);         push_up (x);         rt=x;     }     void  insert (int  x)   {         if (!rt) {             val[++cnt]=x;             ++tot[cnt];             rt=cnt;             push_up (rt);             return ;         }         int  cur=rt,f=0 ;         for (;;) {             if (val[cur]==x) {                 ++tot[cur];                 push_up (cur),push_up (f);                 splay (cur);                 break ;             }             f=cur;             cur=ch[cur][val[cur]<x];             if (!cur) {                 val[++cnt]=x;                 ++tot[cnt];                 fa[cnt]=f;                 ch[f][val[f]<x]=cnt;                 push_up (cnt),push_up (f);                 splay (cnt);                 break ;             }         }     }     int  rk (int  x)   {          int  res=0 ,cur=rt;         for (;;) {             if (x<val[cur]) cur=ch[cur][0 ];             else  {                 res+=sz[ch[cur][0 ]];                 if (x==val[cur]) break ;                 res+=tot[cur];                 cur=ch[cur][1 ];             }         }         splay (cur);         return  res+1 ;     }     int  kth (int  k)   {         int  cur=rt;         for (;;) {             if (ch[cur][0 ]&&k<=sz[ch[cur][0 ]]) cur=ch[cur][0 ];             else  {                 k-=tot[cur]+sz[ch[cur][0 ]];                 if (k<=0 ) break ;                 cur=ch[cur][1 ];             }         }         splay (cur);         return  val[cur];     }     int  pre ()   {         int  cur=ch[rt][0 ];         while (ch[cur][1 ]) cur=ch[cur][1 ];         splay (cur);         return  cur;     }     int  nxt ()   {         int  cur=ch[rt][1 ];         while (ch[cur][0 ]) cur=ch[cur][0 ];         splay (cur);         return  cur;     }     void  del (int  x)   {         rk (x);         if (!ch[rt][0 ]||!ch[rt][1 ]) {             int  cur=rt;             fa[rt=ch[rt][0 ]+ch[rt][1 ]]=0 ;             clear (cur);         }         else  {                          int  cur=rt;             x=pre ();             fa[ch[cur][1 ]]=x;             ch[x][1 ]=ch[cur][1 ];             clear (cur);             push_up (rt);         }     }     int  get_pre (int  x)   {         insert (x);          int  ret=pre ();         del (x);          return  ret;       }     int  get_nxt (int  x)   {         insert (x);           int  ret=nxt ();         del (x);          return  ret;      } }splay; 
 
平衡树维护区间翻转 
LibreOJ 104. 文艺平衡树 
 
对于区间 $[l,r]$,将 $l-1$ 上旋至树根,$r+1$ 上旋至树根的右子树,那么树根的右子树的左子树就是区间 $[l,r]$,将其标记,注意下放标记并且交换左右子树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #include  <bits/stdc++.h>  using  namespace  std;const  int  N=1e5 +5 ;int  n,m;struct  Splay  {    int  rt,cnt,ch[N][2 ],sz[N],fa[N],mark[N];     bool  get (int  x)   {return  x==ch[fa[x]][1 ];}     void  push_up (int  x)   {sz[x]=sz[ch[x][0 ]]+sz[ch[x][1 ]]+1 ;}     void  push_down (int  x)   {         if (mark[x]) {             mark[ch[x][0 ]]^=1 ;             mark[ch[x][1 ]]^=1 ;             mark[x]=0 ;             swap (ch[x][0 ],ch[x][1 ]);         }     }     void  build (int  l,int  r,int  f)   {         if (l>r) return ;         int  mid=l+r>>1 ;         ch[mid][0 ]=ch[mid][1 ]=0 ;         if (f==-1 ) rt=mid,fa[rt]=0 ;         else  ch[f][r>f]=mid,fa[mid]=f;         build (l,mid-1 ,mid);         build (mid+1 ,r,mid);         push_up (mid);     }     void  rotate (int  x)   {         int  y=fa[x],z=fa[y];         push_down (y);         push_down (x);         int  chk=get (x);         ch[y][chk]=ch[x][chk^1 ];         if (ch[x][chk^1 ]) fa[ch[x][chk^1 ]]=y;         ch[x][chk^1 ]=y;         fa[y]=x,fa[x]=z;         if (z) ch[z][y==ch[z][1 ]]=x;         push_up (y);     }     void  splay (int  x,int  t)   {         push_down (x);         for (int  f=fa[x];f=fa[x],f!=t;rotate (x)) if (fa[f]!=t)             rotate (get (x)==get (f)?f:x);         push_up (x);         if (!t) rt=x;     }     int  kth (int  k)   {         int  cur=rt;         for (;;) {             push_down (cur);             if (ch[cur][0 ]&&k<=sz[ch[cur][0 ]]) cur=ch[cur][0 ];             else  {                 k-=1 +sz[ch[cur][0 ]];                 if (k<=0 ) break ;                 cur=ch[cur][1 ];             }         }         splay (cur,0 );         return  cur;     }     void  reverse (int  l,int  r)   {         l=kth (l);         r=kth (r+2 );         splay (l,0 );         splay (r,l);         mark[ch[ch[rt][1 ]][0 ]]^=1 ;     }     void  print (int  u)   {         if (u) {             push_down (u);             print (ch[u][0 ]);             if (u>1 &&u<n+2 ) cout<<u-1 <<' ' ;             print (ch[u][1 ]);         }     } }splay; int  main ()   {    cin>>n>>m;     splay.build (1 ,n+2 ,-1 );     for (int  i=1 ;i<=m;i++) {         int  l,r;         cin>>l>>r;         splay.reverse (l,r);     }     splay.print (splay.rt);     cout<<'\n' ;     return  0 ; } 
 
树套树 树状数组套可持久化线段树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #include  <bits/stdc++.h>  #define  SZ(x) (int)(x).size() #define  ALL(x) (x).begin(),(x).end() #define  PB push_back using  namespace  std;typedef  vector<int > VI;const  int  N=1e5 +5 ;int  n,m,a[N],cnt,rt[N],ls[N*2 *18 *18 ],rs[N*2 *18 *18 ],sum[N*2 *18 *18 ],tot1,tot2,t1[18 ],t2[18 ];VI b; struct  Q  {    char  o;     int  l,r,k;     int  x,v; }q[N]; int  find (int  x)   {    return  lower_bound (ALL (b),x)-b.begin ()+1 ; } void  update (int  &p,int  l,int  r,int  x,int  v)   {    if (!p) p=++cnt;     sum[p]+=v;     if (l==r) return ;     int  mid=l+r>>1 ;     if (x<=mid) update (ls[p],l,mid,x,v);     else  update (rs[p],mid+1 ,r,x,v); } int  query (int  l,int  r,int  k)   {    if (l==r) return  l;     int  mid=l+r>>1 ,sz=0 ;     for (int  i=1 ;i<=tot1;i++) sz-=sum[ls[t1[i]]];     for (int  i=1 ;i<=tot2;i++) sz+=sum[ls[t2[i]]];     if (sz>=k) {         for (int  i=1 ;i<=tot1;i++) t1[i]=ls[t1[i]];         for (int  i=1 ;i<=tot2;i++) t2[i]=ls[t2[i]];         return  query (l,mid,k);     }     else  {         for (int  i=1 ;i<=tot1;i++) t1[i]=rs[t1[i]];         for (int  i=1 ;i<=tot2;i++) t2[i]=rs[t2[i]];         return  query (mid+1 ,r,k-sz);     } } int  lowbit (int  x)   {    return  x&-x; } void  upd (int  p,int  x,int  v)   {    for (;p<=n;p+=lowbit (p)) update (rt[p],1 ,SZ (b),x,v); } int  qry (int  l,int  r,int  k)   {    tot1=tot2=0 ;     for (;l;l-=lowbit (l)) t1[++tot1]=rt[l];     for (;r;r-=lowbit (r)) t2[++tot2]=rt[r];     return  query (1 ,SZ (b),k); } int  main ()   {    scanf ("%d%d" ,&n,&m);     for (int  i=1 ;i<=n;i++) {         scanf ("%d" ,&a[i]);         b.PB (a[i]);     }     for (int  i=1 ;i<=m;i++) {         scanf (" %c" ,&q[i].o);         if (q[i].o=='C' ) scanf ("%d%d" ,&q[i].x,&q[i].v),b.PB (q[i].v);         else  scanf ("%d%d%d" ,&q[i].l,&q[i].r,&q[i].k);     }     sort (ALL (b));     b.resize (unique (ALL (b))-b.begin ());     for (int  i=1 ;i<=n;i++) upd (i,find (a[i]),1 );     for (int  i=1 ;i<=m;i++) {         if (q[i].o=='C' ) {             upd (q[i].x,find (q[i].v),1 );             upd (q[i].x,find (a[q[i].x]),-1 );             a[q[i].x]=q[i].v;         }         else  printf ("%d\n" ,b[qry (q[i].l-1 ,q[i].r,q[i].k)-1 ]);     }     return  0 ; } 
 
莫队 普通莫队 对于长度为 $n$ 的序列上的 $m$ 次区间询问问题,如果从 $[l,r]$ 的答案能够 $O(1)$ 扩展到 $[l-1,r],[l+1,r],[l,r-1],[l,r+1]$ 的答案,可以在 $O(n\sqrt m)$ 的复杂度内求出所有询问的答案。
实现:离线后排序,顺序处理每个询问,暴力从上一个区间的答案转移到下一个区间答案(一步一步移动即可)。
排序方法:设定块的长度为 $S$,取 $S=\lceil\frac{n}{\sqrt m}\rceil$,按照 $(\lfloor\frac l {S}\rfloor,r)$ 的二元组从小到大排序。
奇偶优化:设块的编号从 $1$ 开始,对于属于奇数块的询问,$r$ 按从小到大排序,对于属于偶数块的排序,$r$ 从大到小排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 int  n,m,unit,ans,a[30005 ],cnt[1000005 ],res[200005 ];struct  Q  {    int  l,r,id;     Q () {}     Q (int  l,int  r,int  id):l (l),r (r),id (id) {}     bool  operator  < (const  Q &T) const  {         if (l/unit!=T.l/unit) return  l<T.l;         if ((l/unit)&1 ) return  r>T.r;         return  r<T.r;     } } q[200005 ]; void  move (int  x,int  v)   {    if (v==-1 &&--cnt[a[x]]==0 ) --ans;     if (v==1 &&++cnt[a[x]]==1 ) ++ans; } void  mo ()   {    unit=int (ceil (n/pow (n,0.5 )));     sort (q,q+m);     int  l=0 ,r=-1 ;     for (int  i=0 ;i<m;i++) {         while (l>q[i].l) move (--l,1 );         while (r<q[i].r) move (++r,1 );         while (l<q[i].l) move (l++,-1 );         while (r>q[i].r) move (r--,-1 );         res[q[i].id]=ans;     } } int  main ()   {    ios::sync_with_stdio (false );     cin.tie (nullptr );     cin>>n;     for (int  i=0 ;i<n;i++) cin>>a[i];     cin>>m;     for (int  i=0 ;i<m;i++) {         cin>>q[i].l>>q[i].r;         --q[i].l,--q[i].r;         q[i].id=i;     }     mo ();     for (int  i=0 ;i<m;i++) cout<<res[i]<<'\n' ;     return  0 ; } 
 
带修莫队 因为多了修改操作,所以在普通莫队的基础上多加一个时间轴 $t$,可以在 $O(\sqrt[3]{n^4t})$ 的复杂度内求出所有询问的答案。
排序方法:设定块的长度为 $S$,取 $S=\lceil\sqrt[3]{nt}\rceil$,按照 $(\lfloor\frac l {S}\rfloor, \lfloor\frac r {S}\rfloor, t)$ 的三元组从小到大排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 int  n,m,unit,t=1 ,ans,a[10005 ],cnt[1000005 ],pre[10005 ],res[10005 ];struct  R  {    int  x,v,pre;     R () {}     R (int  x,int  v,int  pre):x (x),v (v),pre (pre) {} } b[1005 ]; struct  Q  {    int  l,r,t,id;     Q () {}     Q (int  l,int  r,int  t,int  id):l (l),r (r),t (t),id (id) {}     bool  operator  < (const  Q &T) const  {         if (l/unit!=T.l/unit) return  l<T.l;         if (r/unit!=T.l/unit) return  r<T.r;         return  t<T.t;     } }; vector<Q> q; void  update_t (int  t,int  l,int  r,int  v)   {    if (v==1 ) {         if (b[t].x>=l&&b[t].x<=r) {             if (--cnt[b[t].pre]==0 ) --ans;             if (++cnt[b[t].v]==1 ) ++ans;         }         a[b[t].x]=b[t].v;     }     else  {         if (b[t].x>=l&&b[t].x<=r) {             if (--cnt[b[t].v]==0 ) --ans;             if (++cnt[b[t].pre]==1 ) ++ans;         }         a[b[t].x]=b[t].pre;     } } void  update (int  x,int  v)   {    if (v==-1 &&--cnt[a[x]]==0 ) --ans;     if (v==1 &&++cnt[a[x]]==1 ) ++ans; } void  mo ()   {    unit=int (ceil (pow (n*t,1.0 /3 )));     sort (ALL (q));     int  t=1 ,l=0 ,r=-1 ;     for (auto  x:q) {         while (t<x.t) update_t (++t,l,r,1 );         while (t>x.t) update_t (t--,l,r,-1 );         while (l>x.l) update (--l,1 );         while (r<x.r) update (++r,1 );         while (l<x.l) update (l++,-1 );         while (r>x.r) update (r--,-1 );         res[x.id]=ans;     }     for (int  i=0 ;i<SZ (q);i++) cout<<res[i]<<'\n' ; } int  main ()   {    ios::sync_with_stdio (false );     cin.tie (nullptr );     cin>>n>>m;     for (int  i=0 ;i<n;i++) {         cin>>a[i];         pre[i]=a[i];     }     int  cnt_q=0 ;     for (int  i=0 ;i<m;i++) {         char  o;cin>>o;         if (o=='R' ) {             int  x,v;cin>>x>>v;             --x;             b[++t]=R (x,v,pre[x]);             pre[x]=v;         }         else  {             int  l,r;cin>>l>>r;             --l,--r;             q.EB (l,r,t,cnt_q++);         }     }     mo ();     return  0 ; } 
 
图论 最短路 Dijkstra 算法 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int  dist[N];bool  st[N];VPII g[N]; void  dijkstra ()   {    memset (dist,0x3f ,sizeof  dist);     dist[1 ]=0 ;     priority_queue<PII,VPII,greater<PII>> q;     q.emplace (0 ,1 );     while (SZ (q)) {         int  u=q.top ().SE;         q.pop ();         if (st[u]) continue ;         st[u]=true ;         for (auto  x:g[u]) {             int  v=x.FI,w=x.SE;             if (!st[v]&&dist[u]+w<dist[v]) {                 dist[v]=dist[u]+w;                 q.emplace (dist[v],v);             }         }     } } 
 
SPFA 算法 
一条最短路最多有 $n-1$ 条边组成,若经过 $n-1$ 次更新后还能更新,则存在负环 
LLL 优化:将普通队列换成双端队列,每次将入队结点距离和队内距离平均值比较,如果更大则插入至队尾,否则插入队首 
SLF 优化:将普通队列换成双端队列,每次将入队结点距离和队首比较,如果更大则插入至队尾,否则插入队首 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int  dist[N];bool  st[N];VPII g[N]; void  SPFA ()   {    memset (dist,0x3f ,sizeof  dist);     dist[1 ]=0 ;     queue<int > q;     q.push (1 );     st[1 ]=true ;     while (SZ (q)) {         int  u=q.front ();         q.pop ();         st[u]=false ;         for (auto  x:g[u]) {             int  v=x.FI,w=x.SE;             if (dist[u]+w<dist[v]) {                 dist[v]=dist[u]+w;                 if (!st[v]) q.push (v),st[v]=true ;             }         }     } } 
 
Floyd 算法 
1 2 3 4 5 6 7 8 int  n,dist[N][N];void  floyd ()   {    memset (dist,0x3f ,sizeof  dist);     for (int  k=1 ;k<=n;k++)         for (int  i=1 ;i<=n;i++)             for (int  j=1 ;j<=n;j++)                 dist[i][j]=min (dist[i][j],dist[i][k]+dist[k][j]); } 
 
同余最短路 
解决形如 $k=\sum_{i=1}^{n}{a_ix_i}$ 的问题 
 
假设数组 $a$ 最小的是 $a_1$,记 $y=\sum_{i=2}^{n}{a_ix_i}$
设 $f[j]$ 为使 $y \bmod {a_1} = j$ 成立的最小的 $y$
可以得到转移式:$f[(j+a_i) % a_1]=min(f[(j+a_i) % a_1],f[j]+a_i)$ $(2\le i \le n,0\le j < a_1)$
因此可以在点 $j$ 和点 $(j+a_i) % a_1$ 间建立一条权值为 $a_i$ 的边,跑最短路
差分约束 一个系统 $n$ 个变量和 $m$ 个约束条件组成,每个约束条件形如 $x_j-x_i \le b_k$
可以发现每个约束条件都形如最短路中的三角不等式 $d_u-d_v \le w _ {u,v}$,因此连一条边 $(i,j,b_k)$ 建图
若要使得所有量两两的值最接近(最小解),源点到各点的距离初始成 $0$,跑最长路
若要使得某一变量与其他变量的差尽可能大(最大解),则源点到各点距离初始化成 $\infty$,跑最短路
若约束条件中的变量为整数,$d_u-d_v < w _ {u,v}$ 可改为 $d_u-d_v \le w _ {u,v}-1$
 
若约束条件中的变量为实数,$d_u-d_v < w _ {u,v}$ 可近似为 $d_u-d_v \le w _ {u,v}$
 
 
根据约束条件,判断跑最短路还是最长路
跑最短路,有负环则无法满足所有约束条件
跑最长路,有正环则无法满足所有约束条件
最小生成树 Kurskal 算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int  n, fa[N];struct  E  {    int  u,v,w;     bool  operator  < (const  E &a) const  { return  w<a.w; } }; vector<E> e; int  find (int  x)   {    return  x == fa[x] ? x : fa[x] = find (fa[x]); } int  kruskal ()   {    for  (int  i = 1 ; i <= n; i++) {         fa[i] = i;     }     sort (ALL (e));     int  ret = 0 ;     for  (auto  x : e) {         int  fu = find (x.u), fv = find (x.v);         if  (Fu != fv) {             fa[fu] = fv;             ret += x.w;         }     }     return  ret; } 
 
Prim 算法 
Prim 算法可以堆优化,但用优先队列实现堆不如直接用 Kruskal 算法。 
稠密图和完全图用暴力 Prim 算法比 Kruskal 算法更优,但不一定跑得快,时间复杂度 $O(n^2+m)$。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int  w[N][N],f[N];bool  vis[N];int  prim (int  n)   {    memset (f,0x3f ,sizeof  f);     memset (vis,false ,sizeof  vis);     f[1 ]=0 ;     int  ret=0 ;     for (int  i=1 ;i<=n;i++) {         int  x=0 ;         for (int  j=1 ;j<=n;j++) if (!vis[j]&&(x==0 ||f[j]<f[x])) {             x=j;         }         vis[x]=true ;         ret+=f[x];         for (int  j=1 ;j<=n;j++) if (!vis[j]&&w[x][j]<f[j]) {             f[j]=w[x][j];         }     }     return  ret; } 
 
最小生成树的唯一性 考虑最小生成树的唯一性。如果一条边不在最小生成树的边集中 ,并且可以替换与其权值相同、并且在最小生成树边集 的另一条边。那么这个最小生成树就是不唯一的
对于 Kruskal 算法,只要计算为当前权值的边可以放几条,实际放了几条,如果这两个值不一样,那么就说明这几条边与之前的边产生了一个环(这个环中至少有两条当前权值的边,否则根据并查集,这条边是不能放的),即最小生成树不唯一
寻找权值与当前边相同的边,我们只需要记录头尾指针,用单调队列即可在基本与原算法时间相同的时间复杂度里优秀解决这个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include  <cstdio>  #include  <algorithm>  using  namespace  std;const  int  N=105 ;struct  E  {    int  u,v,w;     E () {}     E (int  u,int  v,int  w):u (u),v (v),w (w) {}     bool  operator  < (const  E &a) const  {         return  w<a.w;     } }e[N*N]; int  n,m,fa[N],cnt;int  find (int  x)   {    return  x==fa[x]?x:fa[x]=find (fa[x]); } void  kruskal ()   {    for (int  i=1 ;i<=n;i++) fa[i]=i;     sort (e+1 ,e+1 +cnt);     e[++cnt]=E (0 ,0 ,0 );     bool  f=true ;     int  res=0 ,tail=0 ,sum1=0 ,sum2=0 ;     for (int  i=1 ;i<=cnt;i++) {         if (i>tail) {             if (sum1!=sum2) {f=false ;break ;}             sum1=0 ;             for (int  j=i;j<=cnt;j++) {                 if (e[j].w!=e[i].w) {tail=j-1 ;break ;}                 if (find (e[j].u)!=find (e[j].v)) sum1++;             }             sum2=0 ;         }         if (i>cnt) break ;         int  tx=find (e[i].u),ty=find (e[i].v);         if (tx==ty) continue ;         fa[tx]=ty;         res+=e[i].w;         sum2++;     }     if (f) printf ("%d\n" ,res);     else  puts ("Not Unique!" ); } int  main ()   {    int  T;     scanf ("%d" ,&T);     while (T--) {         scanf ("%d%d" ,&n,&m);         cnt=0 ;         for (int  i=1 ;i<=m;i++) {             int  u,v,w;             scanf ("%d%d%d" ,&u,&v,&w);             e[++cnt]=E (u,v,w);         }         kruskal ();     }     return  0 ; } 
 
次小生成树 非严格次小生成树 求出无向图的最小生成树 $T$,设其权值和为 $M$
遍历每条未被选中的边 $e=(u,v,w)$,找到 $T$ 中 $u$ 到 $v$ 路径上边权最大的一条边 $e’=(s,t,w’)$,则在 $T$ 中以 $e$ 替换 $e’$,可得一棵权值和为 $M’=M+w-w’$ 的生成树 $T’$
对所有替换得到的答案 $M’$ 取最小值即可
使用倍增预处理出每个节点的 $2^i$ 级祖先及到达其 $2^i$ 级祖先路径上最大的边权,这样在倍增求 LCA 的过程中可以直接求得 $u,v$ 路径上的边权最大值
严格次小生成树 维护到 $2^i$ 级祖先路径上的最大边权的同时维护严格次大边权,当用于替换的边的权值与原生成树中路径最大边权相等时,我们用严格次大值来替换即可
时间复杂度 $O(m\log m)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 #include  <bits/stdc++.h>  #define  int long long using  namespace  std;const  int  N=1e5 +5 ,M=3e5 +5 ;int  n,m,t,fa[N],d[N],f[N][20 ],g[N][20 ][2 ];bool  st[M];vector<pair<int ,int >> G[N]; struct  E  {    int  u,v,w;     E () {}     E (int  u,int  v,int  w):u (u),v (v),w (w) {}     bool  operator  < (const  E &a) const  {         return  w<a.w;     } }e[M]; int  find (int  x)   {    return  fa[x]==x?x:fa[x]=find (fa[x]); } int  kruskal ()   {    for (int  i=1 ;i<=n;i++) fa[i]=i;     sort (e+1 ,e+1 +m);     int  res=0 ;     for (int  i=1 ;i<=m;i++) {         int  tx=find (e[i].u),ty=find (e[i].v);         if (tx==ty) continue ;         st[i]=true ;         fa[tx]=ty;         res+=e[i].w;         G[e[i].u].push_back (make_pair (e[i].v,e[i].w));         G[e[i].v].push_back (make_pair (e[i].u,e[i].w));     }     return  res; } void  bfs ()   {    queue<int > q;     q.push (1 );     d[1 ]=0 ;     while (!q.empty ()) {         int  u=q.front ();         q.pop ();         for (auto  x:G[u]) {             int  v=x.first;             if (v==f[u][0 ]) continue ;             d[v]=d[u]+1 ;             f[v][0 ]=u;             for (int  i=1 ;i<=t;i++) f[v][i]=f[f[v][i-1 ]][i-1 ];             g[v][0 ][0 ]=x.second;             g[v][0 ][1 ]=0 ;             for (int  j=1 ;j<=t;j++) {                 g[v][j][0 ]=max (g[v][j-1 ][0 ],g[f[v][j-1 ]][j-1 ][0 ]);                 if (g[v][j-1 ][0 ]==g[f[v][j-1 ]][j-1 ][0 ])                     g[v][j][1 ]=max (g[v][j-1 ][1 ],g[f[v][j-1 ]][j-1 ][1 ]);                 else  if (g[v][j-1 ][0 ]<g[f[v][j-1 ]][j-1 ][0 ])                     g[v][j][1 ]=max (g[v][j-1 ][0 ],g[f[v][j-1 ]][j-1 ][1 ]);                 else  if (g[v][j-1 ][0 ]>g[f[v][j-1 ]][j-1 ][0 ])                     g[v][j][1 ]=max (g[v][j-1 ][1 ],g[f[v][j-1 ]][j-1 ][0 ]);             }             q.push (v);         }     } } int  LCA (int  a,int  b)   {    if (d[a]>d[b]) swap (a,b);     for (int  i=t;i>=0 ;i--) if (d[f[b][i]]>=d[a]) b=f[b][i];     if (a==b) return  a;     for (int  i=t;i>=0 ;i--) if (f[a][i]!=f[b][i]) a=f[a][i],b=f[b][i];     return  f[a][0 ]; } pair<int ,int > calc (int  a,int  b)   {    pair<int ,int > res (0 ,0 )  ;     for (int  i=t;i>=0 ;i--)         if (d[f[a][i]]>=d[b]) {             if (g[a][i][0 ]>=res.first) {                 res.second=max (res.second,max (res.first,g[a][i][1 ]));                 res.first=g[a][i][0 ];             }             else  res.second=max (res.second,g[a][i][0 ]);             a=f[a][i];         }     return  res; } int  solve ()   {    int  mst=kruskal ();     bfs ();     int  res=0x7fffffffffffffff ;     for (int  i=1 ;i<=m;i++) {         if (st[i]) continue ;         int  mx,lca=LCA (e[i].u,e[i].v);         pair<int ,int > t1=calc (e[i].u,lca),t2=calc (e[i].v,lca);         if (t1.first!=t2.first) {             if (max (t1.first,t2.first)==e[i].w)                 mx=max (max (t1.second,t2.second),min (t1.first,t2.first));             else  mx=max (t1.first,t2.first);         }         else  {             if (t1.first==e[i].w) mx=max (t1.second,t2.second);             else  mx=t1.first;         }         res=min (res,mst-mx+e[i].w);     }     return  res; } signed  main ()   {    scanf ("%lld%lld" ,&n,&m);     t=(int )(log (n)/log (2 ))+1 ;     for (int  i=1 ;i<=m;i++) {         int  u,v,w;         scanf ("%lld%lld%lld" ,&u,&v,&w);         e[i]=E (u,v,w);     }     printf ("%lld\n" ,solve ());     return  0 ; } 
 
最小 k 度限制生成树 去除 $v_0$,找到所有连通分量,每个连通分量跑最小生成树
添加 $v_0$ 与每个连通分量的最小的边(若有 $t$ 个连通分量,则此时为 $t$ 度最小生成树)
若 $t<k$,则还可添加 $k-t$ 条边,对于每条不属于最小生成树的 $(v_0,v)$,边权为 $z$,找出最小生成树中 $v$ 到 $v_0$ 路径上的的最大边,边权为 $w$,求出使 $w-z$ 最大的点 $v$,若 $w-z>0$,则加入 $(v_0,v)$
求$v$ 到 $v_0$ 路径上最大边可以用 DP 
 
设 $dp[i]$ 为路径 $v_0$ 到 $v$ 无关联且权值最大的边,$fa[v]$ 为 $v$ 的父节点
转移方程:$dp[v]=max(dp[fa[v]],dist[fa[v]][v])$
边界条件:$dp[v_0]=-\infty,dp[v’]=-\infty$ $((v_0,v’)\in E(T))$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 #include  <iostream>  #include  <algorithm>  #include  <vector>  #include  <map>  using  namespace  std;const  int  N=505 ;struct  E  {    int  u,v,w;     E () {}     E (int  u,int  v,int  w):u (u),v (v),w (w) {}     bool  operator  < (const  E &a) const  {         return  w<a.w;     } }mn[N],mx[N]; int  n,m,k,dist[N][N],fa[N],id[N],vis[N];bool  st[N][N];vector<E> e; map<string,int > mp; int  find (int  x)   {    return  x==fa[x]?x:fa[x]=find (fa[x]); } int  kruskal ()   {    for (int  i=1 ;i<=n;i++) fa[i]=i;     sort (e.begin (),e.end ());     int  res=0 ;     for (int  i=0 ;i<e.size ();i++)         if (id[e[i].u]==id[e[i].v]) {             int  tx=find (e[i].u),ty=find (e[i].v);             if (tx==ty) continue ;             fa[tx]=ty;             res+=e[i].w;             st[e[i].u][e[i].v]=st[e[i].v][e[i].u]=true ;         }     return  res; } void  dfs1 (int  u,int  t)   {    id[u]=t;     for (int  i=2 ;i<=n;i++) if (dist[u][i]&&!id[i]) dfs1 (i,t); } void  dp (int  u,int  fa)   {    for (int  i=2 ;i<=n;i++) {         if (i==fa||!st[u][i]) continue ;         if (u!=1 ) {             if (mx[u].w>dist[u][i]) mx[i]=mx[u];             else  mx[i]=E (u,i,dist[u][i]);         }         dp (i,u);     } } int  solve ()   {    int  t=0 ;     for (int  i=2 ;i<=n;i++) if (!id[i]) dfs1 (i,++t);     int  res=kruskal ();     for (int  i=1 ;i<=t;i++) mn[i].w=0x3f3f3f3f ;     for (int  i=2 ;i<=n;i++)         if (dist[1 ][i]&&dist[1 ][i]<mn[id[i]].w)             mn[id[i]]=E (1 ,i,dist[1 ][i]);     for (int  i=2 ;i<=n;i++) {         if (vis[id[i]]) continue ;         st[1 ][mn[id[i]].v]=st[mn[id[i]].v][1 ]=true ;         res+=mn[id[i]].w;         vis[id[i]]=true ;         k--;     }     while (k) {         mx[1 ].w=0xc0c0c0c0 ;         for (int  i=2 ;i<=n;i++) {             if (st[1 ][i]) mx[i].w=0xc0c0c0c0 ;             else  mx[i].w=0 ;         }         dp (1 ,0 );         int  temp=1 ;         for (int  i=2 ;i<=n;i++) {             if (st[1 ][i]||!dist[1 ][i]) continue ;             if (dist[1 ][i]-mx[i].w<dist[1 ][temp]-mx[temp].w) temp=i;         }         st[1 ][temp]=st[temp][1 ]=true ;         st[mx[temp].u][mx[temp].v]=st[mx[temp].v][mx[temp].u]=false ;         if (dist[1 ][temp]-mx[temp].w>0 ) break ;         res+=dist[1 ][temp]-mx[temp].w;         k--;     }     return  res; } int  main ()   {    ios::sync_with_stdio (false );     cin.tie (0 );     mp["Park" ]=++n;     cin>>m;     for (int  i=1 ;i<=m;i++) {         string a,b;         int  w;         cin>>a>>b>>w;         if (!mp.count (a)) mp[a]=++n;         if (!mp.count (b)) mp[b]=++n;         dist[mp[a]][mp[b]]=dist[mp[b]][mp[a]]=w;         e.push_back (E (mp[a],mp[b],w));     }     cin>>k;     cout<<"Total miles driven: " <<solve ()<<'\n' ;     return  0 ; } 
 
瓶颈生成树 定义:无向图 $G$ 的瓶颈生成树是这样的一个生成树,它的最大的边权值在 $G$ 的所有生成树中最小
性质:最小生成树是瓶颈生成树的充分不必要条件
最小瓶颈路 定义:无向图 $G$ 中 $x$ 到 $y$ 的最小瓶颈路是这样的一类简单路径,满足这条路径上的最大的边权在所有 $x$ 到 $y$ 的简单路径中是最小的
性质:根据最小生成树定义,$x$ 到 $y$ 的最小瓶颈路上的最大边权等于最小生成树上 $x$ 到 $y$ 路径上的最大边权。虽然最小生成树不唯一,但是每种最小生成树 $x$ 到 $y$ 路径的最大边权相同且为最小值。也就是说,每种最小生成树上的 $x$ 到 $y$ 的路径均为最小瓶颈路
并不是所有最小瓶颈路都存在一棵最小生成树满足其为树上 $x$ 到 $y$ 的简单路径 
 
最小树形图 
朱刘算法 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int  in[N],st[N],id[N],pre[N];struct  E  {    int  u,v,w;     E () {}     E (int  u,int  v,int  w):u (u),v (v),w (w) {} }; vector<E> e; int  edmonds (int  rt,int  n)   {    int  ret=0 ,v,nn=n;     for (;;) {         for (int  i=1 ;i<=n;i++) in[i]=0x3f3f3f3f ;         for (auto  x:e) if (x.u!=x.v&&x.w<in[x.v]) pre[x.v]=x.u,in[x.v]=x.w;         for (int  i=1 ;i<=n;i++) if (i!=rt&&in[i]==0x3f3f3f3f ) return  -1 ;         int  tn=0 ;         for (int  i=1 ;i<=nn;i++) id[i]=st[i]=-1 ;         in[rt]=0 ;         for (int  i=1 ;i<=n;i++) {             ret+=in[v=i];             while (st[v]!=i&&id[v]==-1 &&v!=rt) st[v]=i,v=pre[v];             if (v!=rt&&id[v]==-1 ) {                 ++tn;                 for (int  u=pre[v];u!=v;u=pre[u]) id[u]=tn;                 id[v]=tn;             }         }         if (!tn) break ;         for (int  i=1 ;i<=n;i++) if (id[i]==-1 ) id[i]=++tn;         for (int  i=0 ;i<SZ (e);) {             auto  &x=e[i];             v=x.v;             x.u=id[x.u],x.v=id[x.v];             if (x.u!=x.v) x.w-=in[v],++i;             else  swap (x,e.back ()),e.pop_back ();         }         n=tn,rt=id[rt];     }     return  ret; } 
 
无定根最小树形图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 int  in[N],st[N],id[N],pre[N],inn[N],sum,t;struct  E  {    int  u,v,w,num;     E () {}     E (int  u,int  v,int  w,int  num):u (u),v (v),w (w),num (num) {} }; vector<E> e; int  edmonds (int  rt,int  n)   {    int  ret=0 ;     int  v,nn=n;     while (1 ) {         for (int  i=1 ;i<=n;i++) in[i]=0x3f3f3f3f ;         for (auto  x:e) {             if (x.w<in[x.v]) {                 pre[x.v]=x.u,in[x.v]=x.w;                 inn[x.v]=x.u;             }         }         t=0x3f3f3f3f ;         for (int  i=1 ;i<=n;i++) if (inn[i]==rt) {             for (auto  x:e) if (x.u==rt&&x.v==i&&in[x.v]==x.w) t=min (t,x.num);         }         int  tn=0 ;         for (int  i=1 ;i<=nn;i++) id[i]=st[i]=-1 ;         in[rt]=0 ;         for (int  i=1 ;i<=n;i++) {             ret+=in[v=i];             while (st[v]!=i&&id[v]==-1 &&v!=rt) st[v]=i,v=pre[v];             if (v!=rt&&id[v]==-1 ) {                 ++tn;                 for (int  u=pre[v];u!=v;u=pre[u]) id[u]=tn;                 id[v]=tn;             }         }         if (!tn) break ;         for (int  i=1 ;i<=n;i++) if (id[i]==-1 ) id[i]=++tn;         for (int  i=0 ;i<SZ (e);) {             auto  &x=e[i];             v=x.v;             x.u=id[x.u],x.v=id[x.v];             if (x.u!=x.v) x.w-=in[v],++i;             else  swap (x,e.back ()),e.pop_back ();         }         n=tn,rt=id[rt];     }     return  ret; } int  main ()   {    int  n,m;     cin>>n>>m;     for (int  i=1 ;i<=m;i++) {         int  u,v,w;         cin>>u>>v>>w;         e.EB (u,v,w,0 );         sum+=w;     }     ++sum;     for (int  i=1 ;i<=n;i++) e.EB (n+1 ,i,sum,i);     int  res=edmonds (n+1 ,n+1 );     if (res==-1 ||res>=2 *sum) cout<<"impossible" <<'\n' ;     else  cout<<res-sum<<' ' <<t<<'\n' ;     return  0 ; } 
 
欧拉图 通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉通路
通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路
具有欧拉回路的无向图称为欧拉图
具有欧拉通路但不具有欧拉回路的无向图称为半欧拉图
性质 
欧拉图中所有顶点的度数都是偶数
若 $G$ 是欧拉图,则它为若干个边不重的圈的并
若 $G$ 是半欧拉图,则它为若干个边不重的圈和一条简单路径的并
判别法 
对于无向图 $G$,$G$ 是欧拉图当且仅当 $G$ 是连通的且没有奇度顶点
对于无向图 $G$,$G$ 是半欧拉图当且仅当 $G$ 是连通的且 $G$ 中恰有 $0$ 个或 $2$ 个奇度顶点
对于有向图 $G$,$G$ 是欧拉图当且仅当 $G$ 的所有顶点属于同一个强连通分量且每个顶点的入度和出度相同
对于有向图 $G$,$G$ 是半欧拉图当且仅当
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 int  cnt,nxt[M<<1 ],to[M<<1 ],head[N];int  top,stk[M<<1 ],t,ans[M<<1 ];bool  st[M<<1 ];void  init ()   {    cnt=1 ;     for (int  i=1 ;i<=n;i++) head[i]=0 ;     top=t=0 ; } void  add_edge (int  u,int  v)   {    nxt[++cnt]=head[u];     to[cnt]=v;     head[u]=cnt; } void  hierholzer (int  s)   {    stk[++top]=s;     while (top) {         int  u=stk[top],i=head[u];         while (i&&st[i]) i=nxt[i];         if (i) {             stk[++top]=to[i];             st[i]=st[i^1 ]=true ;             head[u]=nxt[i];         }         else  top--,ans[++t]=u;      } } 
 
对于混合图 $G$,先将无向边随意规定一个方向,计算出各个顶点的入度和出度。
欧拉图:
如果将 $G$ 中的所有有向边退化为无向边时, $G$ 的所有顶点不属于同一个连通分量或者入度出度差是奇数的顶点个数不是 $0$ 个(对于无向边,无论方向如何,入度出度差奇偶性不变),则图 $G$ 不可能是欧拉图。
接下来可以用网络流确定无向边方向判断图 $G$ 是否是欧拉图。
建立起点 $s$ 和终点 $t$,将 $s$ 与出度大于入度的顶点与相连,出度小于入度的顶点与 $t$ 相连,容量为出度入度差的绝对值的 $\frac{1}{2}$,将之前定向的无向边之间建立一条容量为 $1$ 的边,当网络流中与起点相连的边都满流时存在欧拉图。
我们的目的是调整无向边的方向使所有点的出度等于入度,假设网络流中经过 $(a,b)$ 这条边,意味着 $(a,b)$ 需要改变方向,使 $a$ 的出度 $-1$,入度 $+1$ ,$b$ 的入度 $-1$,出度 $+1$,所以网络流中与起点相连的边都满流时存在欧拉图。
半欧拉图:
如果将 $G$ 中的所有有向边退化为无向边时, $G$ 的所有顶点不属于同一个连通分量或者入度出度差是奇数的顶点个数不是 $0$ 或 $2$ 个,则图 $G$ 不可能是半欧拉图。
将入度出度差是奇数的顶点之间加一条容量为 $1$ 的边,这样则所有顶点入度出度差都为偶数,这样只要判断是否是欧拉图即可。
拓扑排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int  n,in[N];VI g[N]; VI res;  void  add_edge (int  u,int  v)   {    g[u].PB (v);     in[v]++; } bool  topo ()   {    queue<int > q;     for (int  i=1 ;i<=n;i++) if (!in[i]) q.push (i);     while (SZ (q)) {         int  u=q.front ();         q.pop ();         res.PB (u);         for (auto  v:g[u])             if (--in[v]==0 )                 q.push (v);     }     if (SZ (res)==n) return  true ;     return  false ;      } 
 
连通性 边双连通分量 
树形图至少添加 (叶子的数量(若树根只有一个儿子,树根也算叶子)$+1)/2$ 条边使其变为边双连通图 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 VI G[N]; void  init ()   {    for (int  i=1 ;i<=n;i++) G[i].clear (); } int  cnt,dfn[N],low[N],fa[N];bool  isb[N];VI b; void  tarjan (int  u)   {    dfn[u]=low[u]=++cnt;     for (auto  v:G[u]) {         if (!dfn[v]) {             fa[v]=u;             tarjan (v);             low[u]=min (low[u],low[v]);             if (dfn[u]<low[v]) isb[v]=true ,b.PB (v);         }         else  if (v!=fa[u]) low[u]=min (low[u],dfn[v]);     } } void  getBridge ()   {    cnt=0 ;     b.clear ();     for (int  i=1 ;i<=n;i++) dfn[i]=low[i]=fa[i]=0 ,isb[i]=false ;     for (int  i=1 ;i<=n;i++) if (!dfn[i]) tarjan (i); } int  dcc,c[N];void  dfs (int  u)   {    c[u]=dcc;     for (auto  v:G[u]) {         if (c[v]||u==fa[v]&&isb[v]||v==fa[u]&&isb[u]) continue ;         dfs (v);     } } void  shrink ()   {    dcc=0 ;     for (int  i=1 ;i<=n;i++) c[i]=0 ;     for (int  i=1 ;i<=n;i++) if (!c[i]) ++dcc,dfs (i); } vector<int > g[N]; void  build ()   {     for (int  i=1 ;i<=dcc;i++) g[i].clear ();     for (int  i=0 ;i<b.size ();i++) {         int  u=c[b[i]],v=c[fa[b[i]]];         g[u].PB (v);         g[v].PB (u);     } } 
 
点双连通分量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 VI G[N]; void  init ()   {    for (int  i=1 ;i<=n;i++) G[i].clear (); } int  root,cnt,cntDcc,top,dfn[N],low[N],stk[N];bool  cut[N];VI dcc[N]; void  tarjan (int  u)   {    dfn[u]=low[u]=++cnt;     stk[++top]=u;     if (u==root&&!G[u].size ()) {          dcc[++cntDcc].PB (u);         return ;     }     int  flag=0 ;     for (auto  v:G[u]) {         if (!dfn[v]) {             tarjan (v);             low[u]=min (low[u],low[v]);             if (dfn[u]<=low[v]) {                 flag++;                 if (u!=1 ||flag>1 ) cut[u]=true ;                 ++cntDcc;                 int  x;                 do  {                     x=stk[top--];                     dcc[cntDcc].PB (x);                 } while (x!=v);                 dcc[cntDcc].PB (u);             }         }         else  low[u]=min (low[u],dfn[v]);     } } void  shrink ()   {    for (int  i=1 ;i<=cntDcc;i++) dcc[i].clear ();     cnt=cntDcc=top=0 ;     for (int  i=1 ;i<=n;i++) dfn[i]=low[i]=0 ,cut[i]=false ;     for (int  i=1 ;i<=n;i++) if (!dfn[i]) root=i,tarjan (i); } int  num,id[N];VI g[N]; void  build ()   {    num=cntDcc;     for (int  i=1 ;i<=n;i++) id[i]=0 ;     for (int  i=1 ;i<=n;i++) if (cut[i]) id[i]=++num;     for (int  i=1 ;i<=num;i++) g[i].clear ();     for (int  i=1 ;i<=cntDcc;i++)         for (auto  x:dcc[i])             if (id[x]) {                 g[id[x]].PB (i);                 g[i].PB (id[x]);             } } 
 
强连通分量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 VI G[N]; void  init ()   {    for (int  i=1 ;i<=n;i++) G[i].clear (); } int  cnt,dfn[N],low[N],top,stk[N],cntScc,c[N];bool  ins[N];VI scc[N]; void  tarjan (int  u)   {    dfn[u]=low[u]=++cnt;     stk[++top]=u;     ins[u]=true ;     for (auto  v:G[u]) {         if (!dfn[v]) {             tarjan (v);             low[u]=min (low[u],low[v]);         }         else  if (ins[v]) low[u]=min (low[u],dfn[v]);     }     if (dfn[u]==low[u]) {         ++cntScc;         int  x;         do  {             x=stk[top--];             ins[x]=false ;             c[x]=cntScc;             scc[cntScc].PB (x);         } while (u!=x);     } } void  shrink ()   {    for (int  i=1 ;i<=cntScc;i++) scc[i].clear ();     cnt=cntScc=top=0 ;     for (int  i=1 ;i<=n;i++) dfn[i]=low[i]=c[i]=0 ,ins[i]=false ;     for (int  i=1 ;i<=n;i++) if (!dfn[i]) tarjan (i); } vector<int > g[N]; void  build ()   {    for (int  i=1 ;i<=cntScc;i++) g[i].clear ();     for (int  i=1 ;i<=n;i++)         for (int  j=0 ;j<SZ (G[i]);j++) {             int  u=c[i],v=c[G[i][j]];             if (u==v) continue ;             g[u].PB (v);         } } 
 
2 - SAT 给出 $n$ 个集合,每个集合有两个元素,已知若干个 $<a,b>$,表示 $a$ 与 $b$ 矛盾(其中 $a$ 与 $b$ 属于不同的集合)
然后从每个集合选择一个元素,判断能否一共选 $n$ 个两两不矛盾的元素
显然可能有多种选择方案,一般题中只需要求出一种即可
假设有两个集合 ${a,\neg a},{b,\neg b}$
若 $a$ 与 $b$ 矛盾,则选了 $a$ 就必须选 $\neg b$,选了 $b$ 就必须选 $\neg a$,按此关系建立有向边(原命题和逆否命题是成对出现的)
若选 $a$ 本身就是不合法的,那么在 $a$ 和 $\neg a$ 之间建立一条有向边,让它直接产生矛盾
若一个集合中的两个元素在同一个 SCC 中,无解
输出方案时可以通过变量在图中的反拓扑序确定该变量的取值
如果变量 $\neg x$ 的反拓扑序在 $x$ 之后,那么取 $x$ 值为真
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 int  n,cnt,cnt_scc,top,dfn[N],low[N],c[N],stk[N];int  in[N],val[N],opp[N];bool  ins[N];VI G[N],g[N]; void  init ()   {    cnt=cnt_scc=top=0 ;     for (int  i=1 ;i<=n*2 ;i++) {         G[i].clear ();         g[i].clear ();         dfn[i]=low[i]=c[i]=in[i]=val[i]=0 ;         ins[i]=false ;     } } void  tarjan (int  u)   {    dfn[u]=low[u]=++cnt;     stk[++top]=u;     ins[u]=true ;     for (auto  v:G[u]) {         if (!dfn[v]) {             tarjan (v);             low[u]=min (low[u],low[v]);         }         else  if (ins[v]) low[u]=min (low[u],dfn[v]);     }     if (dfn[u]==low[u]) {         cnt_scc++;         int  x;         do  {             x=stk[top--];             ins[x]=false ;             c[x]=cnt_scc;         } while (u!=x);     } } void  rebuild ()   {    for (int  i=1 ;i<=n*2 ;i++) {         for (auto  x:G[i]) {             int  u=c[i],v=c[x];             if (u==v) continue ;             in[u]++;             g[v].PB (u);         }     } } void  topo ()   {    queue<int > q;     for (int  i=1 ;i<=cnt_scc;i++) if (!in[i]) q.push (i);     while (SZ (q)) {         int  u=q.front ();         q.pop ();         if (!val[u]) val[u]=1 ,val[opp[u]]=2 ;         for (auto  v:g[u]) {             in[v]--;             if (!in[v]) q.push (v);         }     } } void  solve ()   {    for (int  i=1 ;i<=n*2 ;i++) if (!dfn[i]) tarjan (i);     for (int  i=1 ;i<=n;i++) {         if (c[i]==c[i+n]) {puts ("NO" );return ;}         opp[c[i]]=c[i+n],opp[c[i+n]]=c[i];     }     rebuild ();     topo ();     for (int  i=1 ;i<=n;i++) printf ("%d\n" ,val[i]<val[i+n]?0 :1 ); } 
 
因为 Tarjan 算法求强连通分量时使用了栈,所以 Tarjan 求得的 SCC 编号相当于反拓扑序,所在 SCC 编号在 $\neg x$ 之前时,取 $x$ 为真
1 for (int  i=1 ;i<=n;i++) printf ("%d\n" ,c[i]<c[i+n]?0 :1 );
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include  <bits/stdc++.h>  using  namespace  std;const  int  N=16005 ;int  n,m,top,stk[N];bool  st[N];vector<int > g[N]; bool  dfs (int  u)   {    st[u]=true ;     stk[++top]=u;     for (auto  v:g[u]) {         if (st[v^1 ]) return  false ;         if (st[v]) continue ;         if (!dfs (v)) return  false ;     }     return  true ; } int  main ()   {    while (~scanf ("%d%d" ,&n,&m)) {         for (int  i=0 ;i<n*2 ;i++) g[i].clear (),st[i]=false ;         for (int  i=1 ;i<=m;i++) {             int  u,v;             scanf ("%d%d" ,&u,&v);             u--,v--;             g[u].push_back (v^1 );             g[v].push_back (u^1 );         }         bool  f=false ;         for (int  i=0 ;i<n*2 ;i+=2 ) {             if (st[i]||st[i^1 ]) continue ;             top=0 ;             if (!dfs (i)) {                 while (top) st[stk[top--]]=false ;                 if (!dfs (i^1 )) {f=true ;break ;}             }         }         if (f) puts ("NIE" );         else  for (int  i=0 ;i<n*2 ;i++) if (st[i]) printf ("%d\n" ,i+1 );     }     return  0 ; } 
 
重链剖分 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int  cnt,sz[N],dep[N],fa[N],top[N],son[N],dfn[N];void  dfs1 (int  u)   {    dep[u]=dep[fa[u]]+1 ;     sz[u]=1 ;     for (auto  v:g[u]) if (v!=fa[u]) {         fa[v]=u;         dfs1 (v);         sz[u]+=sz[v];         if (sz[v]>sz[son[u]]) son[u]=v;     } } void  dfs2 (int  u,int  t)   {    top[u]=t;     dfn[u]=++cnt;     if (son[u]) dfs2 (son[u],t);     for (auto  v:g[u]) if (!top[v]) {         dfs2 (v,v);     } } 
 
树分治 点分治 
 CF 161D :节点对 $(u,v)$ 和节点对 $(v,u)$ 被认为是相同的节点对,求树上距离恰好为 $k$ 的不同节点对的数量。
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 int  n,k,rt,cnt,sz[N],mx[N],dist[N],a[N],tot[N];LL res; bool  st[N];VI g[N]; void  get_rt (int  u,int  fa,int  sum)   {    sz[u]=1 ;     mx[u]=0 ;     for (auto  v:g[u]) if (v!=fa&&!st[v]) {         get_rt (v,u,sum);         sz[u]+=sz[v];         mx[u]=max (mx[u],sz[v]);     }     mx[u]=max (mx[u],sum-sz[u]);     if (mx[u]<mx[rt]) rt=u; } void  get_dist (int  u,int  fa)   {    if (dist[u]>k) return ;     a[++cnt]=dist[u];     for (auto  v:g[u]) if (v!=fa&&!st[v]) {         dist[v]=dist[u]+1 ;         get_dist (v,u);     } } LL calc (int  u,int  t)   {    dist[u]=t;     cnt=0 ;     get_dist (u,0 );     for (int  i=0 ;i<=k;i++) tot[i]=0 ;     for (int  i=1 ;i<=cnt;i++) ++tot[a[i]];     LL ret=0 ;     for (int  i=1 ;i<=cnt;i++) {         --tot[a[i]];         ret+=tot[k-a[i]];     }     return  ret; } void  divide (int  u)   {    mx[rt=0 ]=INT_MAX;     get_rt (u,0 ,sz[u]);     st[u=rt]=true ;     res+=calc (u,0 );     for (auto  v:g[u]) if (!st[v]) {         res-=calc (v,1 );         divide (v);     } } int  main ()   {    cin>>n>>k;     for (int  i=1 ;i<n;i++) {         int  u,v;cin>>u>>v;         g[u].PB (v);         g[v].PB (u);     }     sz[1 ]=n;divide (1 );     cout<<res<<'\n' ;     return  0 ; } 
 
树上启发式合并 
CF 600E :一棵树有 $n$ 个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和。
 
用 $cnt_i$ 表示颜色 $i$ 的出现次数。
遍历一个节点 $u$,我们按以下的步骤进行遍历:
先遍历 $u$ 的轻(非重)儿子,并计算答案,但不保留遍历后它对 $cnt$ 数组的影响; 
遍历它的重儿子,保留它对 $cnt$ 数组的影响; 
再次遍历 $u$ 的轻儿子的子树结点,加入这些结点的贡献,以得到 $u$ 的答案。 
 
时间复杂度:$O(n\log(n))$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 VI g[N]; int  w[N],sz[N],son[N],cnt[N],mx,sn;LL sum,res[N]; void  dfs (int  u,int  fa)   {    sz[u]=1 ;     for (auto  v:g[u]) if (v!=fa) {         dfs (v,u);         sz[u]+=sz[v];         if (sz[v]>sz[son[u]]) son[u]=v;     } } void  update (int  u,int  fa,int  val)   {    cnt[w[u]]+=val;     if (cnt[w[u]]>mx) mx=cnt[w[u]],sum=w[u];     else  if (cnt[w[u]]==mx) sum+=w[u];     for (auto  v:g[u]) if (v!=fa&&v!=sn) {         update (v,u,val);     } } void  dsu (int  u,int  fa,bool  op)   {    for (auto  v:g[u]) if (v!=fa&&v!=son[u]) {         dsu (v,u,true );     }     if (son[u]) dsu (son[u],u,false ),sn=son[u];     update (u,fa,1 );     res[u]=sum;     sn=0 ;     if (op) update (u,fa,-1 ),mx=0 ,sum=0 ; } int  main ()   {    int  n;cin>>n;     for (int  i=1 ;i<=n;i++) cin>>w[i];     for (int  i=1 ;i<n;i++) {         int  u,v;         cin>>u>>v;         g[u].PB (v);         g[v].PB (u);     }     dfs (1 ,0 );     dsu (1 ,0 ,true );     for (int  i=1 ;i<=n;i++) cout<<res[i]<<" \n" [i==n];     return  0 ; } 
 
LCA 重链剖分算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int  fa[N],dep[N],fa[N],son[N],top[N];VI g[N]; void  dfs1 (int  u)   {    dep[u]=dep[fa[u]]+1 ;     sz[u]=1 ;     for (auto  v:g[u]) {         if (v==fa[u]) continue ;         fa[v]=u;         dfs1 (v);         sz[u]+=sz[v];         if (sz[v]>sz[son[u]]) son[u]=v;     } } void  dfs2 (int  u,int  t)   {    top[u]=t;     if (son[u]) dfs2 (son[u],t);     for (auto  v:g[u]) {         if (v==son[u]||v==fa[u]) continue ;         dfs2 (v,v);     } } int  LCA (int  a,int  b)   {    while (top[a]!=top[b]) {         if (dep[top[a]]>dep[top[b]])  a=fa[top[a]];         else  b=fa[top[b]];     }     return  dep[a]<dep[b]?a:b; } 
 
树上倍增算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int  dep[N],f[N][20 ];VI g[N]; void  dfs (int  u)   {    dep[u]=dep[f[u][0 ]]+1 ;     for (auto  v:g[u]) if (v!=f[u][0 ]) {         f[v][0 ]=u;         for (int  i=1 ;i<=LIM;i++) f[v][i]=f[f[v][i-1 ]][i-1 ];         dfs (v);     } } int  LCA (int  a,int  b)   {    if (dep[a]>dep[b]) swap (a,b);     for (int  i=LIM;~i;i--) if (dep[f[b][i]]>=dep[a]) b=f[b][i];     if (a==b) return  b;     for (int  i=LIM;~i;i--) if (f[a][i]!=f[b][i]) a=f[a][i],b=f[b][i];     return  f[a][0 ]; } 
 
树上 Tarjan 算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void  init ()   {    for (int  i=1 ;i<=n;i++) fa[i]=i; } void  add_query (int  a,int  b,int  id)   {    query[a].EB (b,id);     query[b].EB (a,id); } int  get (int  x)   {    if (fa[x]==x) return  x;     return  fa[x]=get (fa[x]); } void  tarjan (int  u)   {    st[u]=1 ;     for (auto  v:g[u]) {         if (st[v]) continue ;         tarjan (v);         fa[v]=u;     }     for (int  i=0 ;i<SZ (query[u]);i++) {         int  v=query[u][i].FI,id=query[u][i].SE;         if (st[v]==2 ) ans[id]=get (v);     }     st[u]=2 ; } 
 
网络流 最大流 
最大流最小割定理:网络流图中,最大流的值等于最小割的容量 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 const  int  INF=0x3f3f3f3f ;struct  MAXIMUM_FLOW  {    int  n,s,t,d[N],cur[N];     VI g[N];     VPII e;     void  init ()   {         for (int  i=1 ;i<=n;i++) g[i].clear ();         e.clear ();     }     void  add_edge (int  u,int  v,int  c)   {         e.EB (v,c);         e.EB (u,0 );         g[u].PB (SZ (e)-2 );         g[v].PB (SZ (e)-1 );     }     bool  bfs ()   {         for (int  i=1 ;i<=n;i++) d[i]=0 ;         queue<int > q;         q.push (s);         d[s]=1 ;         while (SZ (q)) {             int  u=q.front ();             q.pop ();             for (auto  x:g[u]) {                 int  v=e[x].FI,c=e[x].SE;                 if (d[v]||c<=0 ) continue ;                 d[v]=d[u]+1 ;                 q.push (v);             }         }         return  d[t];     }     int  dfs (int  u,int  a)   {         if (u==t) return  a;         int  f,flow=0 ;         for (int  &i=cur[u];i<SZ (g[u]);i++) {             int  v=e[g[u][i]].FI,&c=e[g[u][i]].SE;             if (d[v]!=d[u]+1 ||c<=0 ||(f=dfs (v,min (a,c)))<=0 ) continue ;             c-=f;             e[g[u][i]^1 ].SE+=f;             a-=f;             flow+=f;             if (a==0 ) break ;         }         return  flow;     }     int  dinic ()   {         int  flow=0 ;         while (bfs ()) {             for (int  i=1 ;i<=n;i++) cur[i]=0 ;             flow+=dfs (s,INF);         }         return  flow;     } } mf; 
 
最小费用最大流 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 const  int  INF=0x3f3f3f3f ;int  dist[N],h[N],preu[N],pree[M];VI g[N]; struct  E  {    int  v,c,w;     E (){}     E (int  v,int  c,int  w):v (v),c (c),w (w){} }; vector<E> e; void  init (int  n)   {    for (int  i=1 ;i<=n;i++) {         h[i]=0 ;         g[i].clear ();     }     e.clear (); } void  add_edge (int  u,int  v,int  c,int  w)   {    e.EB (v,c,w);     e.EB (u,0 ,-w);     g[u].PB (SZ (e)-2 );     g[v].PB (SZ (e)-1 ); } bool  dijkstra (int  n,int  s,int  t)   {    for (int  i=1 ;i<=n;i++) dist[i]=INF;     priority_queue<PII,VPII,greater<PII>> q;     dist[s]=0 ;     q.emplace (0 ,s);     while (SZ (q)) {         int  d=q.top ().FI,u=q.top ().SE;         q.pop ();         if (dist[u]!=d) continue ;         for (auto  x:g[u]) {             int  v=e[x].v,c=e[x].c,w=e[x].w;             if (c>0 &&dist[v]>dist[u]-h[v]+w+h[u]) {                 dist[v]=dist[u]-h[v]+w+h[u];                 preu[v]=u;                 pree[v]=x;                 q.emplace (dist[v],v);             }         }     }     return  dist[t]!=INF; } PII mcmf (int  n,int  s,int  t)   {    int  flow=0 ,cost=0 ;     while (dijkstra (n,s,t)) {         int  c=INF;         for (int  i=1 ;i<=n;i++) h[i]=min (INF,h[i]+dist[i]);         for (int  u=t;u!=s;u=preu[u]) c=min (c,e[pree[u]].c);         flow+=c;         cost+=c*h[t];         for (int  u=t;u!=s;u=preu[u]) {             e[pree[u]].c-=c;             e[pree[u]^1 ].c+=c;         }     }     return  MP (flow,cost); } 
 
二分图最大匹配 
匈牙利算法 时间复杂度:$O(n^2)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int  n,match[N];bool  m[N][N],st[N];bool  dfs (int  u)   {    for (int  v=1 ;v<=n;v++) {         if (!m[u][v]||st[v]) continue ;         st[v]=true ;         if (match[v]==-1 ||dfs (match[v])) {             match[v]=u;             return  true ;         }     }     return  false ; } int  hungary ()   {    for (int  i=1 ;i<=n;i++) match[i]=-1 ;     for (int  i=1 ;i<=n;i++) {         for (int  j=1 ;j<=n;j++) st[j]=false ;         if (dfs (i)) res++;     }     return  res; } 
 
HK 算法 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 int  n,m,match[N<<1 ],dep[N<<1 ];VI g[N]; bool  bfs ()   {    queue<int > q;     for (int  i=1 ;i<=n+m;i++) dep[i]=0 ;     for (int  i=1 ;i<=n;i++) if (match[i]==-1 ) dep[i]=1 ,q.push (i);     bool  f=false ;     while (SZ (q)) {         int  u=q.front ();         q.pop ();         for (auto  v:g[u]) {             if (dep[v]) continue ;             dep[v]=dep[u]+1 ;             if (match[v]==-1 ) f=true ;             else  dep[match[v]]=dep[v]+1 ,q.push (match[v]);         }     }     return  f; } bool  dfs (int  u)   {    for (auto  v:g[u]) {         if (dep[v]!=dep[u]+1 ) continue ;         dep[v]=0 ;         if (match[v]==-1 ||dfs (match[v])) {             match[v]=u;             match[u]=v;             return  true ;         }     }     return  false ; } int  HK ()   {    for (int  i=1 ;i<=n+m;i++) match[i]=-1 ;     int  res=0 ;     while (bfs ()) for (int  i=1 ;i<=n;i++) if (match[i]==-1 &&dfs (i)) res++;     return  res; } 
 
二分图最小点覆盖 图的最小点覆盖就是求出一个最小点集 $S$,使得图中任意一条边都有至少一个端点属于 $S$
Konig 定理:二分图最小点覆盖包含的点数等于二分图最大匹配包含的边数 
 
二分图最大独立集 图的最大独立集就是“任意两点之间都没有边相连”的最大点集
定理:二分图中,最大独立集 = $n$ - 最小点覆盖 
 
对应地,“任意两点之间都有一条边相连“的子图被称为无向图的“团”,点数最多的团被称为图的最大团
定理:无向图 $G$ 的最大团等于其补图 $G’$ 的最大独立集 
 
有向无环图的最小不相交路径覆盖 
有向无环图 $G$ 的最小不相交路径覆盖包含的路径条数 $=$ 点数 $-$ 拆点二分图 $G_2$ 的最大匹配数 
 
有向无环图的最小可相交路径覆盖 
有向无环图 $G$ 的最小可相交路径覆盖等价于先对有向图传递闭包,得到有向无环图 $G’$,再在 $G’$ 上求最小不相交路径覆盖 
 
无向图的最小路径覆盖 
无向图 $G$ 的最小路径覆盖包含的路径条数 $=$ 点数 $-$ 拆点二分图 $G_2$ 的最大匹配数 $/2$ 
 
二分图最大权匹配 KM 算法 KM 算法只能在满足带权最大匹配一定是完美匹配的图中求解。
两边点数不同,需增加虚点让两边点数相同,虚点与另一边的所有点相连构成虚边。
若题目允许不完美匹配,虚边边权设为 $0$,否则为 $-\infty$。
注意根据题意考虑邻接矩阵初始值是 $0$ 还是 $-\infty$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 int  w[N][N],la[N],lb[N],ma[N],mb[N],vb[N],slk[N],pre[N];int  km (int  n)   {    for (int  i=1 ;i<=n;i++) {         la[i]=0xc0c0c0c0 ;         lb[i]=ma[i]=mb[i]=0 ;         for (int  j=1 ;j<=n;j++) la[i]=max (la[i],w[i][j]);     }     for (int  i=1 ;i<=n;i++) {         for (int  j=0 ;j<=n;j++) vb[j]=pre[j]=0 ,slk[j]=0x3f3f3f3f ;         int  b=0 ,p=-1 ;         for (mb[b]=i;mb[b];b=p) {             int  d=0x3f3f3f3f ,a=mb[b];             vb[b]=1 ;             for (int  j=1 ;j<=n;j++)                 if (!vb[j]) {                     int  t=la[a]+lb[j]-w[a][j];                     if (t<slk[j]) slk[j]=t,pre[j]=b;                     if (slk[j]<d) d=slk[j],p=j;                 }             for (int  j=0 ;j<=n;j++) {                 if (vb[j]) la[mb[j]]-=d,lb[j]+=d;                 else  slk[j]-=d;             }         }         for (;b;b=pre[b]) mb[b]=mb[pre[b]],ma[mb[b]]=b;     }     int  res=0 ;     for (int  i=1 ;i<=n;i++) res+=w[i][ma[i]];     return  res;                                              } 
 
二分图两边集合大小为 $n,m$,给定匹配边,改变最少的匹配边使二分图权值匹配最大。
 
将权值扩大 $k$ $(k>n)$ 倍,再将原匹配边权值 $+1$,跑 KM 算法,新二分图最大权值匹配 $/k$ 为原二分图最大权值匹配,最少改变的匹配边为 $n-$ 新二分图最大权值匹配 $%k$。
一般图最大匹配 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 int  n,m,cnt,fa[N],vis[N],pre[N],dfn[N],match[N];VI g[N]; queue<int > q; void  init ()   {    for (int  i=1 ;i<=n;i++) g[i].clear (),match[i]=dfn[i]=0 ;     cnt=0 ; } void  add_edge (int  u,int  v)   {    g[u].PB (v);     g[v].PB (u); } int  find (int  x)   {    return  x==fa[x]?x:fa[x]=find (fa[x]); } int  LCA (int  u,int  v)   {    ++cnt;     u=find (u),v=find (v);     while (dfn[u]!=cnt) {         dfn[u]=cnt;         u=find (pre[match[u]]);         if (v) swap (u,v);     }     return  u; } void  blossom (int  u,int  v,int  lca)   {    while (find (u)!=lca) {         pre[u]=v;         v=match[u];         if (vis[v]==2 ) vis[v]=1 ,q.push (v);         if (find (u)==u) fa[u]=lca;         if (find (v)==v) fa[v]=lca;         u=pre[v];     } } bool  aug (int  s)   {    for (int  i=1 ;i<=n;i++) fa[i]=i,vis[i]=pre[i]=0 ;     while (SZ (q)) q.pop ();     q.push (s);     vis[s]=1 ;     while (SZ (q)) {         int  u=q.front ();         q.pop ();         for (auto  v:g[u]) {             if (find (u)==find (v)||vis[v]==2 ) continue ;             if (!vis[v]) {                 vis[v]=2 ,pre[v]=u;                 if (!match[v]) {                     for (int  x=v,lst;x;x=lst) {                         lst=match[pre[x]];                         match[x]=pre[x];                         match[pre[x]]=x;                     }                     return  true ;                 }                 vis[match[v]]=1 ,q.push (match[v]);             }             else  {                 int  lca=LCA (u,v);                 blossom (u,v,lca);                 blossom (v,u,lca);             }         }     }     return  false ; } int  edmonds ()   {  int  res=0 ;   for (int  i=1 ;i<=n;i++) if (!match[i]) res+=aug (i);   return  res; } 
 
数学 龟速乘 1 2 3 4 5 6 7 8 9 LL qmul (LL a,LL b,LL m)   {     LL ret=0 ;     while (b) {         if (b&1 ) ret=(ret+a)%m;         a=(a+a)%m;         b>>=1 ;     }     return  ret; } 
 
快速幂 1 2 3 4 5 6 7 8 9 LL qpow (LL a,LL b,LL m)   {    LL ret=1 ;     while (b) {         if (b&1 ) ret=ret*a%m;         a=a*a%m;         b>>=1 ;     }     return  ret; } 
 
1 2 3 4 5 6 7 8 9 10 LL qpow (LL a,LL b,LL m)   {    LL ret=1 ;     while (b) {         if (b&1 ) ret=qmul (ret,a,m);         a=qmul (a,a,m);         b>>=1 ;     }     return  ret; } 
 
Miller-Rabin 素性测试 
二次探测定理:如果 $p$ 是奇素数,则 $x^2\equiv 1\pmod p$ 的解为 $x\equiv 1 \pmod p$ 或者 $x\equiv p-1 \pmod p$ 
int 范围内只需检查 $2, 7, 61$ 
long long 范围内 $2, 325, 9375, 28178, 450775, 9780504, 1795265022$ 
$3e15$ 内 $2, 2570940, 880937, 610386380, 4130785767$ 
$4e13$ 内 $2, 2570940, 211991001, 3749873356$ 
http://miller-rabin.appspot.com  
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 bool  check (LL a,LL n)   {    if (n==2 ) return  true ;     if (n==1 ||!(n&1 )) return  false ;     LL d=n-1 ;     while (!(d&1 )) d>>=1 ;     LL t=qpow (a,d,n);     while (d!=n-1 &&t!=1 &&t!=n-1 ) {         t=qmul (t,t,n);         d<<=1 ;     }     return  t==n-1 ||d&1 ; } bool  miller_rabin (LL n)   {    static  vector<LL> P={2 ,325 ,9375 ,28178 ,450775 ,9780504 ,1795265022 };     if (n<=1 ) return  false ;     for (LL x:P) {         if (x>n) break ;         if (!check (x,n)) return  false ;     }     return  true ; } 
 
Pollard-Rho 因式分解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 mt19937 mt (time(0 ))  ;LL pollard_rho (LL n,LL c)   {    LL x=uniform_int_distribution <LL>(1 ,n-1 )(mt),y=x;     auto  f=[&](LL v) {         LL t=qmul (v,v,n)+c;         return  t<n?t:t-n;     };     while (1 ) {         x=f (x),y=f (f (y));         if (x==y) return  n;         LL d=__gcd(abs (x-y),n);         if (d!=1 ) return  d;     } } VI fac;  void  get_fac (LL n,LL cc=19260817 )   {    if (n==4 ) {fac.PB (2 ),fac.PB (2 );return ;}     if (miller_rabin (n)) {fac.PB (n);return ;}     LL p=n;     while (p==n) p=pollard_rho (n,--cc);     get_fac (p),get_fac (n/p); } void  go_fac (LL n)   {    fac.clear ();     if (n<=1 ) return ;     get_fac (n); } 
 
积性函数 定义 若函数 $f(n)$ 满足 $f(1)=1$ 且 $\forall x,y \in \mathbb{N}^+,\gcd(x,y)=1$ 都有 $f(xy)=f(x)f(y)$,则 $f(n)$ 为积性函数。
若函数 $f(n)$ 满足 $f(1)=1$ 且 $\forall x,y \in \mathbb{N}^+$ 都有 $f(xy)=f(x)f(y)$,则 $f(n)$ 为完全积性函数。
性质 若 $f(x)$ 和 $g(x)$ 均为积性函数,则以下函数也为积性函数:
$h(x)=f(x^p)$ 
$h(x)=f^p(x)$ 
$h(x)=f(x)g(x)$ 
$h(x)=\sum_{d \mid x}f(x)g(\frac{x}{d})$ 
 
设 $x=\prod p_i^{k_i}$,
若 $F(x)$ 为积性函数,则有 $F(x)=\prod F(p_i^{k_i})$。 
若 $F(x)$ 为完全积性函数,则有 $F(x)=\prod F(p_i)^{k_i}$。 
 
定理 定理 1.1: 
如果 $f$ 是积性函数,那么 $F(n)=\sum_{d\mid n}f(d)$ 也是积性函数。
证明: 
当 $\gcd(n,m)=1$ 时,$nm$ 的因子必能写成 $n$ 的因子 $d_1$ 与 $m$ 的因子 $d_2$ 之积,
所以 $F(nm)=\sum_{i\mid n}\sum_{j\mid m}f(ij)=\sum_{i\mid n}f(i)\sum_{j\mid m}f(j)=F(n)F(m)$。
例子 
单位函数:$\varepsilon(n)=[n=1]$(完全积性) 
恒等函数:$\operatorname{id}_k(n)=n^k$,$\operatorname{id}_1(n)$ 通常简记作 $\operatorname{id}(n)$(完全积性) 
常数函数:$1(n)=1$(完全积性) 
除数函数:$\sigma_{k}(n)=\sum_{d\mid n}d^k$,$\sigma_{0}(n)$ 通常简记为 $\operatorname{d}(n)$ 或 $\tau(n)$,$\sigma_{1}(n)$ 通常简记为 $\sigma(n)$ 
欧拉函数:$\varphi(n)=\sum_{i=1}^n[\gcd(i,n)=1]$ 
莫比乌斯函数:$\mu(n)=\begin{cases} 1 & n=1 \ 0 & \exists d>1,d^2\mid n \ (-1)^k & k;为;n;的不同质因子个数 \end{cases}$ 
 
Dirichlet 卷积 定义 对于两个数论函数 $f(x)$ 和 $g(x)$,则它们的 Dirichlet 卷积得到的结果 $h(x)$ 定义为:
$$h(x)=\sum_{d\mid x}f(d)g(x/d)=\sum_{ab=x}f(a)g(b)$$
简记为:$h=f*g$。
性质 
交换律:$fg=g f$。 
结合律:$(fg)h=f (g h)$。 
分配律:$(f+g)h=f h+g*h$。 
等式的性质:$f=g$ 的充要条件是 $fg=g h$,其中数论函数 $h(x)$ 要满足 $h(1)\neq 0$。 
单位元:单位函数 $\varepsilon$ 是 Dirichlet 卷积运算中的单位元,即对于任何数论函数 $f$,都有 $f*\varepsilon=f$。 
 
定理 定理 2.1: 
两个积性函数的 Dirichlet 卷积也是积性函数。
证明: 
设两个积性函数为 $f(x)$ 和 $g(x)$,再记 $h=f*g$。
设 $\gcd(a,b)=1$,则 $h(a)=\sum_{d_1\mid a}f(a)g(a/d_1),h(b)=\sum_{d_2\mid b}f(b)g(b/d_2)$,
所以 $h(a)h(b)=\sum_{d_1\mid a}f(a)g(a/d_1)\sum_{d_2\mid b}f(b)g(b/d_2)=\sum_{d\mid ab}f(d)g(ab/d)=h(ab)$。
例子 
$\varepsilon=\mu *1\iff\varepsilon(n)=\sum_{d\mid n}\mu(d)$ 
$d=1*1\iff d(n)=\sum_{d\mid n}1$ 
$\sigma=\operatorname{id}*1\iff \sigma(n)=\sum_{d\mid n}d$ 
$\operatorname{id}=\varphi*1\iff n=\sum_{d\mid n}{\varphi(d)}$ 
$\varphi=\operatorname{id}*\mu\iff\varphi(n)=\sum_{d\mid n}{\mu(d)(n/d)}$ 
 
欧拉函数 定理 定理 3.1: 
$n=\sum_{d\mid n} \varphi(d)$。
证明: 
$f(n)=\sum_{d\mid n} \varphi(d)$ 为积性函数。
证明:当 $\gcd(n,m)=1$ 时,
$\begin{aligned} & f(n)f(m) \ = & \sum_{i\mid n}\varphi(i)\sum_{j\mid m}\varphi(j) \ = & \sum_{i\mid n}\sum_{j\mid m}(\varphi(i)\varphi(j)) \ = & \sum_{i\mid n}\sum_{j\mid m}\varphi(ij) \ = & \sum_{d\mid nm}\varphi(d) \ = & f(nm) \end{aligned}$
证毕。
那么根据算术基本定理 $n=p_1^{c_1}p_2^{c_2}\cdots p_k^{c_k}$,
由 $f$ 是积性函数可知 $f(n)=f(p_1^{c_1})f(p_2^{c_2})\cdots f(p_k^{c_k})$,
又因为 $f(p^c)=\varphi(1)+\varphi(p)+\varphi(p^2)+\cdots+\varphi(p^c)=1+(p-1)+(p^2-p)+\cdots+(p^c-p^{c-1})=p^c$,
所以 $f(n)=f(p_1^{c_1})f(p_2^{c_2})\cdots f(p_k^{c_k})=p_1^{c_1}p_2^{c_2}\cdots p_k^{c_k}=n$。
定理 3.2: 
$\varphi(n)=\sum_{d\mid n}(\mu(d)(n/d))$。
证明: 
根据定理 3.1 可得 $\varphi1=\operatorname{id}\iff \varphi \varepsilon=\operatorname{id}*\mu\iff \varphi(n)=\sum_{d\mid n}(\mu(d)(n/d))$。
莫比乌斯函数 定义 $\mu$ 为莫比乌斯函数,定义为:
$$\mu(n)= \begin{cases} 1 & n=1 \ 0 & \exists d>1,d^2\mid n \ (-1)^k & k;为;n;的不同质因子个数 \end{cases}$$
定理 定理 4.1: 
$\mu(n)$ 是积性函数。
证明: 
假设 $\gcd(n,m)=1$
当 $n=1$ 或 $m=1$ 时,若 $n=1$,$\mu(nm)=\mu(n)\mu(m)=\mu(m)$,$m=1$ 时同理; 
当 $n$ 和 $m$ 中至少有一个数含有平方质因子时,$nm$ 也有平方质因子,$\mu(nm)=\mu(n)\mu(m)=0$; 
当 $n,m$ 都没有平方质因子且都不为 $1$ 时,假设 $n$ 有 $p$ 个不同质因子个数,$m$ 有 $q$ 个不同质因子个数,$\mu(nm)=(-1)^{p+q}=(-1)^p+(-1)^q=\mu(n)\mu(m)$。 
 
定理 4.2: 
$F(n)=\sum_{d \mid n}{\mu(d)}=\begin{cases}1 & n=1 \0 & n>1\end{cases}=\varepsilon(n)=[n=1]$
证明: 
当 $n=1$ 时,$F(1)=\sum_{d \mid 1}{\mu(d)}=\mu(1)=1$;
 
当 $n>1$ 时,因为 $\mu(n)$ 是积性函数,所以 $\mu(n)$ 的因数和函数 $F(n)=\sum_{d \mid n}{\mu(d)}$ 也是积性函数,
  现在假设 $p$ 是质数,$F(n)=F(p_1^{k_1})F(p_2^{k_2})\cdots F(p_t^{k_t})$,
  而 $F(p^k)=\sum_{d \mid p^k}{\mu(d)}=\mu(1)+\mu(p)+\mu(p^2)+\cdots+\mu(p^k)=1+(-1)+0+\cdots+0=0$,
  所以 $F(n)=0$。
 
 
定理 4.3: 
若 $f$ 是数论函数,$F(n)=\sum_{d \mid n}{f(d)}$,则 $f(n)=\sum_{d \mid n}\mu(d)F(n/d)$。
证明: 
$\sum_{d \mid n}\mu(d)F(n/d)=\sum_{d \mid n}\mu(d)\sum_{e \mid (n/d)}{f(e)}=\sum_{e \mid n}f(e)\sum_{d \mid (n/e)}{\mu(d)}$,
根据定理 4.2,当 $e=n$ 时,$\sum_{d \mid (n/e)}{\mu(d)}=1$,否则为 $0$,
所以 $\sum_{e \mid n}(f(e)\sum_{d \mid (n/e)}{\mu(d)})=f(n)$。
筛 线性筛 
$1\sim n$ 的素数个数近似为 $\frac {n} {\ln{n}}$ 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int  mn[N];VI p; void  prime_seive (int  n)   {    for  (int  i = 2 ; i <= n; i++) {         if  (!mn[i]) {             mn[i] = i,             p.PB (i);         }         for  (auto  x : p) {             if  (x > mn[i] || x * i > n) {                 break ;             }             mn[x * i] = x;         }     } } 
 
线性筛 & 欧拉函数 $\varphi(N)=N * \prod_{p\mid N}{(1-\frac{1}{p})}$($p$ 为质数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int  mn[N], phi[N];VI p; void  get_phi (int  n)   {    phi[1 ] = 1 ;     for  (int  i = 2 ; i <= n; i++) {         if  (!mn[i]) {             mn[i] = i,             p.PB (i);             phi[i] = i - 1 ;         }         for  (auto  x : p) {             if  (x > mn[i] || x * i > n) {                 break ;             }             mn[x * i] = x;             phi[x * i] = i % x ? phi[i] * (x - 1 ) : phi[i] * x;         }     } } 
 
线性筛 & 莫比乌斯函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int  mn[N], mu[N];VI p; void  get_mu (int  n)   {    mu[1 ] = 1 ;     for  (int  i = 2 ; i <= n; i++) {         if  (!mn[i]) {             mn[i] = i,             mu[i] = -1 ,             p.PB (i);         }         for  (auto  x : p) {             if  (x * i > n) {                 break ;             }             mn[x * i] = x;             if  (i % x == 0 ) {                 mu[x * i] = 0 ;                 break ;             }             mu[x * i] = -mu[i];         }     } } 
 
杜教筛 求 $S(n)=\sum_{i=1}^n{f(i)}$,其中 $f$ 是一个数论函数。
构造一个积性函数 $g$,求 $g*f$ 的前缀和:
$\begin{aligned} \sum_{i=1}^n(g*f)(i)= & \sum_{i=1}^n\sum_{d\mid i}{g(d)f(\frac{i}{d})} \ = & \sum_{d=1}^ng(d)\sum_{d\mid i}f(\frac{i}{d}) \ = & \sum_{d=1}^ng(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}{f(i)} \ = & \sum_{d=1}^ng(d)S(\lfloor\frac{n}{d}\rfloor) \end{aligned}$
容斥一下,有 $g(1)S(n)=\sum_{i=1}^ng(i)S(\lfloor\frac{n}{i}\rfloor)-\sum_{i=2}^ng(i)S(\lfloor\frac{n}{i}\rfloor)=\sum_{i=1}^n(g*f)(i)-\sum_{i=2}^ng(i)S(\lfloor\frac{n}{i}\rfloor)$。
前半部分是 Dirichlet 卷积的前缀和,后半部分可以数论分块求。
我们要做的是构造一个 $g$ 使 $g*f$ 的前缀和好算。
例子:
$\sum_{i=1}^n\mu(i)$
由 $\sum_{d\mid n}\mu(d)=[n=1]$,选择 $g=1(i)$,那么 $\sum_{i=1}^n{(1*\mu)(i)}=1$,所以 $S(n)=\sum_{i=1}^n{\mu(i)}=1-\sum_{i=2}^nS(\lfloor\frac{n}{i}\rfloor)$。
 
$\sum_{i=1}^n\varphi(i)$
由 $\sum_{d\mid n}\varphi(d)=n$,选择 $g=1(i)$,那么 $\sum_{i=1}^n{(1*\varphi)(i)}=\frac{n(n+1)}{2}$,所以  $S(n)=\sum_{i=1}^n{\varphi(i)}=\frac{n(n+1)}{2}-\sum_{i=2}^nS(\lfloor\frac{n}{i}\rfloor)$。
 
 
要能够由此计算 $S(n)$,对 $f,g$ 有一些要求:
$f*g$ 能够快速求前缀和。 
$g$ 能够快速求分段和(前缀和)。 
 
线性筛预处理 $S$ 的前 $n^{\frac{2}{3}}$ 项,剩余部分的时间复杂度为 $O(n^{\frac{2}{3}})$,对于较大的值,用 map 存其对应的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 LL mn[N],phi[N],mu[N]; VI p; unordered_map<LL,LL> _phi,_mu; void  init (int  n)   {    phi[1 ]=mu[1 ]=1 ;     for (int  i=2 ;i<=n;i++) {         if (!mn[i]) mn[i]=i,mu[i]=-1 ,phi[i]=i-1 ,p.PB (i);         for (auto  x:p) {             if (x*i>n) break ;             mn[x*i]=x;             if (i%x==0 ) {mu[x*i]=0 ,phi[x*i]=phi[i]*x;break ;}             mu[x*i]=-mu[i];             phi[x*i]=i%x?phi[i]*(x-1 ):phi[i]*x;         }     }     for (int  i=1 ;i<=n;i++) phi[i]+=phi[i-1 ],mu[i]+=mu[i-1 ]; } LL calc_phi (LL n)   {    if (n<=LIM) return  phi[n];     if (_phi.count (n)) return  _phi[n];     LL ret=0 ;     for (LL l=2 ,r;l<=n;l=r+1 ) {         r=n/(n/l);         ret+=(r-l+1 )*calc_phi (n/l);     }     return  _phi[n]=n*(n+1 )/2 -ret; } LL calc_mu (LL n)   {    if (n<=LIM) return  mu[n];     if (_mu.count (n)) return  _mu[n];     LL ret=0 ;     for (LL l=2 ,r;l<=n;l=r+1 ) {         r=n/(n/l);         ret+=(r-l+1 )*calc_mu (n/l);     }     return  _mu[n]=1 -ret; } 
 
欧拉定理 
欧拉定理:$a^{\varphi(m)}\equiv 1\pmod m$ $(a,m\in \mathbb{Z}^+,a\perp m)$
 
费马小定理:$a^{p-1}\equiv 1\pmod p$($p$ 为质数,$a\perp p$)
 
扩展欧拉定理:
 
 
$$ a^b\equiv \begin{cases} {a^{b\bmod\varphi(m)}} & a\perp m\ {a^b} & a\not \perp m,b<\varphi(m)\ {a^{b\bmod\varphi(m)+\varphi(m)}} &a\not \perp m,b\ge \varphi(m) \end{cases} \pmod{m} $$
也可表示为:
$$ a^b\equiv \begin{cases} a^b & b<\varphi(m)\ a^{b\bmod\varphi(m)+\varphi(m)} & b\ge \varphi(m) \end{cases} \pmod{m} $$
若 $a\perp m$,则满足 $a^x\equiv 1\pmod m$ 的最小正整数 $x_0$ 是 $\varphi(n)$ 的约数 
 
扩展欧几里得算法 裴蜀定理:对于任意整数 $a,b$,存在一对整数,满足$ax+by=\gcd(a,b)$
即 $ax+by=c$,当 $\gcd(a,b)\mid c$,等式成立
若 $x’$,$y’$ 为方程  $ax+by=\gcd(a,b)$ 的一组解,则该方程的任意解表示为:$x=x’+kb/\gcd(a,b),y=y’-ka/\gcd(a,b)$,且对任意整数 $k$ 都成立
 
设 $t=b/\gcd(a,b)$,那么 $x$ 的最小非负整数解 $x=(x’%t+t)%t$
 
 
1 2 3 4 5 6 LL exgcd (LL a,LL b,LL &x,LL &y)   {    if (!b) {x=1 ,y=0 ;return  a;}     LL ret=exgcd (b,a%b,y,x);     y-=a/b*x;     return  ret; } 
 
线性同余方程 形如 $ax\equiv b\pmod m$ 的方程被称为线性同余方程
方程 $ax\equiv b\pmod m$ 等价于方程 $ax+my=b$,有整数解的充要条件为 $gcd(a,m)\mid b$
乘法逆元 如果一个线性同余方程 $ax\equiv 1\pmod m,a\perp m$,则称 $x$ 为 $a\bmod m$ 的乘法逆元,记作 $a^{-1}$
当模数 $m$ 为质数时,$a^{m-2}$ 为 $a$ 的乘法逆元 
当模数 $m$ 为合数时,通过求解同余方程 $ax\equiv 1\pmod m$ 可得到乘法逆元 
 
1 2 3 4 5 6 LL get_inv (LL a,LL m)   {    LL x,y;     if (exgcd (a,m,x,y)!=1 ) return  -1 ;     return  (x%m+m)%m; } 
 
1 2 3 4 5 LL inv[N]; void  init_inv (LL n)   {    inv[1 ]=1 ;     for (LL i=2 ;i<=n;i++) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD; } 
 
1 2 3 4 5 6 7 8 9 LL fac[N],invfac[N]; void  init_invfac (LL n)   {    n=min (n,MOD-1 );     fac[0 ]=1 ;     for (LL i=1 ;i<=n;i++) fac[i]=fac[i-1 ]*i%MOD;     invfac[n]=qpow (fac[n],MOD-2 );     for (LL i=n-1 ;~i;i--) invfac[i]=invfac[i+1 ]*(i+1 )%MOD; } 
 
中国剩余定理 中国剩余定理可求解如下形式的一元线性同余方程组(其中 $m_1,m_2,\cdots,n_k$ 两两互质):
$$ \begin{cases} x & \equiv a_1 \pmod {m_1} \ x & \equiv a_2 \pmod {m_2} \   & \vdots\ x & \equiv a_k \pmod {m_k} \end{cases} $$
算法流程 
计算所有模数的积 $M$
 
对于第 $i$ 个方程:
a. 计算 $b_i=\frac m {m_i}$
b. 计算 $b_i$ 在模 $m_i$ 意义下的逆元 $b_i^{-1}$
c. 计算 $c_i=b_i b_i^{-1}$( 不要对 $m_i$ 取模 )
 
方程组的特解为:$Y=\sum_{i=1}^k a_i c_i \pmod M$,通解为 $kM+Y(k\in \mathbb Z)$
 
 
1 2 3 4 5 6 7 8 9 10 11 LL crt (LL *m,LL *a,int  n)   {    LL M=1 ,res=0 ;     for (int  i=1 ;i<=n;i++) M*=m[i];     for (int  i=1 ;i<=n;i++) {         LL b=M/m[i],x,y;         LL d=exgcd (b,m[i],x,y);         res=(res+qmul (qmul (b,a[i],M),(x%m[i]+m[i])%m[i],M))%M;     }     return  res; } 
 
扩展:模数不互质的情况 
设两个方程分别是 $x\equiv a_1\pmod {m_1}$、$x\equiv a_2\pmod {m_2}$
将它们转为不定方程:$x=m_1+a_1=m_2q+a_2$,其中 $p,q$ 是整数,则有 $m_1 p-m_2 q=a_2-a_1$
由裴蜀定理,当 $a_2-a_1$ 不能被 $\gcd(m_1,m_2)$ 整除时,无解
其他情况下,可以通过扩展欧几里得算法解出来一组可行解 $(p,q)$
则原来的两方程组成的模方程组的解为 $x\equiv b\pmod M$,其中 $b=m_1p+a_1$,$M=\text{lcm}(m_1,m_2)$
两两合并多个方程,能得出特解 $x$,通解为 $k*\text{lcm}(m_1,m_2,\cdots,m_n)+x(k\in \mathbb Z)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 LL excrt (LL *m,LL *a,int  n)   {    if (!n) return  0 ;     LL M=m[1 ],A=a[1 ],x,y;     for (int  i=2 ;i<=n;i++) {         LL d=exgcd (M,m[i],x,y);         LL c=(a[i]-A%m[i]+m[i])%m[i];         if (c%d) return  -1 ;         x=qmul (x,c/d,m[i]/d);          A+=M*x;         M*=m[i]/d;         A%=M;     }     return  (A%M+M)%M; } 
 
离散对数 BSGS 在 $O(\sqrt m)$ 的时间内求解 $a^x\equiv\pmod m,a\perp m$
方程的解 $x$ 满足 $0\le x <m$
令 $x=A\lceil \sqrt m \rceil -B (0\le A,B\le\lceil \sqrt m \rceil)$,则有 $a^{A\lceil \sqrt m \rceil -B} \equiv b\pmod m$,即 $a^{A\lceil \sqrt m \rceil} \equiv ba^B\pmod m$
枚举 $B$,算出 $ba^B$ 的所有取值,然后枚举 $A$,逐一计算 $a^{A\lceil \sqrt m \rceil}$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 LL BSGS (LL a,LL b,LL m)   {    a%=m,b%=m;     if (!a&&!b) return  1 ;     if (!a) return  -1 ;     static  unordered_map<LL,LL> mp;     mp.clear ();     LL r=sqrt (m+1.5 ),v=1 ;     for (int  i=1 ;i<=r;i++) {         v=v*a%m;         mp[b*v%m]=i;     }     LL vv=v;     for (int  i=1 ;i<=r;i++) {         auto  it=mp.find (vv);         if (it!=mp.end ()) return  i*r-it->second;         vv=vv*v%m;     }     return  -1 ; } 
 
数论分块 
$\sum_{i=1}^n \lfloor \frac ni \rfloor$ 
$O(\sqrt{n})$ 
 
1 2 3 4 5 6 7 8 LL solve (int  n)   {    LL res=0 ;     for (int  l=1 ,r;l<=n;l=r+1 ) {         r=n/(n/l);         res+=1ll *(r-l+1 )*(n/l);     }     return  res; } 
 
1 2 3 4 5 6 7 LL solve (int  n)   {    LL res=0 ;     int  t=sqrt (n);     for (int  i=1 ;i<=t;i++) res+=n/i;     res=res*2 -1ll *t*t;     return  res; } 
 
$\sum_{i=1}^n \sum_{j=1}^m\lfloor \frac ni \rfloor \lfloor \frac mi \rfloor$ 
 
1 2 3 4 5 6 7 8 9 LL solve (int  n,int  m)   {    LL res=0 ;     int  lim=min (n,m);     for (int  l=1 ,r;l<=lim;l=r+1 ) {         r=min (n/(n/l),m/(m/l));         res+=1ll *(r-l+1 )*(n/l)*(m/l);     }     return  res; } 
 
二次剩余 一个数 $a$,如果不是 $p$ 的倍数且模 $p$ 同余于某个数的平方,则称 $a$ 为模 $p$ 的 二次剩余。
而一个不是 $p$ 的倍数的数 $b$,不同余于任何数的平方,则称 $b$ 为模 $p$ 的 非二次剩余。
$x^2\equiv n\pmod{p}$($n>0$,$p$ 为质数),对常数 $n$ 求 $x$。
当解得 $x>0$ 时, $p-x$ 也是一个解。
Cipolla 算法 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 LL w; struct  C  {    LL r,i;     C () {}     C (LL r,LL i):r (r),i (i) {} }; C mul (C a,C b,LL p)   {    C ret=C (0 ,0 );     ret.r=((a.r*b.r%p+a.i*b.i%p*w%p)%p+p)%p;     ret.i=((a.r*b.i%p+a.i*b.r%p)%p+p)%p;     return  ret; } LL qpow_r (LL a,LL b,LL p)   {    LL ret=1 ;     while (b) {         if (b&1 ) ret=ret*a%p;         a=a*a%p;         b>>=1 ;     }     return  ret; } LL qpow_i (C a,LL b,LL p)   {    C ret=C (1 ,0 );     while (b) {         if (b&1 ) ret=mul (ret,a,p);         a=mul (a,a,p);         b>>=1 ;     }     return  ret.r; } LL cioplla (LL n,LL p)   {    n%=p;     if (!n) return  0 ;     if (p==2 ) return  n;     if (qpow_r (n,(p-1 )/2 ,p)==p-1 ) return  -1 ;     LL a;     for (;;) {         a=rand ()%p;         w=((a*a%p-n)%p+p)%p;         if (qpow_r (w,(p-1 )/2 ,p)==p-1 ) break ;     }     C x=C (a,1 );     return  qpow_i (x,(p+1 )/2 ,p); } 
 
矩阵运算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 struct  M  {    LL a[N][N];     void  clear ()   {memset (a,0 ,sizeof  a);}     M () {clear ();}     void  init ()   {         clear ();         for (int  i=0 ;i<N;i++) a[i][i]=1 ;     }     M operator  + (const  M &T) const  {         M ret;         for (int  i=0 ;i<N;i++)             for (int  j=0 ;j<N;j++)                 ret.a[i][j]=(a[i][j]+T.a[i][j])%MOD;         return  ret;     }     M operator  - (const  M &T) const  {         M ret;         for (int  i=0 ;i<N;i++)             for (int  j=0 ;j<N;j++)                 ret.a[i][j]=(a[i][j]-T.a[i][j]+MOD)%MOD;         return  ret;     }     M operator  * (const  LL &v) const  {         M ret;         for (int  i=0 ;i<N;i++)             for (int  j=0 ;j<N;j++) if (a[i][j])                 ret.a[i][j]=a[i][j]*v%MOD;         return  ret;     }     M operator  * (const  M &T) const  {         M ret;         for (int  i=0 ;i<N;i++)             for (int  k=0 ;k<N;k++) if (a[i][k])                 for (int  j=0 ;j<N;j++) if (T.a[k][j])                     ret.a[i][j]=(ret.a[i][j]+a[i][k]*T.a[k][j]%MOD)%MOD;         return  ret;     }     M operator  ^ (LL b) const  {         M ret,bas;         for (int  i=0 ;i<N;i++) ret.a[i][i]=1 ;         for (int  i=0 ;i<N;i++)             for (int  j=0 ;j<N;j++)                 bas.a[i][j]=a[i][j];         while (b) {             if (b&1 ) ret=ret*bas;             bas=bas*bas;             b>>=1 ;         }         return  ret;     }     void  print ()   {         for (int  i=0 ;i<N;i++)             for (int  j=0 ;j<N;j++)                 cout<<a[i][j]<<" \n" [j==N-1 ];     } }; 
 
高斯消元 高斯消元解线性方程组 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 const  DB EPS=1e-9 ;DB a[N][N],x[N];  bool  free_x[N]; int  sgn (DB x)   {return  fabs (x)<EPS?0 :(x>0 ?1 :-1 );}int  gauss (int  n,int  m)   {     memset (x,0 ,sizeof  x);     memset (free_x,true ,sizeof  free_x);     int  r=0 ,c=0 ;     while (r<n&&c<m) {         int  R=r;         for (int  i=r+1 ;i<n;i++) if (fabs (a[i][c])>fabs (a[R][c])) R=i;         if (R!=r) for (int  j=c;j<=m;j++) swap (a[r][j],a[R][j]);         if (!sgn (a[r][c])) {a[r][c]=0 ;++c;continue ;}         for (int  i=r+1 ;i<n;i++) if (a[i][c]) {             DB t=a[i][c]/a[r][c];             for (int  j=c;j<=m;j++) a[i][j]-=a[r][j]*t;         }         ++r,++c;     }     for (int  i=r;i<n;i++) if (sgn (a[i][m])) return  -1 ;     if (r<m) {         for (int  i=r-1 ;i>=0 ;i--) {             int  cnt=0 ,k=-1 ;             for (int  j=0 ;j<m;j++) if (sgn (a[i][j])&&free_x[j]) ++cnt,k=j;             if (cnt>1 ) continue ;             DB s=a[i][m];             for (int  j=0 ;j<m;j++) if (sgn (a[i][j])&&j!=k) s-=a[i][j]*x[j];             x[k]=s/a[i][k];             free_x[k]=false ;         }         return  m-r;      }     for (int  i=m-1 ;i>=0 ;i--) {         DB s=a[i][m];         for (int  j=i+1 ;j<m;j++) s-=a[i][j]*x[j];                  x[i]=s/a[i][i];     }     return  0 ; } 
 
高斯消元解 XOR 方程组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int  a[N][N],x[N]; VI free_x; int  gauss (int  n,int  m)   {     memset (x,0 ,sizeof  x);     free_x.clear ();     int  r=0 ,c=0 ;     while (r<n&&c<m) {         int  R=r;         for (int  i=r+1 ;i<n;i++) if (a[i][c]>a[R][c]) R=i;         if (R!=r) for (int  j=c;j<=m;j++) swap (a[r][j],a[R][j]);         if (!a[r][c]) {free_x.PB (c),++c;continue ;}         for (int  i=r+1 ;i<n;i++) if (a[i][c]) {             int  t=a[i][c]^a[r][c];             for (int  j=c;j<=m;j++) a[i][j]^=a[r][j]^t;         }         ++r,++c;     }     for (int  i=r;i<n;i++) if (a[i][m]) return  INT_MAX;      int  mn=INT_MAX;          for (int  i=0 ;i<1 <<(m-r);i++) {          int  cnt=0 ,idx=i;         for (int  j=0 ;j<m-r;j++) {             x[free_x[j]]=idx&1 ;             if (x[free_x[j]]) ++cnt;             idx>>=1 ;         }         for (int  j=r-1 ;j>=0 ;j--) {             int  k=j;             while (!a[j][k]) ++k;             x[k]=a[j][m];             for (int  l=k+1 ;l<m;l++) x[k]^=a[j][l]&x[l];             if (x[k]) ++cnt;         }         mn=min (mn,cnt);     }     return  mn;  } 
 
高斯消元解同余方程组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const  int  MOD=7 ;int  a[N][N],x[N]; int  LCM (int  a,int  b)   {    return  a/__gcd(a,b)*b; } LL get_inv (LL a,LL m)   {    if (a==1 ) return  1 ;     return  get_inv (m%a,m)*(m-m/a)%m; } int  gauss (int  n,int  m)   {     memset (x,0 ,sizeof  x);     int  r=0 ,c=0 ;     while (r<n&&c<m) {         int  R=r;         for (int  i=r+1 ;i<n;i++) if (abs (a[i][c])>abs (a[R][c])) R=i;         if (R!=r) for (int  j=c;j<=m;j++) swap (a[r][j],a[R][j]);         if (!a[r][c]) {++c;continue ;}         for (int  i=r+1 ;i<n;i++) if (a[i][c]) {             int  lcm=LCM (a[i][c],a[r][c]);             int  ta=lcm/a[i][c],tb=lcm/a[r][c];             for (int  j=c;j<=m;j++) a[i][j]=(a[i][j]*ta%MOD-a[r][j]*tb%MOD+MOD)%MOD;         }         ++r,++c;     }     for (int  i=r;i<n;i++) if (a[i][m]) return  -1 ;      if (r<m) return  m-r;      for (int  i=m-1 ;i>=0 ;i--) {          int  s=a[i][m];         for (int  j=i+1 ;j<m;j++) s=(s-a[i][j]*x[j]%MOD+MOD)%MOD;         x[i]=s*get_inv (a[i][i],MOD)%MOD;     }     return  0 ; } 
 
线性基 线性基是向量空间的一组基,通常可以解决有关异或的一些题目
通俗一点的讲法就是由一个集合构造出来的另一个集合,它有以下几个性质:
线性基的元素能相互异或得到原集合的元素的所有相互异或得到的值 
线性基是满足性质 $1$ 的最小的集合 
线性基没有异或和为 $0$ 的子集 
线性基中每个元素的异或方案唯一,也就是说,线性基中不同的异或组合异或出的数都是不一样的 
线性基中每个元素的二进制最高位互不相同 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 const  int  N=100 ;int  n,tot;LL d[N]; bool  add (LL x)   {     for (int  i=62 ;~i;i--) if ((x>>i)&1 ) {         if (!d[i]) {d[i]=x;return  true ;}         x^=d[i];     }     return  false ; } void  calc ()   {     tot=0 ;     for (int  i=0 ;i<63 ;i++) if (d[i]) ++tot; } LL get_max ()   {     LL ret=0 ;     for (int  i=62 ;~i;i--) if (ret^d[i]>ret) ret^=d[i];     return  ret; } LL get_min ()   {     if (tot<n) return  0 ;     for (int  i=0 ;i<63 ;i++) if (d[i]) return  d[i];     return  -1 ; } void  update ()   {    for (int  i=0 ;i<63 ;i++)         for (int  j=0 ;j<i;j++)             if ((d[i]>>j)&1 )                 d[i]^=d[j]; } LL kth (int  k)   {     if (tot<n) --k;      LL ret=0 ;     for (int  i=0 ;i<63 ;i++) if (d[i]) {         if (k&1 ) ret^=d[i];         k>>=1 ;     }     if (k) return  -1 ;     return  ret; } void  init ()   {    memset (d,0 ,sizeof  d);     for (int  i=1 ;i<=n;i++) {         LL x;cin>>x;         add (x);     }     calc ();     update (); } 
 
线性递推数列 一阶线性递推数列 对于一阶线性递推数列,形如 $a_{n+1}=pa_n+q$,可采用参数法求通项。
对于 $a_{n+1}=pa_n+q$,设 $a_{n+1}-t=p(a_n-t)$,
得:$q=t-pt$,解出 $t$ 代入上式,用等比数列通项得到:$a_n=p^{n-1}(a_1-t)+t$。
二阶线性递推数列 对于二阶线性递推数列,形如 $a_{n+2}=pa_{n+1}+qa_n$ $(q\neq 0)$,可采用特征方程法求通项。
对于 $a_{n+2}=pa_{n+1}+qa_n$,设有 $s$ 和 $r$,使 $a_{n+2}-sa_{n+1}=r(a_{n+1}-sa_{n})$。
得:$s+r=p$,$rs=-q$,易发现这是韦达定理。
那么 $s,r$ 为一元二次方程 $x^2-px-q=0$ 的解。
称一元二次方程 $x^2-px-q=0$ 为递推数列 $a_{n+2}=pa_{n+1}+qa_n$ $(q\neq 0)$ 的特征方程。
在等式 $a_{n+2}-sa_{n+1}=r(a_{n+1}-sa_{n})$ 中,易发现 $s,r$ 是可以交换的。
那么可以得到方程组:
$\begin{cases} a_{n+2}-sa_{n+1}=r^{n-1}(a_2-sa_1) \ a_{n+2}-ra_{n+1}=s^{n-1}(a_2-ra_1) \end{cases}$
若 $s\neq r$,$a_n=\frac{r^{n-1}(a_2-sa_1)-s^{n-1}(a_2-ra_1)}{r-s}$; 
若 $s=r$,则 $a=b\neq 0$,$a_n=(2-n)s^{n-1}a_1+(n-1)s^{n-2}a_2$。 
 
概率论 几何分布 在 $n$ 次伯努利试验中,试验 $k$ 次才得到第一次成功的机率。
记在伯努利试验中,成功的概率为 $p$,试验次数为 $x$。
$P(x)=(1-p)^{x-1}p$
$E(x)=\frac{1}{p}$
超几何分布 多项式 FFT 
$n$ 必须超过 $a,b$ 最高指数之和 
空间大于 $a,b$ 最高指数之和的两倍 
所求系数是整数时,卷积后注意四舍五入 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 const  LD PI=acos (-1 );namespace  FFT {    struct  C  {         LD r,i;         C (LD r=0 ,LD i=0 ):r (r),i (i) {}         C operator  + (const  C &T) const  {             return  C (r+T.r,i+T.i);         }         C operator  - (const  C &T) const  {             return  C (r-T.r,i-T.i);         }         C operator  * (const  C &T) const  {             return  C (r*T.r-i*T.i,r*T.i+i*T.r);         }     };     void  FFT (C x[],int  n,int  on)   {         VI rev (n)  ;         for (int  i=0 ;i<n;i++) {             rev[i]=rev[i>>1 ]>>1 |(i&1 ?n>>1 :0 );             if (i<rev[i]) swap (x[i],x[rev[i]]);         }         for (int  mid=1 ;mid<n;mid<<=1 ) {             C wn (cos(PI/mid),sin(on*PI/mid))  ;             for (int  i=0 ;i<n;i+=(mid<<1 )) {                 C w (1 ,0 )  ;                 for (int  j=0 ;j<mid;j++,w=w*wn) {                     C t1=x[i+j],t2=w*x[i+j+mid];                     x[i+j]=t1+t2,x[i+j+mid]=t1-t2;                 }             }         }         if (on==-1 ) for (int  i=0 ;i<n;i++) x[i].r/=n;     }     void  conv (C a[],C b[],int  n)   {         FFT (a,n,1 );         FFT (b,n,1 );         for (int  i=0 ;i<n;i++) a[i]=a[i]*b[i];         FFT (a,n,-1 );     } } 
 
NTT 
$998244353,1004535809,469762049$ 原根都为 $3$ 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 namespace  NTT {   const  LL G=3 ,MOD=998244353 ;    LL qpow (LL a,LL b)   {        LL ret=1 ;        while (b) {            if (b&1 ) ret=ret*a%MOD;            a=a*a%MOD;            b>>=1 ;        }        return  ret;    }     void  NTT (LL x[],int  n,int  on)   {         VI rev (n)  ;         for (int  i=0 ;i<n;i++) {             rev[i]=rev[i>>1 ]>>1 |(i&1 ?n>>1 :0 );             if (i<rev[i]) swap (x[i],x[rev[i]]);         }         for (int  mid=1 ;mid<n;mid<<=1 ) {             LL wn=qpow (on==1 ?G:qpow (G,MOD-2 ),(MOD-1 )/(mid<<1 ));             for (int  i=0 ;i<n;i+=(mid<<1 )) {                 LL w=1 ;                 for (int  j=0 ;j<mid;j++,w=w*wn%MOD) {                     LL t1=x[i+j],t2=w*x[i+j+mid]%MOD;                     x[i+j]=(t1+t2)%MOD,x[i+j+mid]=(t1-t2+MOD)%MOD;                 }             }         }         if (on==-1 ) {             LL inv=qpow (n,MOD-2 );             for (int  i=0 ;i<n;i++) x[i]=x[i]*inv%MOD;         }     }     void  conv (LL a[],LL b[],int  n)   {         NTT (a,n,1 );         NTT (b,n,1 );         for (int  i=0 ;i<n;i++) a[i]=a[i]*b[i]%MOD;         NTT (a,n,-1 );     } } 
 
博弈论 巴什博弈 一堆 $n$ 个物品,两个人轮流从中取出 $1 \sim m$个,最后取光者胜。
先手必败条件:$n%(m+1)=0$。
威佐夫博弈 有两堆各若干个物品,两个人轮流从任一堆取至少一个或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
先手必败条件:$|n-m|*\frac{1+\sqrt{5}}{2}=\min(n,m)$。
斐波那契博弈 一堆石子有 $n$ 个,两人轮流取,先取者第一次可以取任意多个,但是不能取完,以后每次取的石子数不能超过上次取子数的 $2$ 倍。取完者胜。
先手必败条件:$n$ 是斐波那契数。
Nim 游戏 有 $n$ 堆石子,两人轮流取,每次取某堆中不少于 $1$ 个,最后取完者胜。
先手必败条件:各堆石子数目全部异或后结果等于 $0$。
阶梯 Nim 游戏 有 $n$ 个位置 $1\dots n$,每个位置上有 $a_i$ 个石子。两人轮流操作。每次挑选 $1\dots n$ 中任一一个存在石子的位置 $i$,将至少 $1$ 个石子移动至 $i−1$ 位置(也就是最后所有石子都堆在 $0$ 这个位置),谁不能操作谁输。
先手必败条件:奇数位置石子数目全部异或后结果等于 $0$。
反 Nim 游戏 有 $n$ 堆石子,两人轮流取,每次取某堆中不少于 $1$ 个,最后取完者败。
先手必胜条件:
各堆石子数目异或结果等于 $0$,且所有石子堆数目全部为 $1$。
 
各堆石子数目异或结果不等于 $0$,且存在有石子数目大于 $1$ 的石子堆。
 
 
公式 高数公式 
$\lim \limits_{x\to 0}\frac{\sin x}{x}=\lim \limits_{x\to 0}\frac{x}{\sin x}=1$
 
$\lim \limits_{x\to\infty}(1+\frac{1}{x})^x=\lim\limits_{x\to 0}(1+x)^{\frac{1}{x}}=\mathrm{e}$
 
$\int_0^1{x^{a-1}(1-x)^{b-1}}=\frac{1}{b\binom{a+b-1}{a-1}}(a,b\in\mathbb{Z}^+)$
 
 
莫比乌斯反演 $F(n)=\sum_{d\mid n}f(d)\Leftrightarrow f(n)=\sum_{d\mid n}\mu(d)F(\frac{n}{d})$
$F(n)=\sum_{n\mid d}f(d)\Leftrightarrow f(n)=\sum_{n\mid d}\mu(\frac{d}{n})F(d)$
数论公式 1. $\sum_{i=1}^{n}\sum_{j=1}^{m}{\gcd(i,j)}$ 由 $\varphi*1=\operatorname{id}$ 得:
$\sum_{i=1}^{n}\sum_{j=1}^{m}{\gcd(i,j)}=\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{d\mid \gcd(i,j)}{\varphi(d)}=\sum_{d=1}\varphi(d)\lfloor{\frac{n}{d}}\rfloor\lfloor{\frac{m}{d}}\rfloor$
2. $\sum_{i=1}^n{i[\gcd(i,n)=1]}$ 
当 $n=1$ 时,$\sum_{i=1}^n{i[\gcd(i,n)=1]}=1$。
 
当 $n>1$ 时,$\gcd(i,n)=\gcd(n-i,n)$,与 $n$ 互质的数是成对出现的。
因此 $\sum_{i=1}^n{i[\gcd(i,n)=1]}=\frac{n\varphi(n)}{2}$。
 
 
所以 $\sum_{i=1}^n{i[\gcd(i,n)=1]}=\frac{n\varphi(n)+[n=1]}{2}$。
3. $\mu^2(n)=\sum_{d^2\mid n}\mu(d)$ 证明:
$n$ 无平方因子,$\mu^2(n)=1$。
 
$n$ 有平方因子,徦设 $d’$ 是最大的数满足 $d’^2\mid n$ 且 $d’$ 无平方因子,那么 $n$ 的其它因子当且仅当其质因子构成的可重集是 $d$ 的质因子集合的子集时,才对 $\sum_{d^2\mid n}\mu(d)$ 产生贡献。
假设  $d’$ 的质因子集合大小为 $m$,那么考虑枚举 $d’$ 质因子集合的子集:$\sum_{i=0}^m{(-1)^i\binom{m}{i}}=(1-1)^m=0$
 
 
所以 $\mu^2(n)=\sum_{d^2\mid n}\mu(d)$ 成立。
4. $\sum_{i=1}^n{\mu^2(i)}$ $\sum_{i=1}^n{\mu^2(i)}=\sum_{i=1}^n\sum_{d^2\mid i}{\mu(d)}=\sum_{d=1}^{\sqrt{n}}{\lfloor\frac{n}{d^2}\rfloor}\mu(d)$
5. $\sum_{i=1}^n{\operatorname{lcm}(i,n)}$ $\begin{aligned} \sum_{i=1}^n{\operatorname{lcm}(i,n)}= & \sum_{i=1}^n{\frac{in}{\gcd(i,n)}} \ = & \sum_{d\mid n}(n\sum_{i=1}^n{\frac{i}{d}[\gcd(i,n)=d]}) \ = & \sum_{d\mid n}n\sum_{i=1}^{\frac{n}{d}}{i[\gcd(i,\frac{n}{d})=1]} \ = & n\sum_{d\mid n}\frac{(\frac{n}{d})\varphi(\frac{n}{d})+[\frac{n}{d}=1]}{2} \ = & n\sum_{d\mid n}\frac{d\varphi(d)+[d=1]}{2} \ \end{aligned}$
6. $\sum_{i=1}^n\sum_{j=1}^m{\operatorname{lcm}(i,j)}$ $\begin{aligned} \sum_{i=1}^n\sum_{j=1}^m{\operatorname{lcm}(i,j)} = & \sum_{i=1}^n\sum_{j=1}^m{\frac{ij}{\gcd(i,j)}} \ = & \sum_{d=1}\sum_{i=1}^n\sum_{j=1}^m{\frac{ij[\gcd(i,j)=d]}{d}} \ = & \sum_{d=1}\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}{ij[\gcd(i,j)=1]} \ = & \sum_{d=1}d\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}{ij[\gcd(i,j)=1]} \end{aligned}$
设 $f(n,m)=\sum_{i=1}^n\sum_{j=1}^m{ij[\gcd(i,j)=1]}$,
$\begin{aligned} f(n,m)= & \sum_{i=1}^n\sum_{j=1}^m\sum_{d\mid \gcd(i,j)}{ij\mu(d)} \ = & \sum_{d=1}\sum_{d\mid i}^n\sum_{d\mid j}^m{ij\mu(d)} \ = & \sum_{d=1}d^2\mu(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}{ij} \ \end{aligned}$
设 $g(n,m)=\sum_{i=1}^{n}\sum_{j=1}^{m}{ij}$,
根据等差数列求和公式得 $g(n,m)=(n+\frac{n(n-1)}{2})(m+\frac{m(m-1)}{2})=\frac{nm(n+1)(m+1)}{4}$,
$f(n,m)=\sum_{d=1}d^2\mu(d)g(\lfloor\frac{n}{d}\rfloor,\lfloor\frac{m}{d}\rfloor)$,数论分块加速求和。
因此 $\sum_{i=1}^n\sum_{j=1}^m{\operatorname{lcm}(i,j)}=\sum_{d=1}df(\lfloor\frac{n}{d}\rfloor,\lfloor\frac{m}{d}\rfloor)$,数论分块加速求和。
7. $\sum_{i=1}^n\sum_{j=1}^m{\operatorname{d}(ij)}$ $\begin{aligned} d(ij)= & \sum_{x\mid i}\sum_{y\mid j}{[\gcd(x,y)=1]} \ = & \sum_{x\mid i}\sum_{y\mid j}\sum_{d\mid\gcd(x,y)}{\mu(d)} \ = & \sum_{d=1}\mu(d)\sum_{x\mid i}\sum_{y\mid j}{[d\mid \gcd(x,y)]} \ = & \sum_{d\mid i,d\mid j}\mu(d)\operatorname{d}(\frac{i}{d})\operatorname{d}(\frac{j}{d}) \end{aligned}$
代入原式:
$\begin{aligned} \sum_{i=1}^n\sum_{j=1}^m{\operatorname{d}(ij)}= & \sum_{i=1}^n\sum_{j=1}^m\sum_{d\mid i,d\mid j}\mu(d)\operatorname{d}(\frac{i}{d})\operatorname{d}(\frac{j}{d}) \ = & \sum_{d=1}\mu(d)\sum_{d\mid i}^n\sum_{d \mid j}^m\operatorname{d}(\frac{i}{d})\operatorname{d}(\frac{j}{d}) \ = & \sum_{d=1}\mu(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\operatorname{d}(i)\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}\operatorname{d}(j) \ \end{aligned}$
$O(n)$ 预处理 $\mu,\operatorname{d}$ 的前缀和,$O(\sqrt{n})$ 数论分块加速求和。
组合数学公式 
$\binom{n}{m}=\binom{n-1}{m-1}+\binom{n-1}{m}$
 
$(x+y)^n=\sum_{k=0}^{n}\binom{n}{k}x^{n-k}y^k=\sum_{k=0}^{n}\binom{n}{k}x^{k}y^{n-k}$
 
$n$ 个无差别的球放进 $k$ 个不同的盒子,不允许空盒子,方案数为 $\binom{n-1}{k-1}$
 
$n$ 个无差别的球放进 $k$ 个不同的盒子,允许空盒子,方案数为 $\binom{n+k-1}{k-1}$
 
Catalan 数: 递推式:$H(n)=\frac{H(n-1)(4 n-2)}{n+1}$ 该递推关系的解为:$H(n)=\binom{2n}{n}-\binom{2n}{n-1}=\frac{\binom{2n}{n}}{n+1}$
$H_n=\begin{cases} \sum_{i=1}^{n}H_{i-1}H_{n-i} & n\ge 2,n\in\mathbb{N^+} \ 1 & n=0,1 \end{cases}$
以下问题属于 Catalan 数列:
有 $2n$ 个人排成一行进入剧场。入场费 $5$ 元。其中只有 $n$ 个人有一张 $5$ 元钞票,另外 $n$ 人只有 $10$ 元钞票,剧院无其它钞票,问有多少中方法使得只要有 $10$ 元的人买票,售票处就有 $5$ 元的钞票找零? 
一位大城市的律师在她住所以北 $n$ 个街区和以东 $n$ 个街区处工作。每天她走 $2n$ 个街区去上班。如果他从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路? 
在圆上选择 $2n$ 个点,将这些点成对连接起来使得所得到的 $n$ 条线段不相交的方法数? 
对角线不相交的情况下,将一个凸多边形区域分成三角形区域的方法数? 
$n$ 个结点可构造多少个不同的二叉树? 
$n$ 个不同的数依次进栈,求不同的出栈结果的种数? 
$n$ 个 $+1$ 和 $n$ 个 $-1$ 构成 $2n$ 项 $a_1,a_2,\dots,a_{2n}$,其部分和满足 $a_1+a_2+\dots+a_k\ge0(k=1,2,3,\dots,2n)$ 的方法数? 
 
 
 
低阶等幂求和 
$\sum_{i=1}^{n} i^{1} = \frac{n(n+1)}{2} = \frac{1}{2}n^2 +\frac{1}{2} n$ 
$\sum_{i=1}^{n} i^{2} = \frac{n(n+1)(2n+1)}{6} = \frac{1}{3}n^3 + \frac{1}{2}n^2 + \frac{1}{6}n$ 
$\sum_{i=1}^{n} i^{3} = \left[\frac{n(n+1)}{2}\right]^{2} = \frac{1}{4}n^4 + \frac{1}{2}n^3 + \frac{1}{4}n^2$ 
$\sum_{i=1}^{n} i^{4} = \frac{n(n+1)(2n+1)(3n^2+3n-1)}{30} = \frac{1}{5}n^5 + \frac{1}{2}n^4 + \frac{1}{3}n^3 - \frac{1}{30}n$ 
$\sum_{i=1}^{n} i^{5} = \frac{n^{2}(n+1)^{2}(2n^2+2n-1)}{12} = \frac{1}{6}n^6 + \frac{1}{2}n^5 + \frac{5}{12}n^4 - \frac{1}{12}n^2$ 
 
约瑟夫环 $n$ 个人围成一圈,从第一个开始报数,第 $m$ 个将被杀掉,最后剩下一个,其余人都将被杀掉
$f[i]$ 表示 $i$ 个人玩游戏报 $m$ 退出最后胜利者的编号
$f_n = \begin{cases} 0 & n=1\ (f _ {n-1}+m) %n & n>1 \end{cases}$
勾股数 所谓勾股数,一般是指能够构成直角三角形三条边的三个正整数
即 $a^2+b^2=c^2,a,b,c\in N$
当 $a$ 为大于 $1$ 的奇数 $2n+1$ 时,$b=2n²+2n,c=2n²+2n+1$
当 $a$ 为大于 $4$ 的偶数 $2n$ 时,$b=n²-1,c=n²+1$
计算几何 二维 点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 const  DB EPS=1e-8 ;const  DB PI=acos (-1 );int  sgn (DB x)   {return  fabs (x)<EPS?0 :(x>0 ?1 :-1 );}struct  P  {    DB x,y;     P () {}     P (DB x,DB y):x (x),y (y) {}     bool  operator  == (const  P &a) const  {return  !sgn (x-a.x)&&!sgn (y-a.y);}     bool  operator  < (const  P &a) const  {return  sgn (x-a.x)<0 ||sgn (x-a.x)==0 &&sgn (y-a.y)<0 ;}     P operator  + (const  P &a) const  {return  P (x+a.x,y+a.y);}     P operator  - (const  P &a) const  {return  P (x-a.x,y-a.y);}     P operator  * (const  DB &k) const  {return  P (x*k,y*k);}     P operator  / (const  DB &k) const  {return  P (x/k,y/k);}          DB operator  * (const  P &a) const  {return  x*a.x+y*a.y;}          DB operator  ^ (const  P &a) const  {return  x*a.y-y*a.x;}          DB rad (const  P &a,const  P &b)   {return  fabs (atan2 (fabs ((a-*this )^(b-*this )),(a-*this )*(b-*this )));}          DB len ()   {return  hypot (x,y);}          DB len2 ()   {return  x*x+y*y;}          DB dist (const  P &p)   {return  hypot (x-p.x,y-p.y);}          P rotleft ()   {return  P (-y,x);}          P rotright ()   {return  P (y,-x);}          P rotate (const  P &p,const  DB &angle)   {         P v=*this -p;         DB c=cos (angle),s=sin (angle);         return  P (p.x+v.x*c-v.y*s,p.y+v.x*s+v.y*c);     } }; 
 
线 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct  L  {    P s,t;     L () {}     L (P s,P t):s (s),t (t) {}     bool  operator  == (const  L &a) const  {return  s==a.s&&t==a.t;}          int  relation (const  P &p)   {return  sgn ((p-s)^(t-s));}          int  relation (const  L &T)  const   {         return  sgn ((t-s)^(T.t-T.s));     }     P cross_point (const  L &a)   {          DB s1=(t-s)^(a.s-s);         DB s2=(t-s)^(a.t-s);         return  (a.s*s2-a.t*s1)/(s2-s1);     }     bool  parallel (const  L &a)   {return  sgn ((t-s)^(a.t-a.s))==0 ;}      bool  P_on_seg (const  P &p)   {         return  !sgn ((p-s)^(t-s))&&sgn ((p-s)*(p-t))<=0 ;     }           }; 
 
圆 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 struct  C  {    P p;     DB r;     C () {}     C (P p,DB r):p (p),r (r) {}     DB area ()   {return  PI*r*r;}     DB circumference ()   {return  2 *PI*r;}                         int  p_relation (P a)   {         DB d=p.dist (a);         if (sgn (d-r)<0 ) return  0 ;         if (sgn (d-r)==0 ) return  1 ;         return  2 ;     }                                   int  c_relation (C c)   {         DB d=p.dist (c.p);         if (sgn (d-r-c.r)>0 ) return  5 ;         if (sgn (d-r-c.r)==0 ) return  4 ;         DB l=fabs (r-c.r);         if (sgn (d-r-c.r)<0 &&sgn (d-l)>0 ) return  3 ;         if (sgn (d-l)==0 ) return  2 ;         if (sgn (d-l)<0 ) return  1 ;     }     DB c_area (C c)   {         int  rel=c_relation (c);         if (rel>=4 ) return  0 ;         if (rel<=2 ) return  min (area (),c.area ());         DB d=p.dist (c.p);         DB hf=(r+c.r+d)/2 ;         DB ss=2 *sqrt (hf*(hf-r)*(hf-c.r)*(hf-d));         DB a1=acos ((r*r+d*d-c.r*c.r)/(2 *r*d))*r*r;         DB a2=acos ((c.r*c.r+d*d-r*r)/(2 *c.r*d))*c.r*c.r;         return  a1+a2-ss;     } }; P compute_circle_center (P a,P b)   {return (a+b)/2 ;} P compute_circle_center (P a,P b,P c)   {     b=(a+b)/2 ;     c=(a+c)/2 ;     return  L (b,b+(a-b).rotright ()).cross_point ({c,c+(a-c).rotright ()}); } 
 
凸包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void  andrew ()   {    sort (p+1 ,p+1 +n);          for (int  i=1 ;i<=2 ;i++) stk[++top]=p[i];     for (int  i=3 ;i<=n;i++) {         while (top>=2 &&sgn ((stk[top]-stk[top-1 ])^(p[i]-stk[top]))<=0 ) --top;         stk[++top]=p[i];     }     int  temp=top;     stk[++top]=p[n-1 ];     for (int  i=n-2 ;i>=1 ;i--) {         while (top>=temp&&sgn ((stk[top]-stk[top-1 ])^(p[i]-stk[top]))<=0 ) --top;         stk[++top]=p[i];     }      } 
 
最小圆覆盖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 C min_circle_cover (const  vector<P> &T)   {    vector<P> a (ALL(T))  ;     random_shuffle (ALL (a));     P c=a[0 ];DB r=0 ;     for (int  i=1 ;i<SZ (a);i++) if (!p_in_circle (a[i],{c,r})) {         c=a[i];r=0 ;         for (int  j=0 ;j<i;j++) if (!p_in_circle (a[j],{c,r})) {             c=compute_circle_center (a[i],a[j]);             r=a[j].dist (c);             for (int  k=0 ;k<j;k++) if (!p_in_circle (a[k],{c,r})) {                 c=compute_circle_center (a[i],a[j],a[k]);                 r=a[k].dist (c);             }         }     }     return  {c,r}; } 
 
三维 点 1 2 3 4 5 6 struct  P  {    DB x,y,z;     P () {}     P (DB x,DB y,DB z):x (x),y (y),z (z) {}     DB dist (const  P &p)   {return  sqrt ((x-p.x)*(x-p.x)+(y-p.y)*(y-p.y)+(z-p.z)*(z-p.z));} }; 
 
球 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct  S  {    P p;     DB r;     S () {}     S (P p,DB r):p (p),r (r) {}     DB vol ()   {return  4 *PI*r*r*r/3 ;}      DB s_s_area (S &T)   {          DB d=p.dist (T.p);         if (sgn (d-r-T.r)>=0 ) return  0 ;         if (sgn (d-fabs (r-T.r))<=0 ) return  r<T.r?vol ():T.vol ();         DB h1=r-(r*r-T.r*T.r+d*d)/(2 *d);         DB h2=T.r-(T.r*T.r-r*r+d*d)/(2 *d);         return  PI/3 *(h1*h1*(3 *r-h1)+h2*h2*(3 *T.r-h2));     } }; 
 
其它 
圆台体积公式:$V=\frac1 3\pi h(R^2+r^2+Rr)$ ($r$ 为上底半径、$R$ 为下底半径、$h$ 为高) 
 
杂项 排序 归并排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 int  a[N], temp[N];void  merge (int  l, int  mid, int  r)   {    int  i = l, j = mid + 1 , t = l;     while  (i <= mid && j <= r) {         if  (a[i] <= a[j]) {             temp[t++] = a[i++];         }         else  {             temp[t++] = a[j++];         }     }     while  (i <= mid) {         temp[t++] = a[i++];     }     while  (j <= r) {         temp[t++] = a[j++];     }     for  (int  i = l; i <= r; i++) {         a[i] = temp[i];     } } void  merge_sort (int  l, int  r)   {    if  (l < r) {         int  mid = l + r >> 1 ;         merge_sort (l, mid);         merge_sort (mid + 1 , r);         merge (l, mid, r);     } } 
 
快速排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void  quick_sort (int  l, int  r)   {    if  (l < r) {         int  i = l, j = r, key = a[l];         while  (i < j) {             while  (i < j && a[j] >= key) {                 j--;             }             if  (i < j) {                 a[i++] = a[j];             }             while  (i < j && a[i] <= key) {                 i++;             }             if  (i < j) {                 a[j--] = a[i];             }         }         a[i] = key;         quick_sort (l, i - 1 );         quick_sort (i + 1 , r);     } } 
 
离散化 1 2 3 4 5 6 7 8 for  (int  i = 1 ; i <= n; i++) {    b.PB (a[i]); } sort (ALL (b));b.resize (unique (ALL (b)) - b.begin ()); for  (int  i = 1 ; i <= n; i++) {    a[i] = lower_bound (ALL (b), a[i]) - b.begin () + 1 ;  } 
 
二分 
1 2 3 4 5 6 7 8 int  find (int  l,int  r)   {    while (l<r) {         int  mid=l+r>>1 ;         if (check (mid)) r=mid;         else  l=mid+1 ;     }     return  l; } 
 
1 2 3 4 5 6 7 8 int  find (int  l, int  r)   {     while (l<r) {         int  mid=l+r+1 >>1 ;         if  (check (mid)) l=mid;         else  r=mid-1 ;     }     return  l; } 
 
1 2 3 4 5 6 7 8 DB find (DB l,DB r)   {     for (int  i=0 ;i<100 ;i++) {         DB mid=(l+r)/2 ;       	if (check (mid)) l=mid;       	else  r=mid;     }     return  l; } 
 
三分 
三分用来寻找凸函数或凹函数的极值 
以寻找凸函数的极大值为例 
实数三分 
 
1 2 3 4 5 6 for (int  i=0 ;i<100 ;i++) {    DB mid=(r-l)/3.0 ;     if (calc (l+mid)>calc (r-mid)) r=r-mid;     else  l=l+mid; } mx=calc (l); 
 
1 2 3 4 5 6 while (l<r) {    int  lmid=l+(r-l)/3 ,rmid=r-(r-l)/3 ;     if (calc (lmid)>calc (rmid)) r=rmid-1 ;     else  l=lmid+1 ; } mx=calc (l); 
 
高精度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 struct  bign  {    int  len;     int  num[1005 ];     bign () {         len=1 ;         memset (num,0 ,sizeof  num);     }     bool  operator  < (const  bign &a) const  {         if (len!=a.len) return  len<a.len;         for (int  i=len;i>=1 ;i--) if (num[i]!=a.num[i]) return  num[i]<a.num[i];         return  false ;     }     bool  operator  > (const  bign &a) const  {return  a<*this ;}     bool  operator  <= (const  bign &a) const  {return  !(a<*this );}     bool  operator  >= (const  bign &a) const  {return  !(*this <a);}     bool  operator  != (const  bign &a) const  {return  a<*this ||*this <a;}     bool  operator  == (const  bign &a) const  {return  !(a<*this )&&!(*this <a);} }; bign get (const  string &s)   {    bign res;     res.len=SZ (s);     for (int  i=1 ;i<=res.len;i++) res.num[i]=s[res.len-i]-'0' ;     return  res; } bign get (int  n)   {    bign res;     while (n) {         res.num[res.len++]=n%10 ;         n/=10 ;     }     if (res.len>1 ) res.len--;     return  res; } void  print (bign a)   {    for (int  i=a.len;i;i--) cout<<a.num[i];     cout<<'\n' ; } bool  non_zero (bign T)   {    return  T.len>1 ||T.len==1 &&T.num[1 ]; } bign operator  + (bign a,bign b) {     bign res;     res.len=max (a.len,b.len);     int  x=0 ;     for (int  i=1 ;i<=res.len;i++) {         res.num[i]=x+a.num[i]+b.num[i];         x=res.num[i]/10 ;         res.num[i]%=10 ;     }     if (x) res.num[++res.len]=x;     return  res; } bign operator  - (bign a,bign b) {      bign res;     res.len=max (a.len,b.len);     for (int  i=1 ;i<=res.len;i++) {         if (a.num[i]<b.num[i]) --a.num[i+1 ],a.num[i]+=10 ;         res.num[i]=a.num[i]-b.num[i];     }     while (res.len>1 &&res.num[res.len]==0 ) res.len--;     return  res; } bign operator  * (bign a,int  b) {     bign res;     int  x=0 ;     for (int  i=1 ;i<=a.len;i++) {         x+=a.num[i]*b;         res.num[res.len++]=x%10 ;         x/=10 ;     }     while (x) {         res.num[res.len++]=x%10 ;         x/=10 ;     }     while (res.len>1 &&res.num[res.len]==0 ) res.len--;     return  res; } bign operator  * (bign a,bign b) {     bign res;     res.len=a.len+b.len;     for (int  i=1 ;i<=res.len;i++) res.num[i]=0 ;     for (int  i=1 ;i<=a.len;i++) {         int  x=0 ;         for (int  j=1 ;j<=b.len;j++) {             res.num[i+j-1 ]+=a.num[i]*b.num[j]+x;             x=res.num[i+j-1 ]/10 ;             res.num[i+j-1 ]%=10 ;         }         res.num[i+b.len]+=x;     }     while (res.len>1 &&res.num[res.len]==0 ) res.len--;     return  res; } bign operator  / (bign a,int  b) {     bign res;     res.len=a.len;     for (int  i=a.len,t=0 ;i>=1 ;i--) {         t=t*10 +a.num[i];         if (t>=b) res.num[i]=t/b,t%=b;     }     while (res.len>1 &&res.num[res.len]==0 ) res.len--;     return  res; } bign operator  / (bign a,bign b) {     bign res,x;     res.len=a.len;     for (int  i=1 ;i<=res.len;i++) res.num[i]=0 ;     for (int  i=a.len;i>=1 ;i--) {         x=x*10 ;          x.num[1 ]=a.num[i];         while (x>b||x==b) {             x=x-b;              res.num[i]++;         }     }     while (res.len>1 &&res.num[res.len]==0 ) res.len--;     return  res; } bign operator  % (bign a,bign b) {     bign res;     for (int  i=a.len;i>=1 ;i--) {         res=res*10 ;          res.num[1 ]=a.num[i];         while (res>b||res==b) res=res-b;      }     while (res.len>1 &&res.num[res.len]==0 ) res.len--;     return  res; } bign gcd (bign a,bign b)   {    return  non_zero (b)?gcd (b,a%b):a; } bign qpow (bign a,bign b,bign m)   {    bign ret;     ret.num[1 ]=1 ;     while (non_zero (b)) {         if (b.num[1 ]&1 ) ret=ret*a%m;         a=a*a%m;         b=b/2 ;     }     return  ret; } 
 
浮点数的精度问题 int sgn(DB x) {return fabs(x)<EPS?0:(x>0?1:-1);}
传统意义 
修正写法1 
修正写法2 
 
 
a == b 
sgn(a - b) == 0 
fabs(a - b) < eps 
 
a != b 
sgn(a - b) != 0 
fabs(a - b) > eps 
 
a < b 
sgn(a - b) < 0 
a - b < -eps 
 
a <= b 
sgn(a - b) <= 0 
a - b < eps 
 
a > b 
sgn(a - b) > 0 
a - b > eps 
 
a >= b 
sgn(a - b) >= 0 
a - b > -eps 
 
Lowbit 前缀和 1 2 3 4 5 6 int  solve (int  n)   {    int  res=0 ;     n++;     for (int  i=1 ;n>1 ;n-=(n>>1 ),i<<=1 ) res+=i*(n>>1 );     return  res; } 
 
随机数 1 2 3 4 mt19937 mt (time(0 ))  ;uniform_int_distribution<int > rd1 (0 ,10000 )  ; uniform_real_distribution<double > rd2 (0 ,1 )  ; cout<<rd1 (mt)<<' ' <<rd2 (mt)<<'\n' ; 
 
_builtin  系列函数(位运算) 1 2 3 4 5 6 7 8 9 10 11 int  __builtin_ffs(unsigned  int  x)int  __builtin_clz(unsigned  int  x)int  __builtin_ctz(unsigned  int  x)int  __builtin_popcount(unsigned  int  x)int  __builtin_parity(unsigned  int  x)