Template For ACM

一. 字符串

标准库

sscanf

sscanf(const char *__source, const char *__format, ...) :从字符串 __source 里读取变量,比如 sscanf(str,"%d",&a)

sprintf

sprintf(char *__stream, const char *__format, ...) :将 __format 字符串里的内容输出到 __stream 中,比如 sprintf(str,"%d",i)

strcmp

int strcmp(const char *str1, const char *str2):按照字典序比较 str1 str2str1 字典序小返回负值,一样返回 0,大返回正值 请注意,不要简单的认为只有 0, 1, -1 三种,在不同平台下的返回值都遵循正负,但并非都是 0, 1, -1

strcpy

char *strcpy(char *str, const char *src) : 把 src 中的字符复制到 str 中, str src 均为字符数组头指针,返回值为 str 包含空终止符号 '\0'

strncpy

char *strncpy(char *str, const char *src, int cnt) :复制至多 cnt 个字符到 str 中,若 src 终止而数量未达 cnt 则写入空字符到 str 直至写入总共 cnt 个字符。

strcat

char *strcat(char *str1, const char *str2) : 将 str2 接到 str1 的结尾,用 *str2 替换 str1 末尾的 '\0' 返回 str1

strstr

char *strstr(char *str1, const char *str2) :若 str2str1 的子串,则返回 str2str1 的首次出现的地址;如果 str2 不是 str1 的子串,则返回 NULL

strchr

char *strchr(const char *str, int c) :找到在字符串 str 中第一次出现字符 c 的位置,并返回这个位置的地址。如果未找到该字符则返回 NULL

strrchr

char *strrchr(const char *str, char c) :找到在字符串 str 中最后一次出现字符 c 的位置,并返回这个位置的地址。如果未找到该字符则返回 NULL

KMP

//以 1 为起点, 求nxt数组
nxt[1] = 0;
for(int i=2,j=0; i<=n; i++){
while(j > 0 && a[i] != a[j+1]) j = nxt[j];
if(a[i] == a[j+1]) j++;
nxt[i] = j;
} //求 f 匹配数组
for(int i=1,j=0;i<=m;i++){
while(j > 0 && (j == n || b[i] != a[j+1])) j=nxt[j];
if(b[i] == a[j+1]) j++;
f[i] = j;
//if(f[i] == n) 此时为A在B中的某一次出现
}

EXKMP

z[i] 是 s 和从 i 开始的 s 的后缀的最大公共前缀长度。

//初始下标为0
for(int i=1,l=0,r=0;i<n;i++){
if(i <= r) z[i] = min(r-i+1, z[i-l]);
while(i + z[i] < n && s[z[i]] == s[i + z[i]])++z[i];
if(i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
}

Manacher

const int N = 11000000 + 5;
char s[N], ma[N*2];
int mp[N*2];//mp[i] 为在s中以对应位置为中心的极大子回文串的总长度+1
int Manacher(char *s, int n){
int len = 0;
ma[len++] = '$'; ma[len++] = '#';
for(int i=0;i<n;i++){
ma[len++] = s[i];
ma[len++] = '#';
}
ma[len] = 0;
int mx = 0, id = 0;
int res = 0;
for(int i=0;i<len;i++){
mp[i] = mx > i ? min(mp[2*id-i], mx - i) : 1;
while(ma[i+mp[i]] == ma[i-mp[i]]) mp[i]++;
if(i + mp[i] > mx) {
mx = mp[i] + i;
id = i;
}
res = max(res, mp[i] - 1);
}
return res;
}
int main(){
scanf("%s", s);
printf("%d", Manacher(s, strlen(s)));
return 0;
}

AC自动机

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n;
char s[N];
namespace AC{
int tr[N][26],tot;
int e[N],fail[N];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = 0;
}
tot = 0;
}
void insert(char *s){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
if(!tr[u][s[i] - 'a']){
tr[u][s[i] - 'a'] = ++tot;
}
u = tr[u][s[i] - 'a'];
}
e[u] ++;
}
void build(){
for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
for(int i=0;i<26;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
int query(char *t){
int u = 0,res = 0;
for(int i=1;t[i];i++){
u = tr[u][t[i] - 'a'];
for(int j=u;j && e[j] != -1;j = fail[j])
res += e[j],e[j] = -1;
}
return res;
}
}
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%d",&n);
AC::init();
for(int i=1;i<=n;i++)scanf("%s",s+1),AC::insert(s);
scanf("%s",s+1);
AC::build();
printf("%d\n",AC::query(s));
}
return 0;
}

后缀数组

1. 倍增

int wa[N],wb[N],wv[N],c[N], rk[N];
int sa[N], height[N];
int cmp(int *r,int a,int b,int l){
return r[a] == r[b] && r[a + l] == r[b + l];
}
void build_sa(int *r,int * sa,int n,int m){
int i,j,p,*x = wa,*y = wb, *t;
for(i=0;i<m;i++)c[i] = 0;
for(i=0;i<n;i++)c[x[i] = r[i]] ++;
for(i=1;i<m;i++)c[i] += c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]] = i;
for(j=1,p=1;p<n;j *= 2,m=p){
for(p=0,i=n-j;i<n;i++) y[p++] = i;
for(i = 0; i < n; i++)if(sa[i] >= j)y[p++] = sa[i] - j;
for(i=0;i<n;i++)wv[i] = x[y[i]];
for(i=0;i<m;i++)c[i] = 0;
for(i=0;i<n;i++)c[wv[i]] ++;
for(i=1;i<m;i++)c[i] += c[i-1];
for(i=n-1;i>=0;i--)sa[--c[wv[i]]] = y[i];
for(t = x,x = y,y = t,p = 1,x[sa[0]] = 0,i = 1;i<n;i++)
x[sa[i]] = cmp(y,sa[i-1],sa[i],j) ? p-1 : p ++;
}
}

2. DC3

#define F(x) ((x) / 3 + ((x) % 3 == 1 ? 0 : tb))
#define G(x) ((x) < tb ? (x) * 3 + 1 : ((x) - tb) * 3 + 2)
int n;
char s[N], t[N];
int a[N], wa[N], wb[N], c[N], wv[N], sa[N], be[N];
int rk[N], height[N]; int c0(int *r, int a, int b) {
return r[a] == r[b] && r[a + 1] == r[b + 1] && r[a + 2] == r[b + 2];
}
int c12(int k, int *r, int a, int b) {
if (k == 2)
return r[a] < r[b] || r[a] == r[b] && c12(1, r, a + 1, b + 1);
return r[a] < r[b] || r[a] == r[b] && wv[a + 1] < wv[b + 1];
}
void sort(int *r, int *a, int *b, int n, int m) {
for (int i = 0; i < n; i++) wv[i] = r[a[i]];
for (int i = 0; i < m; i++) c[i] = 0;
for (int i = 0; i < n; i++) c[wv[i]]++;
for (int i = 1; i < m; i++) c[i] += c[i - 1];
for (int i = n - 1; i >= 0; i--) b[--c[wv[i]]] = a[i];
}
void dc3(int *r, int *sa, int n, int m) {
int i, j, *rn = r + n, *san = sa + n, ta = 0, tb = (n + 1) / 3, tbc = 0, p;
r[n] = r[n + 1] = 0;
for (i = 0; i < n; i++) if (i % 3 != 0) wa[tbc++] = i;
sort(r + 2, wa, wb, tbc, m);
sort(r + 1, wb, wa, tbc, m);
sort(r, wa, wb, tbc, m);
for (p = 1, rn[F(wb[0])] = 0, i = 1; i < tbc; i++)
rn[F(wb[i])] = c0(r, wb[i - 1], wb[i]) ? p - 1 : p++;
if (p < tbc) dc3(rn, san, tbc, p);
else for (i = 0; i < tbc; i++) san[rn[i]] = i;
for (i = 0; i < tbc; i++) if (san[i] < tb) wb[ta++] = san[i] * 3;
if (n % 3 == 1) wb[ta++] = n - 1;
sort(r, wb, wa, ta, m);
for (i = 0; i < tbc; i++) wv[wb[i] = G(san[i])] = i;
for (i = 0, j = 0, p = 0; i < ta && j < tbc; p++)
sa[p] = c12(wb[j] % 3, r, wa[i], wb[j]) ? wa[i++] : wb[j++];
for (; i < ta; p++) sa[p] = wa[i++];
for (; j < tbc; p++) sa[p] = wb[j++];
}

3. 求\(height\) 以及 \(lcp\)

void calheight(int *r, int * sa, int n){
int i, j, k = 0;
for (int i = 1; i <= n;i++)
rk[sa[i]] = i;
for (int i = 0; i < n;height[rk[i++]] = k){
for (k ? k-- : 0, j = sa[rk[i] - 1]; r[i + k] == r[j + k];k++);
}
}
struct RMQ{
int mi[N][20], kk[N];
void init(int n,int *a){
kk[1] = 0;
for (int i = 2; i <= n;i++)
kk[i] = kk[i / 2] + 1;
for (int i = 1; i <= n;i++)
mi[i][0] = a[i];
for (int j = 1; (1 << j) <= n;j++){
for (int i = 1; i + (1 << j) - 1 <= n;i++){
mi[i][j] = min(mi[i + (1 << (j - 1))][j - 1], mi[i][j - 1]);
}
}
}
int query(int l,int r){
int k = kk[r - l + 1];
return min(mi[l][k], mi[r - (1 << k) + 1][k]);
}
} rmq;

后缀自动机

最多 \(2n\) 个点,\(3n\) 条边

const int N = 200000 + 5; //N为字符串长度两倍
const int P = 26;//P过大时改用unordered_map
char s[N], a[N];
struct node{
int link, len, trans[P];
void clear(){
memset(trans,0, sizeof trans);
link = len = 0;
}
};
struct SAM{
node S[N];
int p, np, size;
int b[N], c[N];
SAM():p(1),np(1),size(1){}
void clear(){
for(int i=0;i<=size;i++)S[i].clear();
np = size = p = 1;
}
void insert(char ch){
int x = ch - 'a';
np = ++size;
S[np].len = S[p].len + 1;
while(p != 0 && !S[p].trans[x]) S[p].trans[x] = np, p = S[p].link;
if(p == 0)S[np].link = 1;
else{
int q, nq;
q = S[p].trans[x];
if(S[q].len == S[p].len + 1) S[np].link = q;
else{
nq = ++size;
S[nq] = S[q];
S[nq].len = S[p].len + 1;
S[np].link = S[q].link = nq;
while(p != 0 && S[p].trans[x] == q) S[p].trans[x] = nq, p = S[p].link;
}
}
p = np;
}
// 基数排序,从size倒序遍历可以自底向上更新,多组数据时,c 数组要更新
void sort(){
for(int i=1;i<=size;i++)++c[S[i].len];
for(int i=1;i<=size;i++)c[i] += c[i-1];
for(int i=1;i<=size;i++)b[c[S[i].len]--] = i;
}
}sam;

回文自动机

namespace PAT{
const int SZ = 6e5+10;
int ch[SZ][26],fail[SZ],cnt[SZ],len[SZ],tot,last;
int be[SZ],ok[SZ];
void init(int n){
for(int i=0;i<=n+10;i++){
fail[i] = cnt[i] = len[i] = 0;
for(int j=0;j<26;j++)ch[i][j] = 0;
be[i] = ok[i] = 0;
}
s[0] = -1;fail[0] = 1;last = 0;
len[0] = 0;len[1] = -1;tot = 1;
}
inline int newnode(int x){
len[++tot] = x;return tot;
}
inline int getfail(char *s, int x,int n){
while(s[n-len[x]-1] != s[n])x = fail[x];
return x;
}
void create(char *s){
s[0] = -1;
for(int i=1;s[i];++i){
int t = s[i]- 'a';
int p = getfail(s, last,i);
if(!ch[p][t]){
int q = newnode(len[p]+2);
fail[q] = ch[getfail(s, fail[p],i)][t];
ch[p][t] = q;
}
++cnt[last = ch[p][t]];
}
}
void solve(){
for(int i=tot;i>=2;i--){
if(be[i] == 0)be[i] = i;
while(be[i] >= 2 && len[be[i]] > (len[i] + 1)/2)be[i] = fail[be[i]];
if(len[be[i]] == (len[i]+1)/2)ok[i] = 1;
be[fail[i]] = be[i];
}
for(int i=tot;i>=2;i--){
cnt[fail[i]] += cnt[i];
if(ok[i]) res[len[i]] += cnt[i];
}
}
}

二. 数论

筛质数

\(O(\sum_{质数p\le n}{n\over p}) = O(nlogn)\)

void primes(int n){
memset(v,0,sizeof v);
for(int i=2;i<=n;i++){
if(v[i])continue;
for(int j=i;j<=n/i;j++)v[i*j] = 1;
}
}

\(O(n)\)

  1. 第一种,v[i] 为 i 的最小质因子
int v[MAX_N],prime[MAX_N];
void primes(int n){
memset(v,0,sizeof v);
m = 0;
for(int i=2;i<=n;i++){
if(v[i] == 0){v[i] = i; prime[++m] = i;}
for(int j=1;j<=m;j++){
if(prime[j] > v[i] || prime[j] > n/i)break;
v[i*prime[j]] = prime[j];
}
}
}
  1. 第二种: v[i] 表示 i 是否为质数
int v[MAX_N],prime[MAX_N];
void primes(int n){
memset(v,0,sizeof v);
m = 0;
for(int i=2;i<=n;i++){
if(v[i] == 0) prime[++m] = i;
for(int j=1;j<=m;j++){
if(prime[j] > n / i)break;
v[i*prime[j]] = 1;
if(i % prime[j] == 0)break;
}
}
}

质因子分解

void divide(int n){
m = 0;
for(int i=2;i<=sqrt(n);i++){
if(n % i == 0){
p[++m] = i,c[m] = 0;
while(n % i == 0)n /= i,c[m]++;
}
}
if(n > 1)
p[++m] = n,c[m] = 1;
}

正因数集合

一个整数N的约数上界约为\(2\sqrt(N)\)

\(1\sim N\) 每个数的约数个数的总和约为\(NlogN\)

vector<int> fac[500010];
for(int i=1;i<=n;i++)
for(int j=1;j<=n/i;j++)
fac[i*j].push_back(i);

Miller Rabin Prollard Rho

/*
Miller_Rabin 算法进行素数测试
速度快可以判断一个 < 2^63 的数是不是素数
*/
const int S = 8;
typedef long long ll;
//计算 res = (a*b)%c
ll mult_mod(ll a, ll b, ll c){
a %= c;
b %= c;
ll res = 0;
ll tmp = a;
for (; b;b>>=1){
if(b & 1) {
res += tmp;
if(res > c)
res -= c;
}
tmp <<= 1;
if(tmp > c)
tmp -= c;
}
return res;
}
ll pow_mod(ll a,ll b, ll mod){
ll res = 1;
a %= mod;
for (; b;b>>=1){
if(b & 1)
res = mult_mod(res, a, mod);
a = mult_mod(a, a, mod);
}
return res;
}
/*
通过 a^(n-1)=1(mod n)来判断 n 是不是素数
n - 1 = x * 2^t 中间使用二次判断
是合数返回true,不一定是合数返回false
*/
bool check(ll a,ll n,ll x,ll t){
ll res = pow_mod(a, x, n);
ll last = res;
for (int i = 1; i <= t;i++){
res = mult_mod(res, res, n);
if(res == 1 && last != 1 && last != n-1)
return true;
last = res;
}
if(res != 1)
return true;
return false;
}
/*
Miller_Rabin 算法
是素数返回true
不是素数返回false
*/
bool Miller_Rabin(ll n){
if(n < 2)
return false;
if(n == 2)
return true;
if((n&1) == 0)
return false;
ll x = n - 1;
ll t = 0;
while((x & 1) == 0) {
x >>= 1, t++;
}
for (int i = 0; i < S;i++){
ll a = rand() % (n - 1) + 1;
if(check(a,n,x,t))
return false;
}
return true;
}
/*
Pollard_rho 算法进行质因数分解
*/
ll fac[100];
int tol;
ll gcd(ll a,ll b){
ll t;
while(b){
t = a;
a = b;
b = t % b;
}
if(a >= 0)
return a;
else
return -a;
}
//找出一个因子
ll pollard_rho(ll x,ll c){
ll i = 1, k = 2;
ll x0 = rand() % (x - 1) + 1;
ll y = x0;
while(1){
i++;
x0 = (mult_mod(x0, x0, x) + c) % x;
ll d = gcd(y - x0, x);
if(d != 1 && d != x)
return d;
if(y == x0)
return x;
if(i == k) {
y = x0;
k += k;
}
}
}
//对 n 进行质因数分解,存入factor,k设置为107左右
void findfac(ll n,int k){
if(n == 1)
return;
if(Miller_Rabin(n)){
fac[tol++] = n;
return;
}
ll p = n;
int c = k;
while(p >= n)
p = pollard_rho(p, c--);
findfac(p, k);
findfac(n / p, k);
}
ll a[100010];
ll cal(ll n, ll x){
ll ans = 0;
while(n / x){
ans += n / x;
n /= x;
}
return ans;
}
unordered_map<ll, int> mp;
int main(){
int T;
ll n,x,y;
scanf("%d", &T);
while(T--){
tol = 0;
mp.clear();
scanf("%lld%lld%lld", &n, &x, &y);
for (int i = 1; i <= n;i++){
scanf("%lld", &a[i]);
}
ll res = 1ll << 60;
findfac(x, 107);
for (int i = 0; i < tol;i++)
mp[fac[i]]++;
for (auto it = mp.begin(); it != mp.end(); it++) {
ll num = 0;
for (int i = 1; i <= n; i++) {
num += cal(a[i], it->first);
}
res = min(res, (cal(y, it->first) - num) / it->second);
}
printf("%lld\n", res);
}
}

高斯消元

const int N = 100 + 5;
int n;
double d[N][N], res[N]; int gauss(int n, int m){
int row = 1;
for(int col = 1; col <= n; col++){
for(int i=row;i<=n;i++) {
if(fabs(d[i][col]) > 1e-12) {
for(int j=col;j<=m;j++) swap(d[i][j], d[row][j]);
}
}
if(fabs(d[row][col]) < 1e-12) break; for(int j=m;j>=col;j--) d[row][j] /= d[row][col]; for(int i=1;i<=n;i++) if(i != row){ // 只限定 i!=row即可,不用画蛇添足
for(int j=m;j>=col;j--){
d[i][j] -= d[i][col] * d[row][j];
}
}
row++;
}
for(int i=1;i<=n;i++) {
if(fabs(d[i][i]) < 1e-12) return -1;
res[i] = d[i][m] /= d[i][i];
}
return 1;
} int main(){
scanf("%d", &n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n+1;j++){
scanf("%lf", &d[i][j]);
}
}
if(gauss(n, n+1) == -1) puts("No Solution");
else {
for(int i=1;i<=n;i++) printf("%.2f\n", res[i]);
}
return 0;
}

矩阵快速幂

struct matrix{
int r, c;
ll s[N][N];
matrix(int r=0,int c=0):r(r),c(c){
memset(s, 0, sizeof s);
}
};
matrix operator*(const matrix&a, const matrix&b){
matrix c = matrix(a.r, b.c);
for (int i = 0; i < c.r;i++){
for (int j = 0; j < c.c;j++)
for (int k = 0; k < a.c;k++)
c.s[i][j] += a.s[i][k] * b.s[k][j];
}
return c;
}
matrix power(matrix a,int b){
matrix res = a;
b--;
for (; b;b>>=1){
if(b & 1)
res = res * a;
a = a * a;
}
return res;
}

欧拉函数

\(N = p_1^{c_1}p_2^{c_2}\cdots p_m^{c_m}\)

\(\phi(N) = N * {p_1-1 \over p_1} * {p_2 - 1 \over p_2} * \cdots {p_m-1\over p_m} = N * \prod_{质数p|N}(1-{1\over p})\)

性质:

  1. \(\forall n>1,1\sim n\) 中与n互质的数的和为\(n*\psi (n)/2\)

  2. 若a,b互质,则\(\psi (ab) = \psi(a)\psi(b)\)

  3. 若\(f\) 是积性函数,且在算术基本定理中 \(n = \prod _{i=1}^m p_i^{c_i}\) ,则\(f(n) = \prod _{i=1}^mf(p_i^{c_i})\)

  4. 若 \(p|n\) 且 \(p^2|n\) ,则 \(\psi(n) = \psi (n/p) * p\)

  5. 若 \(p|n\) 但 \(n \% p^2 != 0\) , 则 \(\psi(n) = \psi(n/p) * (p-1)\)

  6. \(\Sigma_{d|n}\psi(d) = n\)

积性函数:如果当a,b互质时,有 \(f(ab) = f(a)*f(b)\) 那么称函数 \(f\) 为积性函数

欧拉定理:若正整数 a,n互质,则 \(a^{\psi(n)} \equiv 1\pmod n\)

当n为质数时,\(\psi (n)\) 为 \(n-1\),当a不为n的倍数时,a与n互质,故有欧拉定理成立:\(a^{n-1} \equiv 1 \pmod n\) ,左右两边同乘a有\(a^n\equiv a \pmod n\) 。当a为n的倍数时,该式依然成立

费马小定理:若 p 是质数,则对于任意整数a,有 \(a^p \equiv a \pmod p\)

int phi(int n){
int ans = n;
for(int i=2;i*i<=n;i++){
if(n % i == 0){
ans = ans / i * (i - 1);
while(n % i == 0) n /= i;
}
}
if(n > 1)ans = ans / n * (n - 1);
return ans;
}
void enlur(int n){
for(int i=2;i<=n;i++)phi[i] = i;
for(int i=2;i<=n;i++)
if(phi[i] == i)
for(int j = i;j <= n;j += i)
phi[j] = phi[j] / i * (i - 1);
}
const int N = 10000010;
int primes[N],v[N],m,n;
ll phi[N];
void prime(){
phi[1] = 1;
for(int i=2;i<=n;i++){
if(!v[i]){
primes[++m] = i;
phi[i] = i-1;
}
for(int j=1;j<=m;j++){
if(primes[j] > n / i)break;
v[primes[j] * i] = 1;
if(i % primes[j] == 0){
phi[i*primes[j]] = phi[i] * primes[j];
break;
}else{
phi[i*primes[j]] = phi[i] * (primes[j]-1);
}
}
}
}

逆元

  1. 扩展欧几里得
typedef  long long ll;
void extgcd(ll a,ll b,ll& d,ll& x,ll& y){
if(!b){ d=a; x=1; y=0;}
else{ extgcd(b,a%b,d,y,x); y-=x*(a/b); }
}
ll inverse(ll a,ll n){
ll d,x,y;
extgcd(a,n,d,x,y);
return d==1?(x+n)%n:-1;
}
  1. 费马小定理

  2. 当模 p 不是素数时候需要用到欧拉定理

  3. 线性递推逆元

\(inv[i] = (P - \lfloor P/i \rfloor) * inv[P\%i] \% P\)

typedef  long long ll;
const int N = 1e5 + 5;
ll fac[N], inv[N], fac_inv[N];
void getFac(int n){
fac[0] = fac_inv[0] = 1;
for(int i=1;i<=n;i++) fac[i] = fac[i-1] * i % mod;
inv[1] = 1;
for(int i=2;i<=n;i++) inv[i] = 1ll * (mod-mod/i)*inv[mod%i] % mod;
for(int i=1;i<=n;i++) fac_inv[i] = fac_inv[i-1] * inv[i] % mod;
}

组合数学相关

一. 组合数学性质

  1. \(C_n^m = C_n^{n-m}\)

  2. \(C_n^m = C_{n-1} ^ m + C_{n-1} ^{m-1}\) (可用杨辉三角推到)

  3. \(C_n^0 + C_n^1 + C_n^2 + C_n^3 + \cdots + C_n^n = 2^n\)

  4. \(\sum_{i=0}^m C_n^i C_m^{m-i} = C_{m+n}^m(n \geq m)\) 分开取和一起取是一样的(也可以理解成先取和后取)

二. 多重集的排列数

设集合 \(S = \{n_1\cdot a_1,n_2\cdot a_2,\cdots,n_k\cdot a_k\}\) ,是由\(n_1\) 个\(a_1\),\(n_2\)个\(a_2\)\(\cdots\) \(n_k\) 个 \(a_k\) 组成的多重集。S的全排列个数为:

\[n!\over n_1!n_2! \cdots n_k!
\]

三. 多重集组合数

n种不一样的球,每种球的个数是无限的,从中选k个出来组成一个多重集(不考虑元素的顺序),产生的不同多重集的数量为:

\(C_{n+k-1} ^ {k-1}\)

四. 不相邻的排列

\(1\sim n\) 这 n 个自然数中选k 个,这k 个数中任何两个数不相邻数的组合有 \(C_{n-k+1}^k\)种

五. 错位排列

胸口贴着编号为\(1,2,\cdots,n\) 的 n 个球员分别住在编号为\(1,2,\cdots ,n\) 的n个房间里面。现规定每个人住一个房间,自己的编号不能和房间的编号一样

\(d[n] = (n-1)(d[n-1] + d[n-2]) (n\ge 3)\)

\(d[n] = n \times d[n-1]+ (-1)^n\)

六. 圆排列

n 个人全部来围成一圈为 \(Q_n^n\) ,其中已经排好的一圈,从不同位置断开,又变成不同的队列。所以:\(Q_n^n \times n = A_n^n \rightarrow Q_n = (n-1)!\)

由此可知部分圆排列为 \(Q_n^r = {A_n^r \over r} = {n! \over r \times (n-r)!}\)

七. 卢卡斯定理

\(C_n^k = C_{n\over p}^{k\over p} * C_{n\%p}^{k\%p} \pmod p\)

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
typedef long long ll;
int T;
ll n,m,p;
ll j[N];
ll pow(ll a,ll b ,ll p){
ll res = 1;
for(;b;b>>=1){
if(b & 1) res = (res * a)%p;
a = a * a % p;
}
return res;
}
ll C(ll n,ll m){
if(m > n)return 0;
return ((j[n] * pow(j[m],p-2,p))%p * pow(j[n-m],p-2,p)%p);
}
ll Lucas(ll n,ll m){
if(!m)return 1;
return C(n%p,m%p)*Lucas(n/p,m/p)%p;
}
int main(){
j[0] = 1;
cin>>T;
while(T--){
cin >> n >> m >> p;
for(int i=1;i<=p;i++)
j[i] = j[i-1] * i % p;
cout<<Lucas(n+m,n)<<endl;
}
return 0;
}

八. 斯特林数

  1. 把n个不同的球分成r个非空循环排列的方法数

    递推形式 : \(s(n,r) = (n-1)s(n-1,r) + s(n-1,r-1),n>r \ge 1\)

  2. 把n个不同的球放到r个盒子里,假设没有空盒,则放球方案数记作S(n,r),称为第二类Stirling数

    \(S(n,r) = rS(n-1,r) + S(n-1,r-1),n>r\ge 1\)

九. 莫比乌斯函数

\(\mu\) 为莫比乌斯函数

\[\mu(n)= \begin{cases} 1&n=1\\ 0&n\text{ 含有平方因子}\\ (-1)^k&k\text{ 为 }n\text{ 的本质不同质因子个数}\\ \end{cases}
\]

通俗的讲,当n包含相等的质因子时,\(\mu(N)=0\) 。当N的所有质因子各不相等时,若N有偶数个质因子,\(\mu(N) = 1\),若N有奇数个质因子,\(\mu(N)=-1\) 。

若只求一项Mobius函数,则分解质因数即可计算,若求 \(1\sim N\)的每一项Mobius函数,可以利用线性筛法来求mobius函数

void getMu(){
mu[1] = 1;
for(int i=2;i<=n;++i){
if(!v[i])p[++tot] = i,mu[i] = -1;
for(int j=1;j<=tot && i * p[j] <= n;++j){
flg[i * p[j]] = 1;
if(i % p[j] == 0){
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = - mu[i];
}
}
}
void getMu(int n) {
mu[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!flg[i]) p[++tot] = i, mu[i] = -1;
for (int j = 1; j <= tot && i * p[j] <= n; ++j) {
flg[i * p[j]] = 1;
if (i % p[j] == 0) {
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = -mu[i];
}
}
}

十. 把n个物品分成m堆

  1. n个相同物品分成m个相同的堆,可以空

\(R(n,m) = \sum_{k=1}^mS(n,k)\)

  1. 把n 个相同物品分成m个相同的堆,不可空

\(S(n,m) = S(n-1,m-1) + S(n-m,m)\)

  1. 把n个相同物品分成m个不同的堆,可空

\(T(n,m) = C_{n+m-1}^{m-1}\)

  1. 把n个相同物品分成m个不同的堆,不可空

\(U(n,m) = C_{n-1}^{m-1}\)

  1. 把n 个不同物品分成m 个相同的堆,可空

\(P(n,m) = \sum_{k=1}^mQ(n,k)\)

性质:\(\sum_{n=0}^\infty p(n)x^n = \prod_{k=1}^\infty({1\over 1-x^k})\)

应用:

  • 集合的划分数(\(B_n\) 是基数为n 的集合的划分方法的数目,贝尔数)。
  1. 递推:\(B_{n+1} = \sum_{k=0}^n(_k^n)B_k\)

  2. \(Dobinski\) 公式: \(B_n = {1\over e}\sum_{k=0}^\infty{k^n\over k!}\)

  3. \(Touchard\) 同余:\(B_{p+n} \equiv B_n+B_{n+1} \pmod p\)

  4. 第二类\(Stirling\) 数的和\(B_n = \sum_{k=1}^nS(n,k)\)

  5. 指数母函数:\(\sum_{n=0}^\infty {B_n\over n!}x^n = e^{e^x-1}\)

  6. 贝尔三角形&Aitken阵列&Peirce三角形

  • 第一行第一项为1 (\(a_{1,1} = 1\))

  • 对于\(n>1\),第\(n\) 行第一项等同于第 \(n-1\) 行最后一项(\(a_{n,1} = a_{n-1,n-1}\)

  • 对于\(m,n>1\) ,\(a_{n,m} = a_{n,m-1} + a_{n-1,m-1}\)

\[\begin{matrix}
1\\
1&2\\
2&3&5\\
5&7&10&15\\
15&20&27&37&52\\
52&67&87&114&151&203\\
203&255&322&409&523&674&877\\
877&1080&1335&1657&2066&2589&3263&4140\\
\end{matrix}
\]

每行首项是贝尔数。每行之和是第二类\(Stirling\)数

  1. 把n 个不同的物品分成m 个相同的堆,不空

\(Q(n,m) = mQ(n-1,m) + Q(n-1,m-1)\)

三. 数据结构

并查集

带删除并查集

#include <bits/stdc++.h>
using namespace std;
const int N = 4000010;
int fa[N],to[N],sz[N],cnt,a[N],b[N],tot;
int n,m,k,x,y;
int ok[N];
int find(int x){
return x == fa[x]?x:fa[x] = find(fa[x]);
}
void merge(int x,int y){
int a = find(to[x]);
int b = find(to[y]);
if(a==b) return;
fa[b] = a;
sz[a] += sz[b]; sz[b] = 0;
}
void update(int x,int y){
sz[find(to[x])]--;
to[x] = ++cnt;
sz[cnt] = 1;
fa[cnt] = cnt;
merge(x,y);
}
int main(){
scanf("%d%d",&n,&m);cnt = n;
for(int i=1;i<=n;i++)fa[i] = i,to[i] = i,sz[i] = 1;
for(int i=1;i<=m;i++){
scanf("%d%d",&k,&x);if(k!=3)scanf("%d",&y);
if(k == 5){
a[++tot] = x;
b[tot] = y;continue;
}
if(k == 1)merge(x,y);
if(k == 2)update(x,y);
if(k == 4){
if(find(to[x]) == find(to[y]))printf("Yes\n");
else printf("No\n");
} if(k == 3)printf("%d\n",sz[find(to[x])]-1);
} for(int i=1;i<=tot;i++){
if(find(to[a[i]]) == find(to[b[i]])) ok[find(to[a[i]])] = 1;
}
int res = -1;
for(int i=1;i<=n;i++)
if(!ok[find(to[i])])res = max(res,sz[find(to[i])]);
cout<<res<<endl;
return 0;
}

可持久化并查集

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 200000 + 5; int n, m;
struct TreeNode{
int l, r;
};
int root[N];
class Union_Find{
public:
int fa[N*30], dep[N*30], tot;
TreeNode t[N*30];
void build(int &p, int l, int r){
p = ++tot;
if(l == r){ fa[p] = l; return; }
int mid = l + r >> 1;
build(t[p].l, l, mid);
build(t[p].r, mid+1, r);
}
void change(int last, int &p, int l, int r, int pos, int val){
p = ++tot;
t[p].l = t[last].l, t[p].r = t[last].r;
if(l == r){
fa[p] = val;
dep[p] = dep[last];
return;
}
int mid = (l + r) >> 1;
if(pos <= mid) change(t[last].l, t[p].l, l, mid, pos, val);
else change(t[last].r, t[p].r, mid + 1, r, pos, val);
}
int getIndex(int p, int l, int r, int pos){
if(l == r) return p;
int mid = l + r >> 1;
if(pos <= mid) return getIndex(t[p].l, l, mid, pos);
else return getIndex(t[p].r, mid+1, r, pos);
}
void add(int p, int l, int r, int pos){
if(l == r){
dep[p] ++;
return;
}
int mid = l + r >> 1;
if(pos <= mid) add(t[p].l, l, mid, pos);
else add(t[p].r, mid+1, r, pos);
}
// find 返回的是祖先节点的下标
int find(int p, int pos){
int index = getIndex(p, 1, n, pos);
if(fa[index] == pos)
return index;
else
return find(p, fa[index]);
}
void merge(int last, int &p, int x, int y){
int fax = find(p, x), fay = find(p, y);
if(fa[fax] == fa[fay]) return;
if(dep[fax] > dep[fay]) swap(fax, fay);
change(last, p, 1, n, fa[fax], fa[fay]);
if(dep[fax] == dep[fay]) add(p, 1, n, fa[fay]);
}
}uf; int main(){
scanf("%d%d", &n, &m);
uf.build(root[0], 1, n);
for(int i=1;i<=m;i++){
int op, x, y;
scanf("%d%d", &op, &x);
if(op == 1){
scanf("%d", &y);
root[i] = root[i-1];
uf.merge(root[i-1], root[i], x, y);
} else if(op == 2) root[i] = root[x];
else{
scanf("%d", &y);
root[i] = root[i-1];
int fax = uf.find(root[i], x);
int fay = uf.find(root[i], y);
if(uf.fa[fax] == uf.fa[fay]) puts("1");
else puts("0");
}
} return 0;
}

带权删除并查集

2019南昌A

  1. k a b: merge the tribe containing the a-th family and the tribe containing the b-th family;
  2. k a: make the a-th family into extermination;
  3. k a b: the a-th family moves away from their tribe and join in the tribe containing the b-th family;
  4. k a b: report that if the a-th family and the b-th one belong to the same tribe;
  5. k a: report the total number of families in the tribe which contains the a-th family.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 1000000 + 50;
struct Node{
int op, x, y;
}o[N];
vector<int> g[N];
int n, m;
int fa[N*2], dep[N*2], sz[N*2], to[N], tot;
int res[N];
int find(int x){
if(x == fa[x]) return x;
return find(fa[x]);
}
void dfs(int u){
for(auto id : g[u]){
int op = o[id].op, x = o[id].x, y = o[id].y;
if(op == 1){
if(to[x] == -1 || to[y] == -1){
dfs(id);
continue;
}
int rx = find(to[x]), ry = find(to[y]);
if(rx == ry){ dfs(id); continue;}
else if(dep[rx] == dep[ry]){
fa[ry] = rx; sz[rx] += sz[ry]; dep[rx] ++;
dfs(id);
fa[ry] = ry; sz[rx] -= sz[ry]; dep[rx] --;
} else {
if(dep[rx] < dep[ry]) swap(rx, ry);
fa[ry] = rx; sz[rx] += sz[ry];
dfs(id);
fa[ry] = ry; sz[rx] -= sz[ry];
}
} else if(op == 2){
if(to[x] == -1) { dfs(id); continue; }
int rx = find(to[x]);
int t = to[x];
to[x] = -1;
sz[rx] --;
dfs(id);
to[x] = t;
sz[rx] ++;
} else if(op == 3){
if(to[x] == - 1 || to[y] == -1) {dfs(id); continue; }
int t = to[x];
int rx = find(to[x]), ry = find(to[y]);
sz[rx]--;
to[x] = ++tot;
sz[to[x]] = 1;
fa[to[x]] = ry;
sz[ry] ++;
dfs(id);
sz[ry] --;
sz[rx] ++;
to[x] = t;
} else {
if(op == 4){
if(to[x] == -1 || to[y] == -1){
res[id] = 0;
} else {
res[id] = find(to[x]) == find(to[y]);
}
} else if(op == 5){
if(to[x] == -1) res[id] = 0;
else {
res[id] = sz[find(to[x])];
}
}
dfs(id);
}
}
}
int main(){
scanf("%d%d", &n, &m);
for(int i=1;i<=m;i++){
int op, k, x, y = 0;scanf("%d%d%d", &op, &k, &x);
g[k].push_back(i);
if(op != 2 && op != 5) scanf("%d", &y);
o[i] = {op, x, y};
}
for(int i=1;i<=n;i++) to[i] = i, fa[i] = i, sz[i] = 1;
tot = n;
dfs(0);
for(int i=1;i<=m;i++){
if(o[i].op == 4) puts(res[i] ? "Yes" : "No");
else if(o[i].op == 5){
printf("%d\n", res[i]);
}
}
return 0;
}

树链剖分

int fa[N], dep[N], sz[N], son[N], top[N], dfn[N], rnk[N], cnt;
int head[N], ver[N << 1], nxt[N << 1], tot;
void dfs1(int x){
sz[x] = 1;
son[x] = 0;
for (int i = head[x]; i;i=nxt[i]){
int y = ver[i];
if(y == fa[x])
continue;
dep[y] = dep[x] + 1;
fa[y] = x;
dfs1(y);
sz[x] += sz[y];
if(sz[y] > sz[son[x]])
son[x] = y;
}
}
void dfs2(int x,int tp){
dfn[x] = ++cnt;
rnk[cnt] = x;
top[x] = tp;
if(son[x])
dfs2(son[x], tp);
for (int i = head[x]; i;i=nxt[i]){
int y = ver[i];
if(y == fa[x] || y == son[x])
continue;
dfs2(y, y);
}
}
ll querysum(int x,int y){
ll res = 0;
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]])
swap(x, y);
res = (res + query(1, dfn[top[x]], dfn[x])) % mod;
x = fa[top[x]];
}
if(dep[x] < dep[y])
swap(x, y);
res = (res + query(1, dfn[y], dfn[x])) % mod;
return res;
}

左偏树

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int tot,v[maxn],l[maxn],r[maxn],d[maxn];
int n,m,fa[maxn],del[maxn];
int merge(int x,int y){
if(!x)return y;
if(!y)return x;
if(v[x] > v[y] || (v[x] == v[y] && x > y))
swap(x,y);
r[x] = merge(r[x],y);
fa[l[x]] = fa[r[x]] = fa[x] = x;
if(d[l[x]] < d[r[x]])
swap(l[x],r[x]);
d[x] = d[r[x]] + 1;
return x;
}
int init(int x){
tot ++;
v[tot] = x;
l[tot] = r[tot] = d[tot] = 0;
}
int insert(int x,int y){
return merge(x,init(y));
}
int top(int x){
return v[x];
}
int pop(int x){
del[x] = 1;
fa[l[x]] = l[x];
fa[r[x]] = r[x];
return fa[x] = merge(l[x],r[x]);
}
int find(int x){
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int main(){
d[0] = -1;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
v[i] = x;
fa[i] = i;
}
while(m--){
int op,x,y;
scanf("%d",&op);
if(op == 1){//合并,若删除或者xy在同一个堆则无视
scanf("%d%d",&x,&y);
if(del[x] || del[y])continue;
x = find(x);y = find(y);
if(x == y)continue;
fa[x] = fa[y] = merge(x,y);
}
else{//输出所在堆的最小数,并将其删除
scanf("%d",&x);
if(del[x]){
puts("-1");continue;
}
x = find(x);
printf("%d\n",v[x]);
//cout << l[x] << ' ' << r[x] << endl;
pop(x);
}
}
return 0;
}

树哈希

  1. Method I (Hacked)

\(f_{now} = size_{now} \times \sum f_{{son}_{now,i}} \times seed^{i-1}\)

注:要对子节点以 f 关键字排序

  1. Method II (Hacked)

$f_{now}=\bigoplus f_{son_{now,i}}\times seed+size_{son_{now,i}} $

  1. Method III

\(f_{now} = 1 + \sum f_{{son}_{now,i}} \times primes(size_{{son}_{now,i}})\)

线段树合并

const int N = 200000 + 5;
int head[N], ver[N<<1], nxt[N<<1], tot;
int dep[N], f[N][20];
int n, m;
void add(int x, int y){
ver[++tot] = y, nxt[tot] = head[x], head[x] = tot;
}
void dfs(int x, int fa){
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(y == fa) continue;
f[y][0] = x;
dep[y] = dep[x] + 1;
dfs(y, x);
}
}
int lca(int x, int y){
if(dep[x] > dep[y]) swap(x, y);
for(int i=19;i>=0;i--) if(dep[f[y][i]] >= dep[x]) y = f[y][i];
if(x == y) return x;
for(int i=19;i>=0;i--) if(f[y][i] != f[x][i]) y = f[y][i], x = f[x][i];
return f[x][0];
}
struct SegTree{
int l, r;
int mx, pos;
}t[N*40];
int rt[N], cnt;
int res[N], st[N*40], top;
int newnode(){
if(top) return st[top--];
return ++cnt;
}
void rubbish(int p){
st[++top] = p;
t[p].l = t[p].r = t[p].mx = t[p].pos = 0;
}
void pushup(int p){
int ls = t[p].l, rs = t[p].r;
if(t[ls].mx >= t[rs].mx){
t[p].mx = t[ls].mx;
t[p].pos = t[ls].pos;
} else {
t[p].mx = t[rs].mx;
t[p].pos = t[rs].pos;
}
}
void change(int &p, int l, int r, int pos, int val){
if(!p) p = newnode();
if(l == r){
t[p].mx += val;
if(t[p].mx) t[p].pos = l;
else t[p].pos = 0;
return ;
}
int mid = l + r >> 1;
if(pos <= mid) change(t[p].l, l, mid, pos, val);
else change(t[p].r, mid+1, r, pos, val);
pushup(p);
}
int merge(int u, int v, int l, int r){
if(!u) return v;
if(!v) return u;
int w = newnode();
if(l == r){
t[w].mx = t[u].mx + t[v].mx;
if(t[w].mx)t[w].pos = l;
return w;
}
int mid = l + r >> 1;
t[w].l = merge(t[u].l, t[v].l, l, mid);
t[w].r = merge(t[u].r, t[v].r, mid + 1, r);
pushup(w);
rubbish(u);rubbish(v);
return w;
}
void get(int x, int fa){
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(y == fa) continue;
get(y, x);
rt[x] = merge(rt[x], rt[y], 1, 1e5);
}
res[x] = t[rt[x]].pos;
}
int main(){
scanf("%d%d", &n, &m);
for(int i=1;i<n;i++){
int x, y;scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}
dep[1] = 1;
dfs(1, 0);
for(int j=1;j<20;j++)for(int i=1;i<=n;i++) f[i][j] = f[f[i][j-1]][j-1];
while(m--){
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
int t = lca(x, y);
if(t != x && t != y){
change(rt[x], 1, 1e5, z, 1);
change(rt[y], 1, 1e5, z, 1);
change(rt[t], 1, 1e5, z, -1);
change(rt[f[t][0]], 1, 1e5, z, -1);
}else if(t == x){
change(rt[y], 1, 1e5, z, 1);
change(rt[f[t][0]], 1, 1e5, z, -1);
}else if(t == y){
change(rt[x], 1, 1e5, z, 1);
change(rt[f[t][0]], 1, 1e5, z, -1);
}
}
get(1, 0);
for(int i=1;i<=n;i++) printf("%d\n", res[i]);
return 0;
}

洛谷P5494

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 200000 + 5;
/*
0, p, x, y, p 中[x,y] 中的数字放入新的可重集合
1, p, t 将 t 放入 p,并且清空 t
2, p, x, q, 在 p 中放入 x 个q
3,p, x, y,查询p中[x,y]
4, p,k,查询p中第 k 小的数字
*/
int n, m;
struct SegTree{
int ls, rs;
ll sum;// 区间数字个数
}t[N*40];
int root[N];
int st[N*40], top, tot;
int newnode(){
if(top) return st[top--];
return ++tot;
}
void rubbish(int p){
st[++top] = p;
t[p].ls = t[p].rs = t[p].sum = 0;
}
// 0 p x y
void build(int &p, int &q, int l, int r, int x, int y){
if(!q) return ;
if(l >= x && r <= y){
// 信息要传输完整,除了考虑sum,还要考虑其他
p = q;
q = 0;
return;
}
if(!p) p = newnode();
int mid = l + r >> 1;
if(mid >= x) build(t[p].ls, t[q].ls, l, mid, x, y);
if(mid < y) build(t[p].rs, t[q].rs, mid+1, r, x, y);
t[q].sum = t[t[q].ls].sum + t[t[q].rs].sum;
t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum;
} // 1 p q
int merge(int p, int q, int l, int r){
if(!p || !q) return p | q;
int w = newnode();
t[w].sum = t[p].sum + t[q].sum;
if(l == r){
//t[w].sum = t[p].sum + t[q].sum;
return w;
}
int mid = l + r >> 1;
t[w].ls = merge(t[p].ls, t[q].ls, l, mid);
t[w].rs = merge(t[p].rs, t[q].rs, mid+1, r);
rubbish(p); rubbish(q);
//t[w].sum = t[t[w].ls].sum + t[t[w].rs].sum;
return w;
}
// 2, p, x, q;
void change(int &p, int l, int r, int pos, int num){
if(!p) p = newnode();
if(l == r){
t[p].sum += num;return;
}
int mid = l + r >> 1;
if(pos <= mid) change(t[p].ls, l, mid, pos, num);
else change(t[p].rs, mid+1, r, pos, num);
t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum;
}
// 3 p x y
ll ask(int p, int l, int r, int x, int y){
if(l >= x && r <= y) return t[p].sum;
int mid = l + r >> 1;
ll res = 0;
if(t[p].ls && x <= mid) res += ask(t[p].ls, l,mid, x, y);
if(t[p].rs && mid < y) res += ask(t[p].rs, mid+1, r, x, y);
return res;
}
// 4 p, k
ll ask(int p, int l, int r, ll k){
if(k > t[p].sum || k <= 0) return -1;
if(l == r) return l;
int mid = l + r >> 1;
if(t[t[p].ls].sum >= k) return ask(t[p].ls, l, mid, k);
else return ask(t[p].rs, mid+1, r, k - t[t[p].ls].sum);
}
int main(){
scanf("%d%d", &n, &m);
int now = 1;
for(int i=1;i<=n;i++){
int x;scanf("%d", &x);
change(root[1], 1, n, i, x);
}
while(m--){
int op, p, x, y, q;
scanf("%d%d", &op, &p);
if(op == 0){
scanf("%d%d", &x, &y);
now ++;
build(root[now],root[p], 1, n, x, y);
} else if(op == 1){
scanf("%d", &q);
root[p] = merge(root[p], root[q], 1, n);
} else if(op == 2){
scanf("%d%d", &x, &q);
change(root[p], 1, n, q, x);
} else if(op == 3){
scanf("%d%d", &x, &y);
printf("%lld\n", ask(root[p], 1, n, x, y));
} else {
ll x;
scanf("%lld", &x);
printf("%lld\n", ask(root[p], 1, n, x));
}
} return 0;
}

分块

莫队

#include <iostream>
#include <algorithm>
#include <math.h>
#include <cstdio>
using namespace std;
const int N = 50010;
typedef long long ll;
int a[N],be[N],n,m;
ll res = 0,sum[N];
struct node{
int l,r,id;
ll A,B;
}q[N];
//如果在同一块,则按照右端点排序,否则按照左端点
bool cmp1(node a,node b){
return be[a.l] == be[b.l] ? a.r < b.r : a.l < b.l;
}
bool cmp(node a,node b){
return a.id < b.id;
}
ll gcd(ll a,ll b){return b == 0 ? a : gcd(b,a%b);}
ll S(ll x){return x * x;}
//先减去上一次的影响,修改后再重新加新的影响
void move(int pos,int add){res -= S(sum[a[pos]]);sum[a[pos]] += add;res += S(sum[a[pos]]);}
int main(){
scanf("%d%d",&n,&m);
int base = sqrt(n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);q[i].id = i;
be[i] = (i-1) / base + 1;//be[i]即i在第几块
}
for(int i=1;i<=m;i++)scanf("%d%d",&q[i].l,&q[i].r);
sort(q+1,q+1+m,cmp1);
int l = 1,r = 0;
res = 0;//res为当点询问区间内出现的所有颜色的个数平方和
for(int i=1;i<=m;i++){
//暴力调整区间,维护res
while(l < q[i].l)move(l++,-1);
while(l > q[i].l)move(--l,1);
while(r < q[i].r)move(++r,1);
while(r > q[i].r)move(r--,-1);
if(l == r){
q[i].A = 0;q[i].B = 1;continue;
}
q[i].A = res - (r - l + 1);//计算答案分子部分
q[i].B = 1ll * (r - l + 1) * (r - l);//分母部分
ll g = gcd(q[i].A,q[i].B);//约分
q[i].A /= g;
q[i].B /= g;
}
sort(q+1,q+1+m,cmp);
for(int i=1;i<=m;i++){
printf("%lld/%lld\n",q[i].A,q[i].B);
}
return 0;
}

莫队优化

bool cmp1(node a,node b){
return be[a.l] == be[b.l] ?
(be[a.l]&1 ? a.r < b.r : a.r > b.r)
: a.l < b.l;
}

带修莫队

#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
struct Query
{
int l,r,t,id;
}q[N];
struct Modify
{
int pos,New,Old;
}c[N];
int n,m,a[N],num[100*N],be[N],res,ans[N],l=1,r,now[N];
char op[3];
bool cmp(Query a,Query b){
return be[a.l] == be[b.l] ? (be[a.r] == be[b.r] ? a.t < b.t : (be[a.l]&1 ? a.r < b.r : a.r > b.r)) : a.l < b.l;
}
void move(int pos,int add){
num[a[pos]]+=add;
if(num[a[pos]] == 0 && add < 0)
res--;
else if(num[a[pos]] == 1 && add > 0)
res++;
}
void changeTime(int pos,int dat){
if(pos >= l && pos <= r){
move(pos,-1);
a[pos] = dat;
move(pos,1);
}
else a[pos] = dat;
}
int main(){
scanf("%d%d",&n,&m);
int base = pow(n,0.666666);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
now[i] = a[i];be[i] = (i-1) / base + 1;
}
int t = 0,cur = 0;
for(int i=1;i<=m;i++){
int x,y;scanf("%s%d%d",op,&x,&y);
if(op[0] == 'Q')q[++t] = {x,y,cur,t};
else c[++cur] = {x,y,now[x]},now[x] = y;
}
sort(q+1,q+t+1,cmp);
cur = 0;
for(int i=1;i<=t;i++){
while(cur < q[i].t){
changeTime(c[cur+1].pos,c[cur+1].New);cur++;
}
while(cur > q[i].t){
changeTime(c[cur].pos,c[cur].Old);cur--;
}
while(l < q[i].l)move(l++,-1);
while(l > q[i].l)move(--l,1);
while(r < q[i].r)move(++r,1);
while(r > q[i].r)move(r--,-1);
ans[q[i].id] = res;
}
for(int i=1;i<=t;i++)printf("%d\n",ans[i]);
return 0;
}

树上莫队

#include <bits/stdc++.h>
using namespace std;
const int N = 40010;
const int M = 100010;
vector<int> G[N],alls;
int be[N],f[N][19],n,m,ans[M],dep[N],u=1,v=1,sum[N],vis[N],a[N],base,cnt;
int res;
int st[N],top;
struct Query{
int l,r;
int id;
}q[M];
void dfs(int x,int fa = -1){
for(int i=1;i<18;i++)
f[x][i] = f[f[x][i-1]][i-1];
int bottom = top;
for(int i = 0;i<G[x].size();i++){
int y = G[x][i];
if(y == fa)continue;
dep[y] = dep[x] + 1;
f[y][0] = x;
dfs(y,x);
if(top - bottom >= base){
cnt++;
while(top != bottom){
be[st[top--]] = cnt;
}
}
}
st[++top] = x;
}
bool cmp(Query a,Query b){
return be[a.l] == be[b.l] ? be[a.r] < be[b.r] : be[a.l] < be[b.l];
}
int LCA(int x,int y){
if(dep[x] > dep[y])swap(x,y);
for(int i=18;i>=0;i--)if(dep[x] <= dep[f[y][i]]) y = f[y][i];
if(x == y)return x;
for(int i=18;i>=0;i--)if(f[x][i] != f[y][i])x = f[x][i],y = f[y][i];
return f[x][0];
}
void Run(int u){
if(vis[u] == 1){
vis[u] = 0;
if(--sum[a[u]] == 0)res--;
}
else{
vis[u] = 1;
if(sum[a[u]]++ == 0)res++;
}
}
void move(int x,int y){
if(dep[x] < dep[y])swap(x,y);
while(dep[x] > dep[y])Run(x),x = f[x][0];
while(x != y)Run(x),Run(y),x = f[x][0],y = f[y][0];
}
int main(){
scanf("%d%d",&n,&m);
base = sqrt(n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
alls.push_back(a[i]);
}
/*离散化*/
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
for(int i=1;i<=n;i++){
int id = lower_bound(alls.begin(),alls.end(),a[i]) - alls.begin() + 1;
a[i] = id;
}
//存树
for(int i=1;i<n;i++){
int x,y;scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
dep[1] = 1;//这里如果不单独设为wa
dfs(1);
while(top)be[st[top--]] = cnt;
//存询问
for(int i=1;i<=m;i++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id = i;
}
sort(q+1,q+1+m,cmp);
for(int i=1;i<=m;i++){
if(u != q[i].l){move(u,q[i].l);u=q[i].l;}
if(v != q[i].r){move(v,q[i].r);v=q[i].r;}
int anc = LCA(u,v);
Run(anc);//单独考虑LCA
ans[q[i].id] = res;
Run(anc);//
}
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
return 0;
}

分块套路

#include <iostream>
#include <cstdio>
#include <math.h> using namespace std;
typedef long long ll;
const int N = 100010;
ll a[N],sum[N],add[N];
//每段左右端点,每个位置所处哪一段
int L[N],R[N],pos[N];
int n,m,t;
void change(int l,int r,ll d){
int p = pos[l],q = pos[r];
if(p == q){
for(int i=l;i<=r;i++)a[i] += d;
sum[p] += d * (r - l + 1);
}
else{
for(int i = p + 1;i <= q-1;i++)add[i] += d;
for(int i = l;i <= R[p];i++)a[i] += d;
sum[p] += d * (R[p] - l + 1);
for(int i=L[q];i<=r;i++)a[i] += d;
sum[q] += d * (r - L[q] + 1);
}
}
ll ask(int l,int r){
int p = pos[l], q = pos[r];
ll res = 0;
if(p == q){
for(int i=l;i<=r;i++)res += a[i];
res += add[p] * (r-l+1);
}
else{
for(int i=l;i<=R[p];i++)res += a[i];
res += add[p] * (R[p] - l + 1);
for(int i=L[q];i<=r;i++)res += a[i];
res += add[q] * (r - L[q] + 1);
for(int i=p+1;i<=q-1;i++)res += sum[i] + add[i] * (R[i] - L[i] + 1);
}
return res;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
t = sqrt(n);
for(int i=1;i<=t;i++){
L[i] = (i-1) * t+1;
R[i] = i * t;
}
if(R[t] < n) t++,L[t] = R[t-1]+1,R[t] = n;
for(int i=1;i<=t;i++){
for(int j=L[i];j<=R[i];j++){
pos[j] = i;
sum[i] += a[j];
}
}
//指令
while(m--){
char op[3];int l,r,d;
scanf("%s%d%d",op,&l,&r);
if(op[0] == 'C'){
scanf("%d",&d);
change(l,r,d);
}
else printf("%lld\n",ask(l,r));
}
return 0;
}

主席树

静态区间第k大

const int N = 200000 + 5;
struct Seg{
int ls, rs;
int sum;
}t[N*40];
int rt[N], tot;
vector<int> all;
int n, a[N], l, r, k, m;
int build(int l, int r){
int p = ++tot;
if(l == r) {
t[p].sum = 0;
return p;
}
int mid = l + r >> 1;
t[p].ls = build(l, mid);
t[p].rs = build(mid+1, r);
t[p].sum = 0;
return p;
}
int insert(int now, int l, int r, int pos, int val){
int p = ++tot;
t[p] = t[now];
if(l == r){
t[p].sum += val;
return p;
}
int mid = l + r >> 1;
if(pos <= mid) t[p].ls = insert(t[now].ls, l, mid, pos, val);
else t[p].rs = insert(t[now].rs, mid+1, r, pos, val);
t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum;
return p;
}
int ask(int p, int q, int l, int r, int k){
if(l == r) return l;
int mid = (l + r) >> 1;
int lcnt = t[t[q].ls].sum - t[t[p].ls].sum;
if(k <= lcnt) return ask(t[p].ls, t[q].ls, l, mid, k);
else return ask(t[p].rs, t[q].rs, mid+1, r, k - lcnt);
}
int main(){
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++){
scanf("%d", &a[i]);
all.push_back(a[i]);
}
sort(all.begin(),all.end());
all.erase(unique(all.begin(),all.end()),all.end());
for(int i=1;i<=n;i++){
a[i] = lower_bound(all.begin(),all.end(),a[i]) - all.begin() + 1;
}
rt[0] = build(1, all.size());
for(int i=1;i<=n;i++){
rt[i] = insert(rt[i-1], 1, all.size(), a[i], 1);
}
while(m--){
scanf("%d%d%d", &l, &r, &k);
printf("%d\n", all[ask(rt[l-1], rt[r], 1, all.size(), k) - 1]);
}
return 0;
}

树套树

树状数组套主席树

例题选自 2019徐州ICPC H

区间维护mex

using namespace std;
using ll = long long;
#define dbg(x...) do { cout << "\033[32;1m" << #x << " -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg, const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 2e5 + 10;
int n, m, q, a[N], L[30][30], R[30][30], cl, cr;
inline int lowbit(int x) { return x & -x; }
struct SEG {
struct node {
int ls, rs;
ll sum;
void init() { ls = rs = sum = 0; }
}t[N * 80];
int rt[N], tot;
ll res;
int newnode() {
++tot;
t[tot].init();
return tot;
}
void init() { memset(rt, 0, sizeof rt); tot = 0; }
void update(int &rt, int l, int r, int pos, int v) {
if (!rt) rt = newnode();
t[rt].sum += v;
if (l == r) return;
int mid = (l + r) >> 1;
if (pos <= mid) update(t[rt].ls, l, mid, pos, v);
else update(t[rt].rs, mid + 1, r, pos, v);
}
void update(int x, int pos, int v) {
for (; x <= n; x += lowbit(x)) {
update(rt[x], 1, m, pos, v);
}
}
void query(int dep, int l, int r, int ql, int qr) {
if (ql > qr) return;
if (l >= ql && r <= qr) {
for (int i = 1; i <= cl; ++i) res -= t[L[dep][i]].sum;
for (int i = 1; i <= cr; ++i) res += t[R[dep][i]].sum;
return;
}
int mid = (l + r) >> 1;
if (ql <= mid) {
for (int i = 1; i <= cl; ++i) L[dep + 1][i] = t[L[dep][i]].ls;
for (int i = 1; i <= cr; ++i) R[dep + 1][i] = t[R[dep][i]].ls;
query(dep + 1, l, mid, ql, qr);
}
if (qr > mid) {
for (int i = 1; i <= cl; ++i) L[dep + 1][i] = t[L[dep][i]].rs;
for (int i = 1; i <= cr; ++i) R[dep + 1][i] = t[R[dep][i]].rs;
query(dep + 1, mid + 1, r, ql, qr);
}
}
}seg; int main() {
m = 2e5;
while (scanf("%d%d", &n, &q) != EOF) {
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
seg.init();
for (int i = 1; i <= n; ++i) {
seg.update(i, a[i], a[i]);
}
int op, x, y;
for (int i = 1; i <= q; ++i) {
scanf("%d%d%d", &op, &x, &y);
if (op == 1) {
seg.update(x, a[x], -a[x]);
a[x] = y;
seg.update(x, a[x], a[x]);
} else {
--x;
cl = cr = 0;
for (int j = x; j; j -= lowbit(j)) {
L[0][++cl] = seg.rt[j];
}
for (int j = y; j; j -= lowbit(j)) {
R[0][++cr] = seg.rt[j];
}
ll l = 0, r = 0;
do{
r = l + 1;
seg.res = 0;
seg.query(0, 1, m, 1, min(1ll * m, r));
l = seg.res;
} while (l >= r);
printf("%lld\n", l + 1);
}
}
}
return 0;
}

线段树套线段树

常数比较大

const int N = 50000 + 5;
int rt[N << 2], n, m, maxn, tot;
int b[N * 2], totn, L[N], R[N], op[N];
ll K[N];
struct SegTree{
int l, r;
int tag;
ll sz;
} t[N * 400];
void pushdown(int p, int l, int r){
if(t[p].tag){
if(!t[p].l)
t[p].l = ++tot;
if(!t[p].r)
t[p].r = ++tot;
int ls = t[p].l, rs = t[p].r, &v = t[p].tag, mid = l + r >> 1;
t[ls].tag += v, t[rs].tag += v;
t[ls].sz += v * (mid - l + 1);
t[rs].sz += v * (r - mid);
v = 0;
}
}
void update(int &p, int l,int r,int ql, int qr){
if(!p)
p = ++tot;
if(l >= ql && r <= qr){
t[p].tag++;
t[p].sz += r - l + 1;
return;
}
pushdown(p, l, r);
int mid = l + r >> 1;
if(mid >= ql)
update(t[p].l, l, mid, ql, qr);
if(mid < qr)
update(t[p].r, mid + 1, r, ql, qr);
t[p].sz = t[t[p].l].sz + t[t[p].r].sz;
}
void add(int p, int l, int r, int pos, int ql, int qr){
update(rt[p], 1, n, ql, qr);
if(l == r)
return;
int mid = l + r >> 1;
if(pos <= mid)
add(p * 2, l, mid, pos, ql, qr);
else
add(p * 2 + 1, mid + 1, r, pos, ql, qr);
}
ll getsum(int &p, int l,int r,int ql, int qr){
if(!p)
return 0;
if(l >= ql && r <= qr)
return t[p].sz;
pushdown(p, l, r);
int mid = l + r >> 1;
ll res = 0;
if(ql <= mid)
res += getsum(t[p].l, l, mid, ql, qr);
if(mid < qr)
res += getsum(t[p].r, mid + 1, r, ql, qr);
return res;
}
// [l,r] 是权值线段树区间 ,[ql,qr]是询问区间
int query(int p, int l,int r, int ql,int qr, ll x){
if(l == r)
return b[l];
int mid = l + r >> 1;
ll cnt = getsum(rt[p * 2 + 1], 1, n, ql, qr);
//cout << l << ' ' << r << ' ' << cnt << endl;
if(cnt < x)
return query(p * 2, l, mid, ql, qr, x - cnt);
else
return query(p * 2 + 1, mid + 1, r, ql, qr, x);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m;i++){
scanf("%d%d%d%lld", &op[i], &L[i], &R[i], &K[i]);
if(op[i] == 1){
b[++totn] = K[i];
}
}
sort(b + 1, b + 1 + totn);
totn = unique(b + 1, b + 1 + totn) - b - 1;
for (int i = 1; i <= m;i++){
if(op[i] == 1){
K[i] = lower_bound(b + 1, b + 1 + totn, K[i]) - b;
add(1, 1, totn, K[i], L[i], R[i]);
} else {
printf("%d\n", query(1, 1, totn, L[i], R[i], K[i]));
}
}
return 0;
}

四. 图论

最短路

1. Dijkstra

\(n^2\)

const int N = 3010 + 5;
int a[N][N], d[N], n, m;
bool v[N];
void dijkstra(){
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
d[1] = 0;
for(int i=1;i<n;i++){ // 重复进行 n-1 次
int x = 0;
for(int j=1;j<=n;j++){
if(!v[j] && (x == 0 || d[j] < d[x])) x = j;
}
v[x] = 1;
for(int y = 1; y <= n; y++)
d[y] = min(d[y], d[x] + a[x][y]);
}
}

\((m+n)\log n\)

const int N = 100010;
const int M = 1000010;
int head[N], ver[M], edge[M], nxt[M], d[N];
bool v[N];
int n, m, tot, s;
typedef pair<int,int> pii;
priority_queue<pii,vector<pii>, greater<pii> >q;
void add(int x, int y, int z){ver[++tot] = y,edge[tot] = z, nxt[tot]=head[x],head[x] = tot; }
void dijkstra(){
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
d[1] = 0;
q.push(make_pair(0,1));
while(q.size()){
int x = q.top().second;q.pop();
if(v[x])continue;
v[x] = 1;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(d[y] > d[x] + edge[i]){
d[y] = d[x] + edge[i];
q.push({d[y], y});
}
}
}
}
int main() {
scanf("%d%d%d",&n,&m,&s);
for(int i=1,x,y,z;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);//此模板为有向图
}
dijkstra();
for(int i=1;i<=n;i++)printf("%d ",d[i]);
return 0;
}

2. SPFA

\(O(NM)\)

const int N = 100000 + 5;
const int M = 1000010;
int head[N], ver[M], edge[M], nxt[M], d[N];
bool v[N];
int n, m, tot, s;
queue<int> q;
void add(int x, int y, int z){ver[++tot] = y,edge[tot] = z, nxt[tot]=head[x],head[x] = tot; }
void spfa(){
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
d[1] = 0, v[1] = 1;
q.push(1);
while(q.size()){
int x = q.front();q.pop();
v[x] = 0;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(d[y] > d[x] + edge[i]){
d[y] = d[x] + edge[i];
if(!v[y]) q.push(y), v[y] = 1;
}
}
}
} int main() {
scanf("%d%d%d",&n,&m,&s);
for(int i=1,x,y,z;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);//此模板为有向图
}
spfa();
for(int i=1;i<=n;i++)printf("%d ",d[i]);
return 0;
}

3. Floyd

\(O(n^3)\) 可以用来求传递闭包

void floyd(){
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

Johnson

利用势能与路径无关,至于起点和终点有关的性质,将负权边转换为正权边,进而利用dijkstra堆优化快速求解最短路

const int N = 3000 + 5;
const int M = 20000 + 5;
int n,m;
int head[N], ver[M], nxt[M], edge[M], tot;
int h[N],d[N],v[N],cnt[N];
priority_queue<pair<int,int> > q;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
}
bool spfa(){
memset(h, 0x3f, sizeof h);
memset(v, 0, sizeof v);
queue<int> q;
q.push(0);
h[0] = 0;
v[0] = 1;
while(q.size()){
int x = q.front();q.pop();
v[x] = 0;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(h[y] > h[x] + edge[i]){
h[y] = h[x] + edge[i];
cnt[y] = cnt[x] + 1;
if(cnt[y] >= n) return false;
if(!v[y]) q.push(y), v[y] = 1;
}
}
}
return true;
}
void dijkstra(int s){
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
q.push({0, s});d[s] = 0;
while(q.size()){
int x = q.top().second;q.pop();
if(v[x])continue;
v[x] = 1;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(d[y] > d[x] + edge[i]){
d[y] = d[x] + edge[i];
q.push({-d[y],y});
}
}
}
ll res = 0;
for(int i=1;i<=n;i++){
if(d[i] == inf) res += 1ll * i * 1e9;
else {
// d[i] = f[i] + h[s] - h[i];
d[i] -= h[s] - h[i];
res += 1ll * i * d[i];
}
}
printf("%lld\n",res);
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1,x,y,z;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
for(int i=1;i<=n;i++) add(0,i,0);
if(!spfa()){
puts("-1");
return 0;
}
for(int x=1;x<=n;x++){
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
edge[i] = edge[i] + h[x] - h[y];
}
}
for(int x=1;x<=n;x++){
dijkstra(x);
}
return 0;
}

最小生成树

1. Kruskal

\(O(m\log m)\) 当边权比较小时可以直接桶排,降低复杂度(2019上海区域赛E)

struct rec{int x,y,z;} edge[500010];
int fa[100010], n, m, ans;
bool operator < (rec a, rec b){
return a.z < b.z;
}
int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);}
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].z);
sort(edge+1,edge+m+1);
for(int i=1;i<=n;i++) fa[i] = i;
for(int i=1;i<=m;i++){
int x = find(edge[i].x);
int y = find(edge[i].y);
if(x == y)continue;
fa[x] = y;
ans += edge[i].z;
}
cout << ans << endl;
return 0;
}

2. Prim

\(O(n^2)\)

const int N = 3000 + 5;
int a[N][N], d[N], n, m, ans;
bool v[N];
void prim(){
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
d[1] = 0;
for(int i=1;i < n;i++){
int x = 0;
for(int j=1;j<=n;j++)
if(!v[j] && (x == 0 || d[j] < d[x])) x = j;
v[x] = 1;
for(int y=1;y<=n;y++)
if(!v[y])d[y] = min(d[y], a[x][y]);
}
}
int main() {
cin >> n >> m;
memset(a, 0x3f, sizeof a);
for(int i=1;i<=n;i++) a[i][i] = 0;
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
a[y][x] = a[x][y] = min(a[x][y], z);
}
prim();
for(int i=2;i<=n;i++) ans += d[i];
cout << ans << endl;
return 0;
}

\(O(m\log n)\)

const int N = 400010;
int n,m,st,ed;
int head[N],ver[N],nxt[N],edge[N],tot;
int d[N],v[N];
inline void add(int x,int y,int z){
ver[++tot] = y;edge[tot] = z;nxt[tot] = head[x];head[x] = tot;
}
int x,y,z;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
memset(d,0x3f,sizeof d);
priority_queue<pair<int,int> > q;
q.push({0,1});d[1] = 0;
ll res = 0;
while(q.size()){
int x = q.top().second;q.pop();
if(v[x])continue;
res += d[x];
v[x] = 1;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(d[y] > edge[i]){
d[y] = edge[i];
q.push({-d[y],y});
}
}
}
cout << res << endl;
return 0;
}

最小斯坦纳树

给定一个包含 \(n\) 个结点和 \(m\) 条带权边的无向连通图 \(G=(V,E)\)。

再给定包含 \(k\) 个结点的点集 \(S\),选出 \(G\) 的子图 \(G'=(V',E')\) 使得:

  1. \(S\subseteq V'\)
  2. \(G′\) 为连通图;
  3. \(E′\) 中所有边的权值和最小。

    你只需要求出 \(E′\)中所有边的权值和。

\(dp[i][s]\) 表示 以 i 为根的一棵树,包含集合 S 中所有点的最小代价

  1. \(w[i][j] + dp[j][s] -> dp[i][s]\)
  2. \(dp[i][T] + dp[i][S-T] -> dp[i][S]\quad(T \subseteq S)\)

第二类转移可以用枚举子集来转移,第一类转移是一个三角不等式,可以用spfa或者dijkstra

const int N = 100 + 5;
const int M = 1010;
int n, m, k, x, y, z, tot;
int head[N], ver[M], nxt[M], edge[M], dp[N][4200];
int p[N], vis[N];
priority_queue<pair<int,int>> q;
void add(int x, int y, int z){
ver[++tot] = y; edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
}
void dijkstra(int s){
memset(vis, 0, sizeof vis);
while(q.size()){
auto t = q.top();q.pop();
int x = t.second;
if(vis[x]) continue;
vis[x] = 1;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(dp[y][s] > dp[x][s] + edge[i]){
dp[y][s] = dp[x][s] + edge[i];
q.push(make_pair(-dp[y][s], y));
}
}
}
}
int main(){
scanf("%d%d%d", &n, &m, &k);
for(int i=1;i<=m;i++){
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);add(y, x, z);
}
memset(dp, 0x3f, sizeof dp);
for(int i=1;i<=k;i++){
scanf("%d", &p[i]);
dp[p[i]][1<<(i-1)] = 0;
}
for(int s = 1; s < (1 << k); s++){
for(int i=1; i <= n; i++){
for(int subs = (s-1)&s; subs; subs = s & (subs-1)){
dp[i][s] = min(dp[i][s], dp[i][subs]+dp[i][s^subs]);
}
if(dp[i][s]!=inf) q.push(make_pair(-dp[i][s], i));
}
dijkstra(s);
}
printf("%d\n", dp[p[1]][(1<<k)-1]); return 0;
}

树的直径

树形dp

void dp(int x){
v[x] = 1;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(v[y])continue;
dp(y);
ans = max(ans, d[x] + d[y] + edge[i]);
d[x] = max(d[x], d[y], edge[i]);
}
}

两次dfs或bfs,bfs可以记录路径

const int N = 200010;
vector<pair<int,int> > v[N];
int n, pre[N], vis[N],mark[N];
ll d[N];
int bfs(int s){
queue<int> q;
q.push(s);
memset(vis, 0, sizeof vis);
memset(d, 0, sizeof d);
memset(pre, 0, sizeof pre);
vis[s] = d[s] = 0;
int t = s;
while(q.size()){
int x = q.front();
q.pop();
if(d[x] > d[t])***
t = x;
vis[x] = 1;
for (int i = 0; i < v[x].size();i++){
int y = v[x][i].first;
if(vis[y])
continue;
d[y] = d[x] + v[x][i].second;
pre[y] = x;****
q.push(y);
}
}
return t;
}
int main(){
scanf("%d", &n);
for (int i = 1,x, y, z; i < n;i++){
scanf("%d%d%d", &x, &y, &z);
v[x].push_back({y, z});
v[y].push_back({x, z});
}
int s = bfs(1);
int t = bfs(s);
int p = t, np = pre[t];
ll len = d[t];
cout << len << endl;
while(p != s){
mark[p] = 1;
p = pre[p];
mark[p] = 1;
}
p = t;
return 0;
}

树上启发式合并

求每个子树上不同颜色的个数

// U41492
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 100000 + 5;
int c[N], cou[N], res[N], son[N];
int dfn[N], cid[N],sz[N];
int cnt, num;
vector<int> v[N];
int n,m;
void dfs(int x, int fa){
dfn[x] = ++cnt, cid[cnt] = x;
sz[x] = 1;
for(int i=0;i<v[x].size();i++){
int y = v[x][i];
if(y == fa)continue;
dfs(y,x);
sz[x] += sz[y];
if(son[x] == 0 || sz[y] > sz[son[x]]) son[x] = y;
}
}
//核心思想即所有重边只会走一次,
void get(int x, int fa, int flag){
for(int i=0;i<v[x].size();i++){
int y = v[x][i];
if(y == fa || y == son[x])continue;//不保存答案遍历时,一定不能遍历重儿子
get(y, x, 0);
}
if(son[x]) get(son[x], x, 1);
for(int i=0;i<v[x].size();i++){
int y = v[x][i];
if(y == fa || y == son[x])continue;
for(int j=dfn[y];j<=dfn[y]+sz[y]-1;j++){
if(++cou[c[cid[j]]] == 1) num++;
}
}
if(++cou[c[x]] == 1)num++;
res[x] = num;
if(flag == 0){
for(int j=dfn[x];j<=dfn[x]+sz[x]-1;j++){
if(--cou[c[cid[j]]] == 0) num--;
}
}
}
int main() {
scanf("%d",&n);
for(int i=1;i<n;i++){
int x,y;scanf("%d%d",&x,&y);
v[x].push_back(y);
v[y].push_back(x);
}
for(int i=1;i<=n;i++)scanf("%d",&c[i]);
dfs(1, 0);
get(1, 0, 0);
scanf("%d",&m);
while(m--){
int x;scanf("%d",&x);
printf("%d\n",res[x]);
}
return 0;
}

点分治

求是否存在路径权值和为k的路径

const int N = 10000 + 5;
const int M = 20010;
int head[N], ver[M], edge[M], nxt[M];
int sz[N], del[N], dis[N], st[N], q[N], cnt, top;
int query[101], res[N];
bool has[10000010];
int n, m, tot, all, core, min_size;
void add(int x, int y, int z){
ver[++tot] = y, nxt[tot] = head[x], edge[tot] = z, head[x] = tot;
}
void getsize(int x, int fa){
sz[x] = 1;
int mx = 0;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(y == fa || del[y]) continue;
getsize(y, x);
mx = max(mx, sz[y]);
sz[x] += sz[y];
}
mx = max(mx, all - sz[x]);
if(mx < min_size) {min_size = mx, core = x;}
}
void getdis(int x, int fa){
if(dis[x] <= 10000000)
st[++cnt] = x, q[++top] = x;
for(int i=head[x];i;i=nxt[i]) {
int y = ver[i];
if(del[y] || y == fa) continue;
dis[y] = dis[x] + edge[i];
getdis(y, x);
}
}
void get(int x){
cnt = top = 0;
del[x] = 1;
dis[x] = 0;
has[dis[x]] = true;
q[++top] = x;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(del[y]) continue;
dis[y] = edge[i];
cnt = 0;
getdis(y, x);
for(int j=1;j<=cnt;j++){
for(int k=1;k<=m;k++){
if(query[k] >= dis[st[j]]){
res[k] |= has[query[k] - dis[st[j]]];
}
}
}
for(int j=1;j<=cnt;j++) has[dis[st[j]]] = true;
}
for(int i=1;i<=top;i++) has[dis[q[i]]] = false;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(del[y]) continue;
min_size = inf; all = sz[y];
getsize(y, 0);
getsize(core, 0);
get(core);
}
}
int main(){
scanf("%d%d", &n, &m);
for(int i=1;i<n;i++){
int x, y, z;scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
add(y, x, z);
}
for(int i=1;i<=m;i++) scanf("%d", &query[i]);
min_size = inf;
all = n;
core = 0;
getsize(1, 0);
getsize(core, 0);
get(core);
for(int i=1;i<=m;i++) puts(res[i] ? "AYE" : "NAY");
return 0;
}

最近公共祖先

1. 倍增

onst int SZ = 50010;
int f[SZ][20],d[SZ],dist[SZ];//开足空间
int ver[2*SZ],nxt[2*SZ],edge[2*SZ],head[2*SZ];
int T,n,m,tot,t;
queue<int> q;
void add(int x,int y,int z){
ver[++tot] = y;
edge[tot] = z;
nxt[tot] = head[x];
head[x] = tot;
}
void bfs(){ // dfs或者bfs都可以,d[1]一定要为1
q.push(1);d[1] = 1;
while(q.size()){
int x = q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(d[y])continue;
d[y] = d[x] + 1;
dist[y] = dist[x] + edge[i];
f[y][0] = x;
for(int j=1;j<=t;j++)
f[y][j] = f[f[y][j-1]][j-1];
q.push(y);
}
}
}
int lca(int x,int y){
if(d[x] > d[y])swap(x,y);//x在y上面
for(int i=t;i>=0;i--){
if(d[f[y][i]] >= d[x]) y = f[y][i];//不断往上面调整
}
if(x == y)return x;
for(int i=t;i>=0;i--){
if(f[x][i] != f[y][i]) x = f[x][i],y = f[y][i];
}
return f[x][0];
}

3. Tarjan

线性离线,不太常用,但离线,结合并查集的思想值得借鉴

#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
int ver[2*N],nxt[2*N],edge[2*N],head[N];
int fa[N],d[N],v[N],lca[N],ans[N];
vector<int> query[N],query_id[N];
int T,n,m,tot,t;
void add(int x,int y,int z){
ver[++tot] = y;edge[tot] = z;nxt[tot] = head[x];head[x] = tot;
}
void add_query(int x,int y,int id){
query[x].push_back(y);query_id[x].push_back(id);
query[y].push_back(x);query_id[y].push_back(id);
}
int find(int x){
return x==fa[x]?x:fa[x] = find(fa[x]);
}
void tarjan(int x){
v[x] = 1;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(v[y])continue;
d[y] = d[x] + edge[i];
tarjan(y);
fa[y] = x;
}
for(int i=0;i<query[x].size();i++){
int y = query[x][i],id = query_id[x][i];
if(v[y] == 2){
int lca = find(y);
ans[id] = min(ans[id],d[x] + d[y] - 2*d[lca]);
}
}
v[x] = 2;
}
int main(){
cin>>T;
while(T--){
cin>>n>>m;
for(int i=1;i<=n;i++){
head[i] = 0;fa[i] = i;v[i] = 0;
query[i].clear();query_id[i].clear();
}
tot = 0;
for(int i=1;i<n;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);add(y,x,z);
}
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
if(x == y)ans[i] = 0;
else{
add_query(x,y,i);
ans[i] = 1 << 30;
}
}
tarjan(1);
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
}
return 0;
}

无向图连通性

求割边

无向边 \((x,y)\) 是桥,当且仅当搜索树上存在 \(x\) 的一个子节点 \(y\) ,满足:

\[dfn[x] < low[y]
\]
void tarjan(int x, int in_edge){
dfn[x] = low[x] = ++num;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(!dfn[y]){
tarjan(y,i);
low[x] = min(low[x], low[y]);
if(low[y] > dfn[x])
bridge[i] = bridge[i ^ 1] = true; //标记为割边
} else if (i != (in_edge ^ 1)) {
low[x] = min(low[x], dfn[y]);
}
}
}
//main函数中
//加边要保证第一条边编号为2
for(int i=1;i<=n;i++)
if(!dfn[i])tarjan(i, 0);

割点

若 \(x\) 不是搜索树的根节点(\(dfs\)) ,则 \(x\) 是割点当且仅当搜索树上存在 \(x\) 的一个子节点\(y\) 满足:$$dfn[x] \le low[y]$$

void tarjan(int x){
dfn[x] = low[x] = ++num;
int flag = 0;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(!dfn[y]){
tarjan(y);
low[x] = min(low[x], low[y]);
if(low[y] >= dfn[x]){
flag ++;
if(x != root || flag >= 2) cut[x] = true;
}
} else low[x] = min(low[x], dfn[y]);
}
}
for(int i=1;i<=n;i++)
if(!dfn[i])tarjan(i);

边双连通分量 (e-DCC)

只需要求出无向图的所有的桥,把桥都删除后,无向图会分成若干个连通块,每个连通块就是一个“边联通分量”。

int c[N], dcc;
void dfs(int x){
c[x] = dcc;// x所属的连通块编号
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(c[y] || bridge[i])continue; // 不访问桥
dfs(y);
}
}
//main函数中
for(int i=1;i<=n;i++)if(!c[i]){
++dcc;
dfs(i);
}

e-DCC 的缩点

把每个\(e-DCC\) 看作一个节点,把桥\((x,y)\) 看作连接编号为\(c[x]\) 和 \(c[y]\) 的\(e-DCC\) 对应节点的无向边,会产生一棵树(若原来的图不连通,则产生森林)。

int hc[N], vc[N*2], nc[N*2], tc;
void add_c(int x,int y){
vc[++tc] = y, nc[tc] = hc[x], hc[x] = tc;
}
//main函数中
tc = 1;
for(int i=2;i<=tot;i++){
int x = ver[i ^ 1], y = ver[i];
if(c[x] == c[y])continue;
add_c(c[x],c[y]);
}

点双连通分量(v-DCC) 求法

点双连通分量并不是指“删除割点后图中剩余的连通块”

割点可能属于多个\(v-DCC\)。

void tarjan(int x){
dfn[x] = low[x] = ++num;
st[++top] = x;
if(x == root && head[x] == 0){ // 孤立点特判
dcc[++cnt].push_back(x);
return ;
}
int flag = 0;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(!dfn[y]){
tarjan(y);
low[x] = min(low[x], low[y]);
if(low[y] >= dfn[x]){ //割点成立条件满足
flag ++;
if(x != root || flag > 1)cut[x] = true;
cnt++;
int z;
do{
z = st[top--];
dcc[cnt].push_back(z);
}while(z != y);//栈中直到 y 都出栈
dcc[cnt].push_back(x);
}
}else low[x] = min(low[x], dfn[y]);
}
}

v-DCC 的缩点

设图中共有 \(p\) 个割点和 \(t\) 个\(v-DCC\)。我们建立一张包含\(p+t\) 个节点的新图,把每个\(v-DCC\) 和每个割点作为新图中的节点,并在每个割点与包含它的所有\(v-DCC\) 之间连边。容易发现,这张新图其实是一颗树(或森林)

//给每个割点一个新的编号(编号从cnt+1开始)
num = cnt;
for(int i=1;i<=n;i++)
if(cut[i])new_id[i] = ++num;
tc = 1;
for(int i=1;i<=cnt;i++){
for(int j=0;j<dcc[i].size();j++){
int x = dcc[i][j];
if(cut[x]){
add_c(i,new_id[x]);
add_c(new_id[x],i);
}else c[x] = i; // 除割点外,其他点仅属于1个v-DCC
}
}

有向图连通性

求强连通分量

const int N = 100010, M = 1000010;
int head[N], ver[M], nxt[M], dfn[N], low[N];
int st[N], ins[N], c[N];
vector<int> scc[N];
int n, m, tot, num, top, cnt;
void add(int x, int y){
ver[++tot] = y, nxt[tot] = head[x], head[x] = cnt;
}
void tarjan(int x){
dfn[x] = low[x] = ++num;
st[++top] = x, ins[x] = 1;
for(int i=head[x];i;i=nxt[i]){
if(!dfn[ver[i]]){
tarjan(ver[i]);
low[x] = min(low[x], low[ver[i]]);
}else if(ins[ver[i]]) //有作用的横叉边以及返祖边
low[x] = min(low[x], dfn[ver[i]]);
}
if(dfn[x] == low[x]){//x可以作为这个连通分量的入口
cnt ++;int y;
do{
y = st[top--], ins[y] = 0;
c[y] = cnt, scc[cnt].push_back(y);
}while(x != y);
}
}

缩点

for(int x=1;x<=n;x++){
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(c[x] == c[y])continue;
add_c(c[x],c[y]);
}
}

欧拉路

void enlur(){
st[++top] = 1;
while(top > 0){
int x = st[top] , i = head[x];
while(i && vis[i]) i = nxt[i];
if(i){
st[++top] = ver[i];
vis[i] = vis[i ^ 1] = 1;
head[x] = nxt[i]; // 会修改head[x] 的值
}else{
top --;
ans[++t] = x;
}
}
}

二分图

1. 二分图判定

染色法

int n;
int head[N],edge[M],nxt[M],tot;
int color[N];
bool dfs(int u,int fa,int c){
co[u] = c;
for(int i=head[u];i;i=nxt[i]){
int j = edge[i];
if(co[j] == -1)
if(!dfs(j,u,!c))return false;
else if(co[j] == c)return false;
}
return true;
}
bool check(){
memset(co,-1,sizeof co);
bool flag = true;
for(int i=1;i<=n;i++){
if(co[i] == -1){
if(!dfs(i,-1,0)){
flag = false;
break;
}
}
}
return flag;
}

2. 二分图最大匹配

匈牙利算法 复杂度 \(O(NM)\)

bool dfs(int x){
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(!vis[y]){
vis[y] = 1;
if(!match[y] || dfs(match[y])){
match[y] = x; return true;
}
}
}
return false;
}
for(int i=1;i<=n;i++){
memset(vis, 0, sizeof vis);
if(dfs(i)) res++;
}

3. 二分图带权最大匹配

KM \(O(n^3)\)

2019南京 J spy

ll n, a[N],b[N],c[N],p[N];
ll w[N][N];
ll lx[N] , ly[N];
ll match[N];
ll slack[N];
bool vy[N];
ll pre[N];
void bfs( ll k ){
ll x , y = 0 , yy = 0 , delta;
memset( pre , 0 , sizeof(pre) );
for( ll i = 1 ; i <= n ; i++ ) slack[i] = inf;
match[y] = k;
while(1){
x = match[y]; delta = inf; vy[y] = true;
for( ll i = 1 ; i <= n ;i++ ){
if( !vy[i] ){
if( slack[i] > lx[x] + ly[i] - w[x][i] ){
slack[i] = lx[x] + ly[i] - w[x][i];
pre[i] = y;
}
if( slack[i] < delta ) delta = slack[i] , yy = i ;
}
}
for( ll i = 0 ; i <= n ; i++ ){
if( vy[i] ) lx[match[i]] -= delta , ly[i] += delta;
else slack[i] -= delta;
}
y = yy ;
if( match[y] == -1 ) break;
}
while( y ) match[y] = match[pre[y]] , y = pre[y];
} ll KM(){
memset( lx , 0 ,sizeof(lx) );
memset( ly , 0 ,sizeof(ly) );
memset( match , -1, sizeof(match) );
for( ll i = 1 ; i <= n ; i++ ){
memset( vy , false , sizeof(vy) );
bfs(i);
}
ll res = 0 ;
for( ll i = 1 ; i <= n ; i++ ){
if( match[i] != -1 ){
res += w[match[i]][i] ;
}
}
return res;
} int main()
{
scanf("%lld",&n);
for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
for(ll i=1;i<=n;i++) scanf("%lld",&p[i]);
for(ll i=1;i<=n;i++) scanf("%lld",&b[i]);
for(ll i=1;i<=n;i++) scanf("%lld",&c[i]);
for(ll i=1;i<=n;i++){
for(ll j=1;j<=n;j++){
ll s=0;
for(ll k=1;k<=n;k++){
if(b[i]+c[j]>a[k]) s+=p[k];
}
w[i][j]=s;
}
}
printf("%lld\n",KM());
return 0;
}

虚树

P2495

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 250000 + 5;
const int M = 2*N;
int n, q, k, a[N], mark[N];
int head[N], ver[M], edge[M], nxt[M], tot;
int dfn[N], cid[N], cnt;
int dep[N], f[N][20], minval[N][20];
int st[N], top;
ll d[N];
struct Graph{
int head[N], ver[M], edge[M], nxt[M], tot;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
}
}G;
void add(int x, int y, int z){
ver[++tot] = y, nxt[tot] = head[x], edge[tot] = z, head[x] = tot;
}
void dfs(int x,int fa){
dfn[x] = ++cnt, cid[cnt] = x;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(y == fa)continue;
f[y][0] = x;
dep[y] = dep[x] + 1;
minval[y][0] = edge[i];
dfs(y, x);
}
}
int MinVal;
int lca(int x, int y){
MinVal = inf;
if(dep[x] > dep[y]) swap(x, y);
for(int i=19;i>=0;i--){
if(dep[f[y][i]] >= dep[x]){
MinVal = min(MinVal, minval[y][i]);
y = f[y][i];
}
}
if(x == y)return x;
for(int i=19;i>=0;i--){
if(f[y][i] != f[x][i]){
MinVal = min(MinVal, min(minval[x][i], minval[y][i]));
y = f[y][i], x = f[x][i];
}
}
return f[x][0];
}
void insert(int x){
if(x == 1) return;
int t = lca(x, st[top]);
if(t != st[top]){
while(top > 1 && dfn[st[top-1]] > dfn[t]){
lca(st[top-1], st[top]);
G.add(st[top-1], st[top], MinVal);
top --;
}
if(dfn[t] > dfn[st[top-1]]){ // t 不在栈中
G.head[t] = 0; lca(t, st[top]);
G.add(t, st[top], MinVal); st[top] = t;
} else { // 说明 t 已经在栈中
lca(t,st[top]);
G.add(t, st[top--], MinVal);
}
}
G.head[x] = 0, st[++top] = x;
}
bool cmp(int x, int y){return dfn[x] < dfn[y];}
void build(){
sort(a + 1, a + 1 + k, [=](const int a, const int b)->bool{return dfn[a] < dfn[b];});
st[top=1] = 1; G.tot = 0, G.head[1] = 0;
for(int i=1,l;i<=k;i++) insert(a[i]);
for(int i=1;i<top;i++){
lca(st[i], st[i+1]); G.add(st[i], st[i+1], MinVal);
}
}
void dfs(int x){
d[x] = 0;
for(int i=G.head[x];i;i=G.nxt[i]){
int y = G.ver[i];
//cout << x << ' ' << y << ' ' << G.edge[i] << endl;
dfs(y);
if(mark[y]) d[x] += G.edge[i];
else d[x] += min(1ll*G.edge[i], d[y]);
}
} int main() {
memset(minval, 0x3f, sizeof minval);
scanf("%d",&n);
for(int i=1,x,y,z;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
dep[1] = 1;
dfs(1, 0);
for(int j=1;j<20;j++)
for(int i=1;i<=n;i++)
f[i][j] = f[f[i][j-1]][j-1], minval[i][j] = min(minval[i][j-1], minval[f[i][j-1]][j-1]);
scanf("%d",&q);
while(q--){
scanf("%d",&k);
for(int i=1;i<=k;i++)scanf("%d",&a[i]), mark[a[i]] = 1;
build();
dfs(1);
printf("%lld\n", d[1]);
for(int i=1;i<=k;i++) mark[a[i]] = 0;
}
return 0;
}

网络流

一定要注意边序号初始为1

最大流

1. \(EK\)

\(O(nm^2)\) 一般可以处理\(10^3\sim 10^4\) 规模的网络

const int inf = 1 << 29, N = 2010, M = 20010;
int head[N],ver[M],edge[M],nxt[M],v[N],incf[N],pre[N];
int n,m,s,t,tot,maxflow;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
bool bfs(){
memset(v,0,sizeof v);
queue<int> q;
q.push(s); v[s] = 1;
incf[s] = inf;
while(q.size()){
int x = q.front(); q.pop();
for(int i=head[x];i;i=nxt[i]){
if(edge[i]){
int y = ver[i];
if(v[y])continue;
incf[y] = min(incf[x], edge[i]);
pre[y] = i;
q.push(y), v[y] = 1;
if(y == t) return 1;
}
}
}
return 0;
}
void update(){
int x = t;
while(x != s){
int i = pre[x];
edge[i] -= incf[t];
edge[i ^ 1] += incf[t];
x = ver[i ^ 1];
}
maxflow += incf[t];
}
int main(){
while(cin >> n >> m){
memset(head,0,sizeof head);
s = 1, t = n, tot = 1, maxflow = 0;
for(int i=1;i<=m;i++){
int x,y,z;scanf("%d%d%d",&x,&y,&z);
add(x,y,z);add(y,x,z);
}
while(bfs()) update();
cout << maxflow << endl;
}
}
2. Dinic

\(O(n^2m)\) 该算法求解二分图最大匹配的时间复杂度为\(O(m\sqrt{n})\)

const int inf = 1<<29, N = 50010,M=30010;
int head[N], ver[M], edge[M], nxt[M], d[N];
int n, m, s, t, tot, maxflow;
queue<int> q;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
bool bfs(){
memset(d, 0, sizeof d);
while(q.size())q.pop();
q.push(s);d[s] = 1;
while(q.size()){
int x = q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
if(edge[i] && !d[ver[i]]){
q.push(ver[i]);
d[ver[i]] = d[x] + 1;
if(ver[i] == t) return 1;
}
}
}
return 0;
}
int dinic(int x, int flow){
if(x == t) return flow;
int rest = flow, k;
for(int i=head[x];i && rest; i=nxt[i]){
if(edge[i] && d[ver[i]] == d[x] + 1){
k = dinic(ver[i], min(rest, edge[i]));
if(!k) d[ver[i]] = 0;
edge[i] -= k;
edge[i ^ 1] += k;
rest -= k;
}
}
return flow - rest;
}
int main(){
cin >> n >> m;
cin >> s >> t;
tot = 1;
for(int i=1;i<=m;i++){
int x, y, c;scanf("%d%d%d",&x,&y,&c);
add(x,y,c);
}
int flow = 0;
while(bfs())
while(flow = dinic(s,inf)) maxflow += flow;
cout << maxflow << endl;
}

费用流

const int N = 5000 + 5;
const int M = 100010;
int head[N], ver[M], nxt[M], cost[M], edge[M];
int d[N], v[N], pre[N], incf[N];
int n, m, s, t, tot;
int maxflow, ans;
void add(int x, int y, int z, int c){
ver[++tot] = y, nxt[tot] = head[x], edge[tot] = z, cost[tot] = c, head[x] = tot;
ver[++tot] = x, nxt[tot] = head[y], edge[tot] = 0, cost[tot] = -c, head[y] = tot;
}
bool spfa(){
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
d[s] = 0; v[s] = 1;
incf[s] = inf;
queue<int> q;
q.push(s);
while(q.size()){
int x = q.front();q.pop();
v[x] = 0;//注意这里
for(int i=head[x];i;i=nxt[i])if(edge[i]){
int y = ver[i];
if(d[y] > d[x] + cost[i]){
d[y] = d[x] + cost[i];
incf[y] = min(incf[x], edge[i]);
pre[y] = i;
if(!v[y]) v[y] = 1, q.push(y);
}
}
}
return d[t] != inf;
}
void update(){
int x = t;
while(x != s){
int i = pre[x];
edge[i] -= incf[t];
edge[i^1] += incf[t];
x = ver[i^1];
}
maxflow += incf[t];
ans += incf[t] * d[t];
}
int main(){
tot = 1;//注意这里
scanf("%d%d%d%d", &n, &m, &s, &t);
for(int i=1;i<=m;i++){
int x, y, z, c;scanf("%d%d%d%d", &x, &y, &z, &c);
add(x, y, z, c);
}
while(spfa()) update();
printf("%d %d\n", maxflow, ans); return 0;
}

灭绝树

DAG

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
const int N = 200010;
//E:原图,G反图,T支配树
//dep:T中的深度,deg:反向图中的入度
vector<int> E[N],G[N],T[N];
int n,m,deg[N],rt,a[N],dep[N],val[N];
int f[N][20],tot;
void BFS(){
queue<int> q;
rt = n+1;//超级源点.....nmb
for(int i=1;i<=n;i++)if(!deg[i]){q.push(i);E[rt].pb(i);G[i].pb(rt);}
int tot = 0;
while(!q.empty()){
int u = q.front();q.pop();
a[++tot] = u;
for(int v : E[u])if((--deg[v]) == 0)q.push(v);
}
}
int LCA(int x,int y){
if(dep[x] > dep[y])swap(x,y);
for(int i=19;i>=0;i--)if(dep[y] > dep[x] && dep[f[y][i]] >= dep[x])y = f[y][i];
if(x == y)return x;
for(int i=19;i>=0;i--)if(f[x][i] != f[y][i]) x = f[x][i],y=f[y][i];
return f[x][0];
}
int main(){
int tt;scanf("%d",&tt);
while(tt--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n+1;i++){
E[i].clear();G[i].clear();T[i].clear();
dep[i] = deg[i] = 0;
}
for(int i=1,u,v;i<=m;i++){
scanf("%d%d",&u,&v);
E[v].pb(u);G[u].pb(v);deg[u] ++;
}
BFS();
dep[rt] = 1;
for(int i=1;i<=n;i++){
int u = a[i],fa = -1;
for(int v:G[u])fa = (fa == -1 ?v:LCA(fa,v));
dep[u] = dep[fa]+1;
f[u][0] = fa;T[fa].pb(u);
for(int i=1;i<=19;i++)f[u][i] = f[f[u][i-1]][i-1];
}
int q;scanf("%d",&q);
while(q--){
int u,v;scanf("%d%d",&u,&v);
int lca = LCA(u,v);
printf("%d\n",dep[u] + dep[v] - dep[lca] - 1);
}
}
}

有向图+DAG

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5+10;
struct Map{
int head[N],ver[N<<1],nxt[N<<1],cnt;
void reset(){cnt = 0;memset(head,0,sizeof head);}
void link(int x,int y){ver[++cnt]=y;nxt[cnt]=head[x];head[x]=cnt;}
}E,G,T,TR,D;
int deg[N],dep[N],dfn[N],id[N],fa[N],f[N][20],semi[N],mm[N],tot,anc[N],ans[N],top[N],cnt,n,m;
void dfs(int x){
dfn[x] = ++tot;id[tot] = x;
for(int i=E.head[x];i;i=E.nxt[i]){
int y = E.ver[i];
if(dfn[y])continue;
dfs(y);T.link(x,y);
//2
anc[y] = x;
}
}
int find(int x){
if(x == fa[x])return x;
int ff = fa[x];fa[x] = find(fa[x]);
if(dfn[semi[mm[ff]]] < dfn[semi[mm[x]]])mm[x] = mm[ff];
return fa[x];
}
int LCA(int x,int y){
if(dep[x] > dep[y])swap(x,y);
for(int i=18;i>=0;i--)if(dep[x] < dep[y] && dep[f[y][i]] >= dep[x])y=f[y][i];
if(x == y)return x;
for(int i=18;i>=0;i--)if(f[x][i] != f[y][i])x=f[x][i],y=f[y][i];
return f[x][0];
}
int getAns(int x){
ans[x] = 1;
for(int i=D.head[x];i;i=D.nxt[i]){
int y = D.ver[i];
getAns(y),ans[x] += ans[y];
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
semi[i] = mm[i] = fa[i] = i;
}
for(int i=1,u,v;i<=m;i++){
scanf("%d%d",&u,&v);
E.link(u,v);G.link(v,u);
}
dfs(1);anc[1] = 0;
for(int i=n;i>=2;i--){
int x = id[i],res = n;
if(!x)continue;
for(int i=G.head[x];i;i=G.nxt[i]){
int y = G.ver[i];
if(!dfn[y])continue;
if(dfn[y] < dfn[x])res = min(res,dfn[y]);
else{
//1.
find(y);res = min(res,dfn[semi[mm[y]]]);
}
}
semi[x] = id[res];fa[x] = anc[x];
T.link(semi[x],x);
}
for(int x=1;x<=n;x++)
for(int i=T.head[x];i;i=T.nxt[i]){
int y = T.ver[i];
TR.link(y,x);deg[y]++;
}
queue<int> q;
for(int i=1;i<=n;i++)if(deg[i]==0)q.push(i);
while(!q.empty()){
int x = q.front();q.pop();
top[++cnt] = x;
for(int i=T.head[x];i;i=T.nxt[i]){
int y = T.ver[i];
if((--deg[y]) == 0)q.push(y);
}
}
for(int i=1;i<=cnt;i++){
int x = top[i],bb = -1;
for(int j=TR.head[x];j;j=TR.nxt[j]){
int y = TR.ver[j];
bb = bb==-1?y:LCA(y,bb);
}
f[x][0] = bb;D.link(bb,x);dep[x] = dep[bb]+1;
for(int j=1;j<=18;j++)f[x][j] = f[f[x][j-1]][j-1];
}
getAns(1);
for(int i=1;i<=n;i++)printf("%d ",ans[i]);
return 0;
}

有向图

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5+10;
struct Map{
int head[N],ver[N<<1],nxt[N<<1],cnt;
void reset(){cnt = 0;memset(head,0,sizeof head);}
void link(int x,int y){ver[++cnt]=y;nxt[cnt]=head[x];head[x]=cnt;}
}E,G,T,D;
//E原图,G反图,T为DAG
int dfn[N],id[N],fa[N],f[N][20],semi[N],mm[N],tot,anc[N],ans[N],top[N],cnt,n,m,idom[N];
void dfs(int x){
dfn[x] = ++tot;id[tot] = x;
for(int i=E.head[x];i;i=E.nxt[i]){
int y = E.ver[i];
if(dfn[y])continue;
dfs(y);
anc[y] = x;
}
}
int find(int x){
if(x == fa[x])return x;
int ff = fa[x];fa[x] = find(fa[x]);
if(dfn[semi[mm[ff]]] < dfn[semi[mm[x]]])mm[x] = mm[ff];
return fa[x];
}
int getAns(int x){
ans[x] = 1;
for(int i=D.head[x];i;i=D.nxt[i]){
int y = D.ver[i];
getAns(y),ans[x] += ans[y];
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
semi[i] = mm[i] = fa[i] = i;
}
for(int i=1,u,v;i<=m;i++){
scanf("%d%d",&u,&v);
E.link(u,v);G.link(v,u);
}
dfs(1);anc[1] = 0;
for(int i=n;i>=2;i--){//dfs序倒着来
int x = id[i],res = n;
if(!x)continue;
for(int i=G.head[x];i;i=G.nxt[i]){
int y = G.ver[i];
if(!dfn[y])continue;
if(dfn[y] < dfn[x])res = min(res,dfn[y]);
else{
find(y);
res = min(res,dfn[semi[mm[y]]]);
}
}
semi[x] = id[res];fa[x] = anc[x];
T.link(semi[x],x);
x = anc[x];
for(int j=T.head[x];j;j=T.nxt[j]){
int y = T.ver[j];
find(y);
if(semi[mm[y]] == x)idom[y] = x;
else idom[y] = mm[y];
}
}
for(int i=2,now;i<=tot;i++){
now = id[i];
if(idom[now] != semi[now])idom[now] = idom[idom[now]];
}
for(int i=2;i<=n;i++)if(idom[i])D.link(idom[i],i);
getAns(1);
for(int i=1;i<=n;i++)printf("%d ",ans[i]);
return 0;
}

五. 技巧

调试

#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }

快读

inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x<<1) + (x<<3) + (ch^48);ch=getchar();}
return x * f;
}

快写

void put(int x){
int num = 0; char c[15];
while(x) c[++num] = (x % 10) + 48, x /= 10;
while(num) putchar(c[num--]);
putchar('\n');
}

枚举子集

S是一个二进制数,表示一个集合,可以用S0=S(初始),S0=(S0-1)&S(下一个)这种方法枚举遍S的所有子集。

注意到这种枚举方法是二进制数值上从大到小枚举子集的。用归纳法证明合理性,假设枚举到某个S0,大于它的所有S子集都枚举过了,下一个枚举S1=(S0-1)&S,需要证明S1是S0紧挨着的下一个子集,才能保证枚举不漏,也就是要证明区间(S1,S0)里不存在S的其他子集。

设S0以k(k=0,1,2…)个0结尾,S0=xxxx10...0,则S0-1=xxxx01...1,S1=(S0-1)&S,易见S1是以xxxx0开头的最大子集,而S0是以xxxx1开头的最小子集,如果存在某子集\(S1<Sx<S0\),Sx要么以xxxx0开头,要么以xxxx1开头,无论哪种情况,都会出现矛盾。

复杂度\(O(3^n)\)

\(3^15=14348907,3^14=4782969\)

for (int S=1; S<(1<<n); ++S){
for (int S0=S; S0; S0=(S0-1)&S)
//do something.

如果不包含自身

for (int S=1; S<(1<<n); ++S){
for (int S0=(S-1)&S; S0; S0=(S0-1)&S)
//do something.
}

大数

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct BigInteger{
//BASE为vector数组中一位中最大存储的数字,前面都是以10计算的
//WIDTH为宽度
static const int BASE = 100000000;
static const int WIDTH = 8;
vector<int> s;
//正数为1,负数为-1
int flag = 1; //构造函数
BigInteger(ll num = 0){*this = num;}
BigInteger(string str){*this = str;}
BigInteger(const BigInteger& t){
this->flag = t.flag;
this->s = t.s;
}
//赋值函数
BigInteger operator = (ll num){
s.clear();
do {
s.push_back(num % BASE);
num /= BASE;
}while(num > 0);
return *this;
}
BigInteger operator = (string &str){
s.clear();
int x,len = (str.length()-1)/WIDTH + 1;
for(int i=0;i<len;i++){
int end = str.length() - i*WIDTH;
int start = max(0,end - WIDTH);
sscanf(str.substr(start,end-start).c_str(),"%d",&x);
s.push_back(x);
}
return *this;
} //基本比较函数 A < B
bool cmp( vector<int> &A, vector<int> & B){
if(A.size() != B.size())return A.size() < B.size();
for(int i=A.size()-1;i>=0;i--){
if(A[i] != B[i]){
return A[i] < B[i];
}
}
return false;
}
//比较函数如果小于则返回真
bool operator < ( BigInteger & b){
return cmp(s,b.s);
}
bool operator > ( BigInteger& b){
return b < *this;
}
bool operator <= ( BigInteger &b){
return !(b < *this);
}
bool operator >= ( BigInteger &b){
return !(*this < b);
}
bool operator == ( BigInteger &b){
return !(b < *this) && (*this < b);
}
//基本四则运算
vector<int> add(vector<int> &A, vector<int> &B);
vector<int> sub(vector<int> &A, vector<int> &B);
vector<int> mul(vector<int> &A, int b);
vector<int> mul(vector<int> &A, vector<int> &B);
vector<int> div(vector<int> &A, int b);
vector<int> div(vector<int> A, vector<int> B); //重载运算符
BigInteger operator + (BigInteger &b);
BigInteger operator - (BigInteger &b);
BigInteger operator * (BigInteger &b);
BigInteger operator * (int& b);
BigInteger operator / (BigInteger & b);
BigInteger operator / (int b);
};
//重载<<
ostream& operator << (ostream &out,const BigInteger& x) {
if(x.flag == -1)out << '-';
out << x.s.back();
for(int i = x.s.size() - 2; i >= 0;i--){
char buf[20];
sprintf(buf,"%08d",x.s[i]);//08d此处的8应该与WIDTH一致
for(int j=0;j<strlen(buf);j++)out<<buf[j];
}
return out;
}
//重载输入
istream& operator >> (istream & in,BigInteger & x){
string s;
if(!(in>>s))return in;
x = s;
return in;
}
vector<int> BigInteger::add( vector<int> &A, vector<int> &B){
if(A.size() < B.size())return add(B,A);
int t = 0;
vector<int> C;
for(int i=0;i<A.size();i++){
if(i<B.size())t += B[i];
t += A[i];
C.push_back(t%BASE);
t /= BASE;
}
if(t)C.push_back(t);
while(C.size() > 1 && C.back() == 0)C.pop_back();
return C;
}
vector<int> BigInteger::sub( vector<int> &A, vector<int> &B){
vector<int> C;
for(int i=0,t=0;i<A.size();i++){
t = A[i] - t;
if(i<B.size())t -= B[i];
C.push_back((t+BASE)%BASE);
if(t < 0)t = 1;
else t = 0;
}
while(C.size() > 1 && C.back() == 0)C.pop_back();
return C;
}
vector<int> BigInteger::mul(vector<int> &A,int b){
vector<int> C;
int t = 0;
for(int i = 0;i < A.size() || t; i++){
if(i < A.size()) t += A[i] * b;
C.push_back(t%BASE);
t /= BASE;
}
return C;
}
//大数乘大数乘法需要将BASE设置为10,WIDTH设置为1
vector<int> BigInteger::mul( vector<int> &A, vector<int> &B) {
int la = A.size(),lb = B.size();
vector<int> C(la+lb+10,0);
for(int i=0;i<la;i++){
for(int j=0;j<lb;j++){
C[i+j] += A[i] * B[j];
}
}
for(int i=0;i<C.size();i++){
if(C[i] >= BASE){
C[i + 1] += C[i] / BASE;
C[i] %= BASE;
}
}
while(C.size() > 1 && C.back() == 0)C.pop_back();
return C;
}
//大数除以整数
vector<int> BigInteger::div(vector<int> & A,int b){
vector<int> C;
int r = 0;
for(int i = A.size() - 1;i >= 0;i--){
r = r * 10 + A[i];
C.push_back(r/b);
r %= b;
}
reverse(C.begin(),C.end());
while(C.size() > 1 && C.back() == 0)C.pop_back();
return C;
}
//大数除以大数
vector<int> BigInteger::div(vector<int> A,vector<int> B){
int la = A.size(),lb = B.size();
int dv = la - lb; // 相差位数
vector<int> C(dv+1,0);
//将除数扩大,使得除数和被除数位数相等
reverse(B.begin(),B.end());
for(int i=0;i<dv;i++)B.push_back(0);
reverse(B.begin(),B.end());
lb = la;
for(int j=0;j<=dv;j++){
while(!cmp(A,B)){
A = sub(A,B);
C[dv-j]++;
}
B.erase(B.begin());
}
while(C.size()>1 && C.back() == 0)C.pop_back();
return C;
}
BigInteger BigInteger::operator + ( BigInteger & b){
BigInteger c;
c.s.clear();
c.s = add(s,b.s);
return c;
} BigInteger BigInteger::operator - ( BigInteger & b) {
BigInteger c;
c.s.clear();
if(*this < b){
c.flag = -1;
c.s = sub(b.s,s);
}
else{
c.flag = 1;
c.s = sub(s,b.s);
}
return c;
}
BigInteger BigInteger::operator *(BigInteger & b){
BigInteger c;
c.s = mul(s,b.s);
return c;
}
BigInteger BigInteger::operator *(int& b){
BigInteger c;
c.s = mul(s,b);
return c;
}
BigInteger BigInteger::operator /(BigInteger & b){
BigInteger c;
if(*this < b){
c.s.push_back(0);
}
else{
c.flag = 1;
c.s = div(s,b.s);
}
return c;
}
BigInteger BigInteger::operator /(int b){
BigInteger c;
BigInteger t = b;
if(*this < t){
c.s.push_back(0);
}
else{
c.flag = 1;
c.s = div(s,b);
}
return c;
}
int main(){
BigInteger A,B;
cin>>A>>B;
cout<<A+B<<endl;
cout<<A-B<<endl;
cout<<A*B<<endl;
cout<<A/B<<endl;
return 0;
}

提交前必看!

  1. 有没有爆int,有没有爆longlong?
  2. 有没有把数组错开成插入?
  3. 有没有把调试信息删除干净?

ACM 模板库的更多相关文章

  1. 实验8 标准模板库STL

    一.实验目的与要求: 了解标准模板库STL中的容器.迭代器.函数对象和算法等基本概念. 掌握STL,并能应用STL解决实际问题. 二.实验过程: 完成实验8标准模板库STL中练习题,见:http:// ...

  2. [工具使用]-利用latex管理创建自己的ACM模板

    从很早入坑ACM开始,便和各种算法的模板打着交道,虽然kaungbin的模板已经足够强大,但是自己在平常做题中也逐渐有着自己的一些模板,也有一些kuangbin模板中没有的更快的板子,虽然不确定时候以 ...

  3. STL标准模板库(简介)

    标准模板库(STL,Standard Template Library)是C++标准库的重要组成部分,包含了诸多在计算机科学领域里所常见的基本数据结构和基本算法,为广大C++程序员提供了一个可扩展的应 ...

  4. c++转载系列 std::vector模板库用法介绍

    来源:http://blog.csdn.net/phoebin/article/details/3864590 介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作 ...

  5. Handlebars模板库浅析

    Handlebars模板库简单介绍 Handlebars是JavaScript一个语义模板库,通过对view(模板)和data(ajax请求的数据,一般是json)的分离来快速构建Web模板.它采用& ...

  6. 【转】C++标准库和标准模板库

    C++强大的功能来源于其丰富的类库及库函数资源.C++标准库的内容总共在50个标准头文件中定义.在C++开发中,要尽可能地利用标准库完成.这样做的直接好处包括:(1)成本:已经作为标准提供,何苦再花费 ...

  7. STL标准模板库介绍

    1. STL介绍 标准模板库STL是当今每个从事C++编程的人需要掌握的技术,所有很有必要总结下 本文将介绍STL并探讨它的三个主要概念:容器.迭代器.算法. STL的最大特点就是: 数据结构和算法的 ...

  8. c++模板库(简介)

    目 录 STL 简介 ......................................................................................... ...

  9. 【c++】标准模板库STL入门简介与常见用法

    一.STL简介 1.什么是STL STL(Standard Template Library)标准模板库,主要由容器.迭代器.算法.函数对象.内存分配器和适配器六大部分组成.STL已是标准C++的一部 ...

随机推荐

  1. Command3

    压缩和解压 gzip gunzip .gz 指定文件必须带后缀 gzip file filename.gz zip unzip .zip unzip filename.zip directory zi ...

  2. docker进入mysql数据库并进行导入 导出

    一:导入 1.首先查看docker运行的容器: docker ps 2.将宿主机文件拷贝到docker容器中: docker cp 2020415.sql af491d5466ea:/opt/2020 ...

  3. .NET 云原生架构师训练营(模块二 基础巩固 RabbitMQ Masstransit 介绍)--学习笔记

    2.6.6 RabbitMQ -- Masstransit 介绍 Masstransit 是什么 Quickstart 消息 Message Masstransit 是什么 Masstransit 是 ...

  4. 如何使用 VS Code开发.NET Core应用程序

    Visual Studio Code(VS Code)是Microsoft为Windows,Linux和Mac操作系统开发的免费,跨平台,轻量级的源代码编辑器,它是源代码编辑器,而Visual Stu ...

  5. Centos 打开ssh 密码验证 和 root 登录

    # 1 打开系统的密码验证功能: vim /etc/ssh/sshd_config #允许使用密码登录(注释此行 就是允许证书登录) PasswordAuthentication yes # 2 打开 ...

  6. Kubernetes学习笔记之安装minikube并运行个简单应用程序

    前言:本笔记仅记录学习记录,可能存在错误!!!使用的环境是Ubuntu Desktop 20.04,也有用Windows 10 操作的,根据的文档是minikube的文档教程,链接:https://m ...

  7. 数据库MySQL(带你零基础入门MySQL)

    (一)认识数据库 redis默认端口:6379 mysql默认端口:3306 什么是数据库? 数据库的英文单词:data base,简称DB. 数据库实际上就是一个文件集合,是一个存储数据的仓库,本质 ...

  8. C# datagridview设置标题为汉语

    正常情况下,在给datagridview绑定数据源之后,显示的是SQL语句中的栏位,如下 我们想让标题显示汉语,可以有一下两种方法 1.在SQL中设置列别名 SELECT TITLE AS '报警标题 ...

  9. Linux内核分析_课程学习总结报告

    请您根据本课程所学内容总结梳理出一个精简的Linux系统概念模型,最大程度统摄整顿本课程及相关的知识信息,模型应该是逻辑上可以运转的.自洽的,并举例某一两个具体例子(比如读写文件.分配内存.使用I/O ...

  10. 什么是Etcd,如何运维Etcd ?

    介绍 ETCD 是一个分布式.可靠的 key-value 存储的分布式系统,用于存储分布式系统中的关键数据:当然,它不仅仅用于存储,还提供配置共享及服务发现:基于Go语言实现. ETCD的特点 简单: ...