const ll inf = (ll)1e16;
int n, m; bool vis[N]; ll dis[N];
vector <pii> g[N];
struct Node{
int id; ll d;
Node() {}
Node(int id, ll d):id(id),d(d){}
bool operator < (const Node &A) const { return d > A.d; }
};
void dijkstra(int st){
for(int i=1; i<=n; i++){
vis[i] = 0; dis[i] = inf;
}
dis[st] = 0;
priority_queue <Node> Q;
Q.push(Node(st, 0));
Node nd;
while(!Q.empty()){
nd = Q.top(); Q.pop();
if(vis[nd.id]) continue; vis[nd.id] = true;
for(int i=0; i<g[nd.id].size(); i++){
int j = g[nd.id][i].first;
int k = g[nd.id][i].second;
if(nd.d + k < dis[j] && !vis[j]){
dis[j] = nd.d + k;
Q.push(Node(j, dis[j]));
}
}
}
}
Dijkstra + 配对堆(高级):
#include<ext/pb_ds/priority_queue.hpp>
using namespace __gnu_pbds;
const int mn = 100005;
const int maxn = 200005 ;
const int inf = 2147483647;
typedef __gnu_pbds::priority_queue< pair<int,int> ,\
greater< pair<int,int> >,pairing_heap_tag > heap;
heap::point_iterator id[mn];//记录下每个点的迭代器
heap q;
struct edge{int to, next, dis;};
edge e[maxn * 2];
int head[mn], edge_max;
int n, m, st, dis[mn];
void add(int x, int y, int z){
e[++edge_max].to=y;
e[edge_max].dis=z;
e[edge_max].next=head[x];
head[x]=edge_max;
}
void dij(int x){
for(int i=1; i<=n; i++) dis[i] = inf; dis[x]=0;
id[x] = q.push(make_pair(0, x));//每次push会返回新加入点的迭代器
while(!q.empty()){
int now = q.top().second; q.pop();
for(int i=head[now]; i; i=e[i].next){
if(e[i].dis+dis[now] < dis[e[i].to]){
dis[e[i].to] = dis[now]+e[i].dis;
if(id[e[i].to]!=0) //如果在堆中
q.modify(id[e[i].to], make_pair(dis[e[i].to], e[i].to));
else id[e[i].to] = q.push(make_pair(dis[e[i].to], e[i].to));
}
}
}
}
const ll inf = (ll)1e16;
int n, m, k, st, ed;
vector <pii> V[N];
bool vis[N][12];
ll dis[N][12];
struct Node {
int id, step; ll d;
Node() {}
Node(int id, int step, ll d):id(id),step(step),d(d) {}
bool operator < (const Node &A)const { return d > A.d; }
};
void dijkstra(int st) {
for(int i=1; i<=n; ++i)
for(int j=0; j<=k; ++j)
dis[i][j] = inf, vis[i][j] = 0;;
dis[st][0] = 0;
priority_queue<Node> Q;
Q.push(Node(st, 0, 0));
Node nd;
while(!Q.empty()) {
nd = Q.top(); Q.pop();
if(nd.id == ed) break;
if(vis[nd.id][nd.step]) continue;
vis[nd.id][nd.step] = true;
for(int i=0; i<V[nd.id].size(); ++i) {
int j = V[nd.id][i].first;
int len = V[nd.id][i].second;
int step = nd.step;
if(dis[nd.id][step] + len < dis[j][step]){
dis[j][step] = dis[nd.id][step] + len;
Q.push(Node(j, step, dis[j][step]));
}
if(step == k) continue;
if(dis[nd.id][step] < dis[j][step+1]){
dis[j][step+1] = dis[nd.id][step];
Q.push(Node(j, step+1, dis[j][step+1]));
}
}
}
}
用于求边权带负的全源最短路
令
d
i
s
i
,
j
dis_{i,j}
disi,j 为从
i
i
i 到
j
j
j 的最短路,在第
i
i
i 行输出
∑
j
=
1
n
j
×
d
i
s
i
,
j
\sum\limits_{j=1}^n j\times dis_{i,j}
j=1∑nj×disi,j
struct node {
int dis, id;
inline bool operator < (const node &x) const { return dis > x.dis; }
node (int x, int y) { dis = x, id = y; }
} ;
bool vis[maxn];
int n, m, in[maxn];
ll h[maxn], dis[maxn];
vector<pair<int, int> > G[maxn];
inline bool SPFA(int s) { //从源点开始求一遍最短路 , 记为 h
queue <int> q;
memset(h, 0x3f, sizeof(h));
h[s] = 0; vis[s] = 1; q.push(s);
while(!q.empty()) {
int u = q.front(); q.pop(); vis[u] = 0;
for(int i=0; i<G[u].size(); i++) {
int v = G[u][i].first, w = G[u][i].second;
if(h[v] > h[u]+w) {
h[v] = h[u] + w;
if(!vis[v]) {
vis[v] = 1; q.push(v);
if(++in[v] >= n) return 0;
}
}
}
} return 1;
}
inline void dijkstra(int s) { //从每个点开始走一遍最短路模板
priority_queue <node> q;
memset(vis, 0, sizeof(vis));
for(int i=1; i<=n; i++) dis[i] = (i==s) ? 0 : (INF);
q.push(node(0, s));
while(!q.empty()) {
int u = q.top().id; q.pop();
if(vis[u]) continue; vis[u] = 1;
for(int i=0; i<G[u].size(); i++) {
int v = G[u][i].first, w = G[u][i].second;
if(dis[v] > dis[u]+w) {
dis[v] = dis[u] + w;
if(!vis[v]) q.push(node(dis[v], v));
}
}
}
}
int main() {
scanf("%d%d", &n, &m);
while(m--) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
G[u].push_back(make_pair(v, w));
// G[v].push_back(make_pair(u, w));
}
for(int i=1; i<=n; i++) G[0].push_back(make_pair(i, 0)); //创造上帝视角
if(!SPFA(0)) return puts("-1"), 0; //负环结束
for(int u=1; u<=n; u++)
for(int i=0; i<G[u].size(); i++)
G[u][i].second += h[u] - h[G[u][i].first]; //重构
for(int i=1; i<=n; i++) {
dijkstra(i); ll ans = 0;
for(int j=1; j<=n; j++)
ans += (dis[j]==INF) ? (j*INF) : (j*(dis[j]+h[j]-h[i]));
printf("%lld\n", ans); //跑最短路并统计答案
}
}
struct EDGE {
int next; int to; ll w;
} edge[MAXM];
int n, m, st, ed, cnt, pre[MAXN];
int head[MAXN], num[MAXN];
ll dis[MAXN]; bool vis[MAXN];
queue<int> Q;
void Add(int u, int v, ll w) {
edge[++cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt;
}
bool SPFA(int x) {
while(!Q.empty()) Q.pop();
for(int i=1; i<=n; i++) dis[i] = ANS_MAX;
dis[x] = 0; num[x] = 1; Q.push(x); vis[x] = true;
while(!Q.empty()) {
int k = Q.front(); Q.pop();
vis[k] = false;
if(dis[k] == ANS_MAX) continue;
for(int i=head[k]; i!=0; i=edge[i].next) {
int j = edge[i].to;
if(dis[j] > dis[k] + edge[i].w) {
dis[j] = dis[k] + edge[i].w;
num[j] = num[k]+1; pre[j] = k;
//if(num[j]>n) return 1; //判断负环
if(!vis[j]) {
Q.push(j);
vis[j] = true;
}
}
}
}
return 0;
}
void print_path(int end) { //打印最短路的路径
stack <int> path;
int now = end;
while(1) { //前驱
path.push(now);
if(now == st) break;
now = pre[now];
}
while(!path.empty()) {
now = path.top();
path.pop();
if(path.empty()) printf("%d\n", now);
else printf("%d-->", now);
}
}
int main() {
int cas; cas = Read();
while(cas--){
memset(head, 0, sizeof(head));
memset(pre, -1, sizeof(pre));
cnt = 0; read(); SPFA(st);
//SPFA(1) ? printf("YES\n") : printf("NO\n");
for(int i=1; i<=n; i++) printf("%lld ", dis[i]);
printf("\n");
//for(int i=1; i<=n; i++) print_path(i);
}
}
int n, m, st; //点,边,起点
typedef struct Edge { //边
int u, v, w;
} Edge;
Edge edge[N];
int dis[N], pre[N];
bool Bellman_Ford() {
for(int i=1; i<=n; ++i) //初始化
dis[i] = (i == st ? 0 : MAX);
for(int i=1; i<=n-1; ++i){
bool flag = false;
for(int j = 1; j <= m; ++j)
if(dis[edge[j].v] > dis[edge[j].u] + edge[j].w) {
dis[edge[j].v] = dis[edge[j].u] + edge[j].w;
flag = true;
}
if(!flag) return true;//没有负环回路
}
bool flag = 1; //判断是否含有负权回路
for(int i=1; i<=m; ++i)
if(dis[edge[i].v] > dis[edge[i].u] + edge[i].w) {
flag = 0; break;
}
return flag;
}
const int INF = INT_MAX/100;
int n, m, d[3000][3000];
void floyed() {
for(int k=0; k<n; k++) {
for(int i=0; i<n; i++) {
for(int j=0; j<n; j++) {
if(d[i][k]<INF && d[k][j]<INF)
d[i][j]=min(d[i][j], d[i][k]+d[k][j]);
}
}
}
}
void init() {
scanf("%d%d", &n, &m);
for(int i=0; i<n; i++) {
for(int j=0; j<n; j++)
if(i==j) d[i][j]=0;
else d[i][j]=INF;
}
for(int i=0; i<m; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
d[x-1][y-1]=z;
}
}
tarjan缩点
大意:求一条路径,点权和最大
题解:缩点后DP,也可以DFS
int n, m, tot, top, ans, numc;
int dfn[maxn], low[maxn], st[maxn], a[maxn];
int vis[maxn], col[maxn], cnt[maxn], sum[maxn];
vector <int> g1[maxn], g2[maxn];
void tarjan(int u){
dfn[u] = low[u] = ++tot;
st[++top] = u;
vis[u] = 1;
for(auto v : g1[u]){
if(!dfn[v]){
tarjan(v);
low[u] = min(low[u], low[v]);
} else if(vis[v]) low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u]){
numc++;
while(st[top+1] != u){
col[st[top]] = numc;
sum[numc] += a[st[top]];
vis[st[top--]] = 0;
}
}
}
int dfs(int u, int fa){
int ret = sum[u], mx = 0;
for(auto v : g2[u]){
if(v == fa) continue;
mx = max(mx, dfs(v, u));
}
return ret + mx;
}
int main() {
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++) scanf("%d", a+i);
for(int i=1, u, v; i<=m; i++) {
scanf("%d%d", &u, &v);
g1[u].push_back(v);
}
for(int i=1; i<=n; i++)
if(!dfn[i]) tarjan(i);
for(int i=1; i<=n; i++)
for(auto v : g1[i])
if(col[i] ^ col[v])
g2[col[i]].push_back(col[v]);
for(int i=1; i<=numc; i++)
ans = max(ans, dfs(i, 0));
printf("%d\n", ans);
}
tarjan求割点、桥
int n, m, tot, ans;
int dfn[maxn], low[maxn], iscut[maxn];
vector <int> g[maxn];
vector <pii> bri;
void tarjan(int u, int fa){
dfn[u] = low[u] = ++tot;
int child = 0;
for(auto v : g[u]){
if(v == fa) continue;
if(!dfn[v]){
child++;
tarjan(v, u);
if(low[v] >= dfn[u]) iscut[u] = 1;
if(low[v] > dfn[u]) bri.push_back({min(u, v), max(u, v)});
low[u] = min(low[u], low[v]);
} else low[u] = min(low[u], dfn[v]);
}
if(!fa && child==1) iscut[u] = 0;
}
int main() {
scanf("%d%d", &n, &m);
for(int i=1, u, v; i<=m; i++){
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
for(int i=1; i<=n; i++)
if(!dfn[i]) tarjan(i, 0);
for(int i=1; i<=n; i++)
if(iscut[i]) ans++;
printf("%d\n", ans);
for(int i=1; i<=n; i++)
if(iscut[i]) printf("%d ", i);
// printf("%d\n", bri.size());
// sort(bri.begin(), bri.end());
// for(auto i : bri)
// printf("%d %d\n", i.first, i.second);
}
介绍
n
n
n 个变量
a
i
a_i
ai,每个变量能且只能选
0
/
1
0/1
0/1
给出若干条件,形如:
(
n
o
t
)
(not)
(not)
a
i
a_i
ai
o
p
t
opt
opt
(
n
o
t
)
(not)
(not)
a
j
=
0
/
1
,
o
p
t
=
a
n
d
,
o
r
,
x
o
r
a_j = 0/1,opt = and,or,xor
aj=0/1,opt=and,or,xor
求出满足所有限制的一组
a
a
a
做法
每个点拆成
i
i
i 和
i
+
n
i+n
i+n,分别表示选取和不选取,下面用
i
′
i'
i′ 表示
i
+
n
i+n
i+n
定义有向边
u
→
v
u \rightarrow v
u→v,表示选择了
u
u
u 就必须选择
v
v
v
然后对所有关系连边,包括逆否命题的连边:
- i , j i,j i,j 不能同时选,即选了 i i i 就要选 j ′ : i → j ′ 、 j → i ′ a i j':i \rightarrow j'、j \rightarrow i' \quad a_i j′:i→j′、j→i′ai x o r xor xor a j = 1 a_j = 1 aj=1
- i , j i,j i,j 必须同时选,即选了 i i i 就要选 j : i → j 、 j → i a i j:i \rightarrow j、j \rightarrow i \qquad a_i j:i→j、j→iai x o r xor xor a j = 0 a_j = 0 aj=0
- i , j i,j i,j 至少选一个,即选了 i ′ i' i′ 就要选 j : i ′ → j 、 j ′ → i a i j:i' \rightarrow j、j' \rightarrow i \quad a_i j:i′→j、j′→iai o r or or a j = 1 a_j = 1 aj=1
- i i i 必须选: i ′ → i , a i = 1 i' \rightarrow i,a_i = 1 i′→i,ai=1
那么对于一个强联通分量里的点,肯定是全选或全不选
用
t
a
r
j
a
n
tarjan
tarjan 缩点后,即可选出可行解,对于每个变量
x
x
x 有四种情况:
- x x x 和 ¬ x \neg x ¬x 毫无关系:任意取
- x ⇒ ¬ x x \Rightarrow \neg x x⇒¬x:取 x x x 为假
- ¬ x ⇒ x \neg x \Rightarrow x ¬x⇒x:取 x x x 为真
- x ⇒ ¬ x x \Rightarrow \neg x x⇒¬x 并且 ¬ x ⇒ x \neg x \Rightarrow x ¬x⇒x:无解
那么缩点后,若
x
x
x 与
¬
x
\neg x
¬x 在同个强联通分量里则无解, 否则选取拓扑序较大的那么点
但是注意:
t
a
r
j
a
n
tarjan
tarjan 里的染色顺序是逆拓扑,我们可以直接用这个染色顺序来求解
也就是若 col[i] < col[i+n]
,则直接选择
i
i
i
求字典序最小的解
这里用暴力 D F S DFS DFS,实际复杂度是 n 2 n^2 n2 的
int n, m, top, vis[maxn], st[maxn];
vector <int> g[maxn];
bool dfs(int u) {
if(vis[u^1]) return false;
if(vis[u]) return true;
vis[u] = 1;
st[++top] = u;
for(auto v : g[u])
if(!dfs(v)) return false;
return true;
}
bool solve() {
for(int i=0; i<n<<1; i+=2) {
if(!vis[i] && !vis[i+1]) {
top = 0;
if(!dfs(i)) {
while(top) vis[st[top--]] = 0;
if(!dfs(i+1)) return false;
}
}
}
return true;
}
int main() {
while(~scanf("%d%d", &n, &m)) {
memset(vis, 0, sizeof(vis));
for(int i=0; i<n<<1; i++) g[i].clear();
for(int i=1, a, b; i<=m; i++) {
scanf("%d%d", &a, &b);
a--, b--;
g[a].push_back(b ^ 1);
g[b].push_back(a ^ 1);
}
if(!solve()) puts("NIE");
else
for(int i=0; i<n<<1; i++)
if(vis[i]) printf("%d\n", i + 1);
}
}
对于有向无环图的支配树
按照拓扑排序建树
每个节点在支配树上的父亲
是原图上所有父亲的
L
C
A
LCA
LCA
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
int n, m, cnt, ans[maxn];
int in[maxn], tp[maxn];
int f[maxn][25], dep[maxn];
vector <int> g[maxn], t[maxn], fa[maxn];
int lca(int x, int y){
if(dep[x] < dep[y]) swap(x, y);
for(int i=20; ~i; i--)
if(dep[f[x][i]] >= dep[y])
x = f[x][i];
if(x == y) return x;
for(int i=20; ~i; i--)
if(f[x][i] ^ f[y][i])
x=f[x][i], y=f[y][i];
return f[x][0];
}
void build(int x){
int lcaf = fa[x][0];
for(int i=1; i<fa[x].size(); i++)
lcaf = lca(lcaf, fa[x][i]);
t[lcaf].push_back(x);
dep[x] = dep[lcaf] + 1;
f[x][0] = lcaf;
for(int i=1; i<=20; i++)
f[x][i] = f[f[x][i-1]][i-1];
}
void tp_sort(){
queue <int> Q;
for(int i=1; i<=n; i++)
if(!in[i]) {
in[i]++;
g[0].push_back(i);
fa[i].push_back(0);
}
Q.push(0);
while(!Q.empty()){
int q = Q.front(); Q.pop();
tp[cnt++] = q;
for(auto i : g[q]){
in[i]--;
if(!in[i]) {
Q.push(i);
build(i);
}
}
}
}
int main() {
scanf("%d", &n);
for(int i=1, x; i<=n; i++){
while(1){
scanf("%d", &x);
if(x==0) break;
g[x].push_back(i);
fa[i].push_back(x);
in[i]++;
}
}
tp_sort(); dfs(0);
}
- 最小点覆盖 == 最大匹配
- 最大独立集 == 顶点数 - 最大匹配
- 最小边覆盖 == 顶点数 - 最大匹配
- 最小路径覆盖 == 顶点数 - 最大匹配
- 最大团 == 补图的最大独立集
- 最小割 == 最小点权覆盖集 = 点权和 - 最大点权独立集 = 最大流
1、匈牙利
时间复杂度: O ( n ∗ m ) O(n*m) O(n∗m)
const int maxn = 1e3 + 5; vector <int> g[maxn]; // 1 ~ n int n, m, lik[maxn]; bool vis[maxn]; bool dfs(int x) { for(int i=0; i<g[x].size(); i++) { int y = g[x][i]; if(vis[y]) continue; vis[y] = true; if(lik[y]==-1 || dfs(lik[y])) { lik[y] = x; return true; } } return false; } int hungry() { int ret = 0; memset(lik, -1, sizeof(lik)); for(int i=1; i<=n; i++) { // n为左边顶点数 memset(vis, 0, sizeof(vis)); if(dfs(i)) ret++; } return ret; }
2、Hopcroft-Karp
时间复杂度: O ( n ∗ m ) O(\sqrt{n}*m) O(n∗m)
const int maxn = 1e3 + 5; int n, m, dis, vis[maxn]; int mx[maxn], my[maxn], dx[maxn], dy[maxn]; vector<int> g[maxn]; int bfs() { dis = INT_MAX; memset(dx, -1, sizeof(dx)); memset(dy, -1, sizeof(dy)); queue <int> Q; for(int i=1; i<=n; i++) if(mx[i] == -1) { Q.push(i); dx[i] = 0; } while(!Q.empty()) { int x = Q.front(); Q.pop(); if(dx[x] > dis) break; for(int i=0; i<g[x].size(); i++) { int y = g[x][i]; if(dy[y] != -1) continue; dy[y] = dx[x]+1; if(my[y] == -1) dis = dy[y]; else { dx[my[y]] = dy[y]+1; Q.push(my[y]); } } } return dis != INT_MAX; } int dfs(int x) { for(int i=0; i<g[x].size(); i++) { int y = g[x][i]; if(vis[y] || dy[y]!=dx[x]+1) continue; vis[y] = true; if(my[y]!=-1 && dy[y]==dis) continue; if(my[y]==-1 || dfs(my[y])) { my[y] = x; mx[x] = y; return 1; } } return 0; } int hungry_fast() { int ret = 0; memset(my, -1, sizeof(my)); memset(mx, -1, sizeof(mx)); while(bfs()) { memset(vis, 0, sizeof(vis)); for(int i=1; i<=n; i++) if(mx[i]==-1 && dfs(i)) ret++; } return ret; }
—— 网络流 —— Dinic int n, m, st, ed; ll ans, dis[maxn]; int cnt, head[maxn], cur[maxn]; struct EDGE { int nxt, to; ll w; } edge[maxn]; void add(int u, int v, ll w) { edge[cnt].nxt = head[u]; edge[cnt].w = w; edge[cnt].to = v; head[u] = cnt++; // 从0开始 } int bfs() { memset(dis, -1, sizeof(dis)); dis[st] = 0; queue <int> Q; Q.push(st); while(!Q.empty()) { int u = Q.front(); Q.pop(); for(int i=head[u]; ~i; i=edge[i].nxt) { int v = edge[i].to; if(dis[v]==-1 && edge[i].w>0) { dis[v] = dis[u] + 1; Q.push(v); } } } return dis[ed] != -1; // 判断是否联通。 } ll dfs(int u, ll exp) { if(u == ed) return exp; // 到达终点,全部接受。 ll flow = 0, tmp = 0; for(int &i=cur[u]; ~i; i=edge[i].nxt) { // 当前弧优化 int v = edge[i].to; if(dis[v]==dis[u]+1 && edge[i].w>0) { tmp = dfs(v, min(exp, edge[i].w)); if(!tmp) continue; exp -= tmp; // 流量限制-流量,后边有判断。 flow += tmp; edge[i].w -= tmp; // 路径上的边残量减少 edge[i^1].w += tmp; // 流经的边的反向边残量增加。 if(!exp) break; // 判断是否在限制边缘 } } return flow; } int main() { while(~scanf("%d%d%d%d", &n, &m, &st, &ed)) { cnt = ans = 0; memset(head, -1, sizeof(head)); int x, y; ll z; for(int i=1; i<=m; i++) { scanf("%d%d%lld", &x, &y, &z); add(x, y, z); add(y, x, 0); // 相邻建边。 } while(bfs()) { for(int i=0; i<=n; i++) cur[i] = head[i]; ans += dfs(st, INF); } printf("%lld\n", ans); } }
费用流 struct edge { int to, cap, cost, rev; edge() {} edge(int _to, int _cap, int _cost, int _rev):\ to(_to), cap(_cap), cost(_cost), rev(_rev) {} }; struct Min_Cost_Max_Flow { int V, h[maxn], dis[maxn], prev[maxn], pree[maxn]; vector <edge> G[maxn]; void init(int n) { // 调用前初始化 V = n; for(int i=0; i<=V; ++i) G[i].clear(); } void add_edge(int from, int to, int cap, int cost) { G[from].push_back(edge(to, cap, cost, G[to].size())); G[to].push_back(edge(from, 0, -cost, G[from].size()-1)); } // flow是自己传进去的变量,就是最后的最大流,返回的是最小费用 int min_cost_max_flow(int s, int t, int f, int &flow) { int res = 0; fill(h, h+V+1, 0); while(f) { priority_queue<pii, vector<pii>, greater<pii> > q; fill(dis, dis+V+1, INF); dis[s] = 0; q.push(pii(0, s)); while(!q.empty()) { pii now = q.top(); q.pop(); int v = now.second; if(dis[v] < now.first) continue; for(int i=0; i<G[v].size(); ++i) { edge &e = G[v][i]; if(e.cap>0 && dis[e.to]>dis[v]+ \ e.cost+h[v]-h[e.to]) { dis[e.to] = dis[v]+e.cost+h[v]-h[e.to]; prev[e.to] = v; pree[e.to] = i; q.push(pii(dis[e.to], e.to)); } } } if(dis[t] == INF) break; for(int i=0; i<=V; ++i) h[i] += dis[i]; int d = f; for(int v=t; v!=s; v=prev[v]) d = min(d, G[prev[v]][pree[v]].cap); f -= d; flow += d; res += d * h[t]; for(int v=t; v!=s; v=prev[v]) { edge &e = G[prev[v]][pree[v]]; e.cap -= d; G[v][e.rev].cap += d; } } return res; } } MCMF; int n, m, s, t; int from, to, cap, cost, flow; int main() { scanf("%d%d%d%d", &n, &m, &s, &t); MCMF.init(n); for(int i=0; i<m; ++i) { scanf("%d%d%d%d", &from, &to, &cap, &cost); MCMF.add_edge(from, to, cap, cost); } printf("%d %d\n", flow, MCMF.min_cost_max_flow(s, t, INF, flow)); }
网格图网络流 —— 平面图转对偶图 对偶图
一个图的对偶图如下:
黑点为原图,红点为对偶图
平面图每一个面是对偶图的每一个点
平面图中面与面的割线是对偶图的边
若平面图中某一条边只属于一个面,那么在对偶图中就是一个环边
平面图周围无边界的面也是对偶图中的一个点
网格图网络流
如图,要求 ( 1 , 1 ) (1, 1) (1,1) 到 ( n , m ) (n, m) (n,m) 的最小割
一条割边相当于一条杠,在网格图网络流中,源点和汇点不连通后,这些杠是连续的
因此,就可以在割线上跑最短路,边权即为原图的权值
然后要设最短路的源点和汇点,这里我们要使得左上至右下不连通,因此源点和汇点分别在左下和右上
题目即为上述的图,注意这里不是严格的网格图,但是同样具有网格图网络流的性质
因此将每个小三角当成一个点建图即可int n, m, cnt, st, ed; int vis[maxn], head[maxn]; ll dis[maxn]; int main(){ scanf("%d%d", &n, &m); int w; st = 0, ed = (n - 1) * (m - 1) * 2 + 1; memset(head, -1, sizeof(head)); for(int i=1, tot=2; i<=n; i++) for(int j=1; j<m; j++, tot+=2) { scanf("%d", &w); int u = i == 1 ? ed : tot - m * 2 + 1, v = i == n ? 0 : tot; add(u, v, w), add(v, u, w); } for(int i=1, tot=1; i<n; i++){ for(int j=1; j<m; j++, tot+=2) { scanf("%d", &w); int u = j == 1 ? 0 : tot - 1, v = tot; add(u, v, w), add(v, u, w); } scanf("%d", &w); add(tot-1, ed, w), add(ed, tot-1, w); } for(int i=1, tot=1; i<n; i++) for(int j=1; j<m; j++, tot+=2) { scanf("%d", &w); add(tot, tot+1, w), add(tot+1, tot, w); } dijkstra(st); printf("%lld", dis[ed]); }
—— 拓扑排序 —— int n, m, cnt, in[maxn], tp[maxn]; vector <int> v[maxn]; void tp_sort(){ queue <int> Q; for(int i=1; i<=n; i++) if(!in[i]) Q.push(i); while(!Q.empty()){ int q = Q.front(); Q.pop(); tp[cnt++] = q; for(auto i : v[q]){ in[i]--; if(!in[i]) Q.push(i); } } } int main() { scanf("%d%d", &n, &m); for(int i=0, x, y; i<m; i++){ scanf("%d%d", &x, &y); v[x].push_back(y); in[y]++; } tp_sort(); for(int i=0; i<cnt; i++) printf("%d ", tp[i]); }
—— 树链剖分 —— 树的重心 概念
以树的重心为整棵树的根时,它的最大子树最小(也就是删除该点后最大联通块最小)
定义及性质
定义1:找到一个点,删除它得到的森林中最大的子树节点数最少,那么这个点就是这棵树的重心
定义2:删除重心后得到的所有子树,其顶点数必然不超过 n / 2 n/2 n/2
性质1:树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么他们的距离和一样
性质2:把两个树通过一条边相连得到一个新的树,那么新的树的重心在连接原来两个树的重心的路径上
性质3:把一个树添加或删除一个叶子,那么它的重心最多只移动一条边的距离
求法
s z [ u ] sz[u] sz[u] 表示以 u u u 为根的子树总大小(包括根)
m a x _ p a r t max\_part max_part表示最大子树的大小
n − s z [ u ] n - sz[u] n−sz[u] 表示删除 u u u 后上方联通块的大小
模板题,求表示重心的所有的子树中最大的子树的结点数目
int n, sz[maxn], ans_id, ans_num = maxn; vector <int> g[maxn]; void dfs(int u, int fa){ sz[u] = 1; int max_part = 0; for(auto v : g[u]){ if(v == fa) continue; dfs(v, u); sz[u] += sz[v]; max_part = max(max_part, sz[v]); } max_part = max(max_part, n - sz[u]); if(max_part < ans_num){ ans_num = max_part; ans_id = u; } } int main() { scanf("%d", &n); for(int i=1, u, v; i<n; i++){ scanf("%d%d", &u, &v); g[u].push_back(v); g[v].push_back(u); } dfs(1, 0); printf("%d\n", ans_num); }
题意:一颗有根树,对于每次询问 v v v,判断以 v v v 为根的子树重心是哪个
题解:
a n s [ u ] ans[u] ans[u] 表示以 u u u 为根的子树重心, m a x _ i d max\_id max_id 表示重儿子
根据重心定义2:删除重心后得到的所有子树,其顶点数必然不超过 n / 2 n/2 n/2
则判断节点 u u u 是否为以 u u u 为根的树的重心: s z [ m a x _ i d ] ∗ 2 ≤ s z [ u ] sz[max\_id]*2 ≤ sz[u] sz[max_id]∗2≤sz[u]若 u u u 本身不是重心,则考虑重心的转移,根据性质2:
以 u u u 为根的重心,一定在以 m a x _ i d max\_id max_id 为根的重心 到 u u u 的路径上,即 a n s [ m a x _ i d ] ans[max\_id] ans[max_id] 到 u u u
或者说:如果 m a x _ i d max\_id max_id 的 s i z e ∗ 2 > s z [ u ] size*2>sz[u] size∗2>sz[u],则重心一定在这个子树中
由于 s z [ a n s [ m a x _ i d ] ] sz[ans[max\_id]] sz[ans[max_id]] 一定小于 s z [ u ] / 2 sz[u] / 2 sz[u]/2,则只需要判断另一边即可
注意枚举重心转移的总时间为 O ( n ) O(n) O(n),则总时间复杂度: O ( n ) O(n) O(n)int n, q, ans[maxn], f[maxn], sz[maxn]; vector <int> g[maxn]; void dfs(int u, int fa){ sz[u] = 1, ans[u] = u; int max_id = 0; for(auto v : g[u]){ if(v == fa) continue; dfs(v, u); sz[u] += sz[v]; if(sz[v] > sz[max_id]) max_id = v; } if(sz[max_id]*2 > sz[u]) { int tmp = ans[max_id]; while((sz[u]-sz[tmp])*2 > sz[u]) tmp = f[tmp]; ans[u] = tmp; } } int main() { scanf("%d%d", &n, &q); for(int i=2, u; i<=n; i++){ scanf("%d", &u); f[i] = u; g[u].push_back(i); g[i].push_back(u); } dfs(1, 0); while(q--){ int tmp; scanf("%d", &tmp); printf("%d\n", ans[tmp]); } }
树的中心 概念
以树的中心为整棵树的根时,从该根到每个叶子节点的最长路径最短
求法
d p [ u ] [ 0 ] dp[u][0] dp[u][0] 表示以 u u u 为根的子树中的最长链, d p [ u ] [ 1 ] dp[u][1] dp[u][1] 表示以 u u u 为根的子树中的次长链
注意最长链和次长链没有交集, c [ u ] [ 0 ] c[u][0] c[u][0] / / / c [ u ] [ 1 ] c[u][1] c[u][1] 分别表示两者的决策点
设 u p [ u ] up[u] up[u] 为从 u u u 这个点往上走的最远距离,注意这里往上指向上任意一个方向
d p dp dp 和 c c c 很容易求,求完之后考虑怎么求 u p up up对于一个点 u − > v u -> v u−>v
如果 v v v 是 u u u 最长链的决策点, u p [ v ] = u p [ u ] up[v] = up[u] up[v]=up[u] 和 u u u 的次长链取最大值 + w + w +w
如果不是最长链决策点, u p [ v ] = u p [ u ] up[v] = up[u] up[v]=up[u] 和 u u u 的最长链取最大值 + w + w +w
模板题,求每个点为根时,到叶子结点的最长距离
int n, dp[maxn][2], c[maxn][2], up[maxn]; vector <pii> g[maxn]; void dfs1(int u, int fa) { for(auto vv : g[u]) { int v = vv.first, w = vv.second; if(v == fa) continue; dfs1(v, u); if(dp[v][0] + w > dp[u][0]) { dp[u][1] = dp[u][0], c[u][1] = c[u][0]; dp[u][0] = dp[v][0] + w, c[u][0] = v; } else if(dp[v][0] + w > dp[u][1]) dp[u][1] = dp[v][0] + w, c[u][1] = v; } } void dfs2(int u, int fa) { for(auto vv : g[u]) { int v = vv.first, w = vv.second; if(v == fa) continue; if(v == c[u][0]) up[v] = max(up[u], dp[u][1]) + w; else up[v] = max(up[u], dp[u][0]) + w; dfs2(v, u); } } signed main() { while(~scanf("%d", &n)) { memset(dp, 0, sizeof(dp)); memset(up, 0, sizeof(up)); for(int i=1; i<=n; i++) g[i].clear(); for(int i=2, v, w; i<=n; i++) { scanf("%d%d", &v, &w); g[i].push_back({v, w}); g[v].push_back({i, w}); } dfs1(1, 0); dfs2(1, 0); for(int i=1; i<=n; i++) printf("%d\n", max(dp[i][0], up[i])); // int pos, ans = 1e9; // for(int i=1; i<=n; i++) // if(max(dp[i][0], up[i]) < ans) // ans = max(dp[i][0], up[i]), pos = i; // printf("%d %d\n", pos, ans); } }
树上差分 一、点差分
对于树上路径 p a t h ( u , v ) path(u,v) path(u,v)
d l t [ u ] + + , d l t [ v ] + + , d l t [ l c a ( u , v ) ] − − , d l t [ f ( l c a ( u , v ) ) ] − − dlt[u]++, dlt[v]++, dlt[lca(u, v)] --, dlt[f(lca(u, v))] -- dlt[u]++,dlt[v]++,dlt[lca(u,v)]−−,dlt[f(lca(u,v))]−−询问点 x x x 被多少个标记覆盖时 d f s dfs dfs
从根节点开始,将其本身的权值加上所有子节点的权值
每个节点的权值既是其被路径覆盖的次数
二、边差分
对于树上路径 p a t h ( u , v ) path(u,v) path(u,v)
d l t [ u ] + + , d l t [ v ] + + , d l t [ l c a ( u , v ) ] − = 2 dlt[u]++, dlt[v]++, dlt[lca(u, v)] -=2 dlt[u]++,dlt[v]++,dlt[lca(u,v)]−=2询问点 x x x 与其父亲的连边被多少个标记覆盖时 d f s dfs dfs
从根节点开始,将其本身的权值加上所有子节点的权值
每个节点的权值即表示与其父亲的连边,被路径覆盖的次数
树链剖分 名称 解释 f [ u ] f[u] f[u] 保存结点 u u u 的父亲节点 d e p [ u ] dep[u] dep[u] 保存结点 u u u 的深度值 s i z e [ u ] size[u] size[u] 保存以 u u u 为根的子树节点个数 s o n [ u ] son[u] son[u] 保存重儿子 r k [ u ] rk[u] rk[u] 保存当前 d f s dfs dfs 标号在树中所对应的节点 t o p [ u ] top[u] top[u] 保存当前节点所在链的顶端节点(替代 l c a lca lca) i d [ u ] id[u] id[u] 保存树中每个节点剖分以后的新编号( D F S DFS DFS的执行顺序)
#define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define pushup(rt) t[rt] = t[rt<<1] + t[rt<<1|1]; ll t[maxn<<2], lazy[maxn<<2], a[maxn]; int n, m, f[maxn], size[maxn]; int cnt, head[maxn], dep[maxn]; int rk[maxn], rks, id[maxn]; int son[maxn], top[maxn]; struct EDGE { int next, to, w; } edge[maxn<<2]; void add(int u, int v, int w) { edge[++cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt; } /*-------------------------树剖-------------------------*/ void dfs1(int cur, int fa, int de){ dep[cur] = de, f[cur] = fa, size[cur] = 1; for(int i=head[cur]; i; i=edge[i].next){ int v = edge[i].to; if(v == fa) continue; dfs1(v, cur, de+1); size[cur] += size[v]; if(size[son[cur]] < size[v]) son[cur] = v; } } void dfs2(int cur, int tp){ top[cur] = tp, id[cur] = ++rks, rk[rks] = cur; if(son[cur]) dfs2(son[cur], tp); for(int i=head[cur]; i; i=edge[i].next){ int v = edge[i].to; if(v == f[cur]) continue; if(v != son[cur]) dfs2(v, v); } } /*-------------------------树剖-------------------------*/ /*-------------------------线段树-------------------------*/ void build(int l,int r,int rt){ lazy[rt] = 0; if(l==r){ t[rt] = a[rk[l]]; return ; } int m = l + r >> 1; build(lson); build(rson); pushup(rt); } void pushdown(int l,int r,int rt) { if(lazy[rt]) { lazy[rt<<1] += lazy[rt]; lazy[rt<<1|1] += lazy[rt]; t[rt<<1] += l*lazy[rt]; t[rt<<1|1] += r*lazy[rt]; lazy[rt] = 0; } } void update(int L,int R,int C,int l,int r,int rt) { if(L<=l&&r<=R) { t[rt] += (r-l+1)*C; lazy[rt] += C; return ; } int m = (l+r)>>1; pushdown(m-l+1,r-m,rt); if(L<=m) update(L,R,C,lson); if(R>m) update(L,R,C,rson); pushup(rt); } ll query(int L,int R,int l,int r,int rt) { if(L<=l&&r<=R) return t[rt]; int m = (l+r)>>1; pushdown(m-l+1,r-m,rt); ll ans = 0; if(L<=m) ans += query(L,R,lson); if(R>m) ans += query(L,R,rson); return ans; } /*-------------------------线段树-------------------------*/ /*----------------------树剖 + 线段树----------------------*/ ll sum(int x, int y){ ll ret = 0; while(top[x] ^ top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x, y); ret += query(id[top[x]],id[x],1,n,1); x = f[top[x]]; } if(id[x]>id[y]) swap(x, y); return ret + query(id[x],id[y],1,n,1); } void updates(int x, int y, int c){ while(top[x] ^ top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x, y); update(id[top[x]],id[x],c,1,n,1); x = f[top[x]]; } if(id[x]>id[y]) swap(x, y); update(id[x],id[y],c,1,n,1); } /*----------------------树剖 + 线段树----------------------*/ int main() { int rt; scanf("%d%d%d%d", &n, &m, &rt, &mod); for(int i=1; i<=n; i++) scanf("%d", a+i); for(int i=1; i<n; i++){ int u, v; scanf("%d%d", &u, &v); add(u, v, 1); add(v, u, 1); } dfs1(rt, 0, 1); dfs2(rt, rt); build(1, n, 1); for(int i=1; i<=m; i++){ int x, y, c, op; scanf("%d", &op); if(op == 1){ scanf("%d%d%d", &x, &y, &c); updates(x, y, c); } else if(op == 2){ scanf("%d%d", &x, &y); printf("%lld\n", sum(x, y)); } else if(op == 3){ scanf("%d%d", &x, &c); update(id[x],id[x]+size[x]-1,c,1,n,1); } else { scanf("%d", &x); printf("%lld\n", query(id[x],id[x]+size[x]-1,1,n,1)); } } }
—— 最小生成树 —— Prim + 邻接链表 + 堆优化(优先队列):
int n, m, cnt, ans, vis[maxn], head[maxn], dis[maxn]; priority_queue <pii, vector<pii>, greater<pii> > q; struct EDGE{ int next, to, w; } e[maxn<<1]; void add(int u, int v, int w) { e[++cnt].next = head[u]; e[cnt].w = w; e[cnt].to = v; head[u] = cnt; } void prim(int st){ memset(vis,0,sizeof(vis)); for(int i=1; i<=n; i++) dis[i] = INT_MAX; q.push(make_pair(0, st)); dis[st] = 0; int num = 0; while(!q.empty() && num<n){ int d = q.top().first; int k = q.top().second; q.pop(); if(vis[k]) continue; num++; ans += d; vis[k] = 1; for(int i=head[k]; i; i=e[i].next) if(e[i].w<dis[e[i].to]){ dis[e[i].to] = e[i].w; q.push(make_pair(e[i].w, e[i].to)); } } } int main() { n = read(), m = read(); memset(head,0,sizeof(head)); ans = cnt = 0; for(int i=0; i<m; i++){ int u, v, w; u = read(), v = read(), w = read(); add(u, v, w); add(v, u, w); } prim(1); printf("%d\n", ans); }
Kruskal:
int n, m, cnt, ans, f[maxn]; struct EDGE{ int u, v, w; bool operator <(const EDGE &A) const { return w<A.w; } } e[maxn]; int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); } int main() { int u, v, w; n = read(), m = read(); for(int i=1; i<=n; i++) f[i] = i; for(int i=0; i<m; i++){ e[i].u = read(); e[i].v = read(); e[i].w = read(); } sort(e, e+m); for(int i=0; i<m; i++){ u = find(e[i].u); v = find(e[i].v); if(u!=v){ f[u] = v; ans += e[i].w; cnt++; } if(cnt==n-1) break; } printf("%d\n", ans); }
—— 欧拉回路 —— 判定
有向图回路:图联通 & & \&\& && 每个点入度 = = = 出度
无向图回路:图联通 & & \&\& && 每个点的度数为偶数有向图通路:图联通 & & \&\& && 每个点入度 = = = 出度
或者可以存在两个点,其中一个出度比入度大 1 1 1,为路径的起点
另外一个入度比出度大 1 1 1,为路径的终点
无向图通路:图联通 & & \&\& && 度数为奇数的的点只有 2 2 2 个或者 0 0 0 个
当有两个奇点时,一个为起点,另一个为终点
欧拉回路 输出
从起点开始找一条回路
找出有尚未访问的边的路径上的第一个顶点, 并执行另外一次深度优先搜索
这将给出另外一个回路, 把它拼接到原来的回路上
继续该过程直到所有的边都被遍历为止s o l v e 1 solve1 solve1 为无向图回路, s o l v e 2 solve2 solve2 为有向图回路
int T, n, m; bool solve1() { // 无向图 scanf("%d%d", &n, &m); vector <vector <pii> > g(2 * n + 5); for(int i=1, u, v; i<=m; i++) { scanf("%d%d", &u, &v); g[u].push_back({v, i}); g[v].push_back({u, -i}); } for(int i=1; i<=n; i++) if(g[i].size() & 1) return printf("NO\n"), 0; vector <bool> del(m + 5); vector <int> ans; function <void(int, int)> dfs = [&] (int u, int fa) { while(g[u].size()) { auto it = g[u].back(); g[u].pop_back(); int v = it.first, id = it.second; if(!del[abs(id)]) { del[abs(id)] = true; dfs(v, id); } } if(fa != 0) ans.push_back(fa); }; for(int i=1; i<=n; i++) if(g[i].size()) { dfs(i, 0); break; } if(ans.size() != m) return printf("NO\n"), 0; printf("YES\n"); for(int i=ans.size()-1; ~i; i--) printf("%d ", ans[i]); } bool solve2() { // 有向图 scanf("%d%d", &n, &m); vector <int> in(n + 5), out(n + 5); vector <vector <pii> > g(2 * n + 5); for(int i=1, u, v; i<=m; i++) { scanf("%d%d", &u, &v); g[u].push_back({v, i}); in[v]++, out[u]++; } for(int i=1; i<=n; i++) if(in[i] ^ out[i]) return printf("NO\n"), 0; vector <bool> del(m + 5); vector <int> ans; function <void(int, int)> dfs = [&] (int u, int fa) { while(g[u].size()) { auto it = g[u].back(); g[u].pop_back(); int v = it.first, id = it.second; if(!del[id]) { del[id] = true; dfs(v, id); }; } if(fa != 0) ans.push_back(fa); }; for(int i=1; i<=n; i++) if(g[i].size()) { dfs(i, 0); break; } if(ans.size() != m) return printf("NO\n"), 0; printf("YES\n"); for(int i=ans.size()-1; ~i; i--) printf("%d ", ans[i]); } signed main() { scanf("%d", &T); T == 1 ? solve1() : solve2(); }
欧拉通路 输出
int T, n, m; signed main() { scanf("%d%d", &n, &m); vector <vector <pii> > g(2 * n + 5); for(int i=1, u, v; i<=m; i++) { scanf("%d%d", &u, &v); g[u].push_back({v, i}); g[v].push_back({u, -i}); } int st = 1; for(int i=1; i<=n; i++) if(g[i].size() & 1) st = i; vector <bool> del(m + 5); vector <int> ans; function <void(int, int)> dfs = [&] (int u, int fa) { while(g[u].size()) { auto it = g[u].back(); g[u].pop_back(); int v = it.first, id = it.second; if(!del[abs(id)]) { del[abs(id)] = true; dfs(v, id); } } ans.push_back(u); }; for(int i=1; i<=n; i++) if(g[i].size()) { dfs(st, 0); break; } for(int i=ans.size()-1; ~i; i--) printf("%d ", ans[i]); }