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 str2
若 str1
字典序小返回负值,一样返回 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)
:若 str2
是 str1
的子串,则返回 str2
在 str1
的首次出现的地址;如果 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];
}
}
}
数论
矩阵快速幂
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;
}
数据结构
线段树合并
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;
}
图论
最短路
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')\) 使得:
- \(S\subseteq V'\)
- \(G′\) 为连通图;
- \(E′\) 中所有边的权值和最小。
你只需要求出 \(E′\)中所有边的权值和。
\(dp[i][s]\) 表示 以 i 为根的一棵树,包含集合 S 中所有点的最小代价
- \(w[i][j] + dp[j][s] -> dp[i][s]\)
- \(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的路径
#include <stdio.h>
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <string>
#include <cstring>
#include <algorithm>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <ctime>
#include <vector>
#include <fstream>
#include <list>
#include <iomanip>
#include <numeric>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define ms(s) memset(s, 0, sizeof(s))
const int inf = 0x3f3f3f3f;
const int N = 10010,M = 20010;
int head[N],ver[M],nxt[M],tot;
int del[N],sz[N],st[N], q[N], top;
int n,m, core, min_height, cnt, all;
int query[N];
int dis[N],k,edge[M];
bool has[10000010], ret[N];
void add(int x,int y,int z){
ver[++tot] = y;edge[tot] = z;nxt[tot]=head[x];head[x] = tot;
}
void getsz(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;
getsz(y, x);
mx = max(mx, sz[y]);
sz[x] += sz[y];
}
mx = max(mx, all - sz[x]);
if(mx < min_height) min_height = mx, core = x;
}
void getdis(int x, int fa){
if(dis[x]<=10000000) //没用的就不要存到里面
st[++cnt] = dis[x],q[++top] = dis[x];
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(y == fa || del[y])continue;
dis[y] = dis[x] + edge[i];
getdis(y, x);
}
}
void get(int x, int fa){
del[x] = 1;
top = cnt = 0;
has[0] = true;
q[++top] = 0; // 添加到历史集合中
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(y == fa || del[y])continue;
cnt = 0;
dis[y] = edge[i];
getdis(y, fa);
for(int k=1;k<=cnt;k++){
for(int j=1;j<=m;j++){
if(query[j] >= st[k]) ret[j] |= has[query[j] - st[k]];
}
}
for(int k=1;k<=cnt;k++) has[st[k]] = 1;
}
for(int i=1;i<=top;i++) has[q[i]] = false;
for(int i=head[x];i;i=nxt[i]){
int y = ver[i];
if(y == fa || del[y])continue;
min_height = inf;
core = 0;
all = sz[y];
getsz(y, x);
getsz(core, 0);
get(core, x);
}
}
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]);
core = 0;min_height = inf;
getsz(1, 0);
getsz(core, 0);
get(core, 0);
for(int i=1;i<=m;i++){
if(ret[i]) puts("AYE");
else puts("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\) ,满足:
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(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){
v[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = 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[y], 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;
}
费用流
最大费用最大流,用spfa找到最长路进行增广
const int inf = 0x3f3f3f3f;
const int N = 5000 + 5;
const int M = 200010;
int ver[M], edge[M], cost[M], nxt[M], head[N];
int d[N], incf[N], pre[N], v[N];
int n, k, tot, s, t, maxflow, ans;
void add(int x,int y,int z,int c){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
inline int num(int i, int j, int k) { return (i - 1) * n + j + k * n * n; }
bool spfa(){
queue<int> q;
memset(d, 0xcf, sizeof d);
memset(v, 0, sizeof v);
q.push(s);
d[s] = 0, v[s] = 1;
incf[s] = inf;
while(q.size()){
int x = q.front();
q.pop();
v[x] = 0;
for (int i = head[x]; i;i=nxt[i]){
if(!edge[i])
continue;
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);
}
}
}
if(d[t] == 0xcfcfcfcf)
return false;
return true;
}
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 += d[t] * incf[t];
}
int main()
{
cin >> n >> k;
s = 1, t = 2 * n * n;
tot = 1;
for (int i = 1; i <= n;i++){
for (int j = 1; j <= n;j++){
int c;
scanf("%d", &c);
add(num(i, j, 0), num(i, j, 1), 1, c);
add(num(i, j, 0), num(i, j, 1), k - 1, 0);
if(i < n)
add(num(i, j, 1), num(i + 1, j, 0), k, 0);
if(j < n)
add(num(i, j, 1), num(i, j + 1, 0), k, 0);
}
}
while(spfa()){
update();
}
cout << ans << endl;
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;
}
提交前必看!
- 有没有爆int,有没有爆longlong?
- 有没有把数组错开成插入?
- 有没有把调试信息删除干净?