目录 1
Graph 图论 3
| DAG的深度优先搜索标记 3
| 无向图找桥 3
| 无向图连通度(割) 3
| 最大团问题 DP + DFS 3
| 欧拉路径O(E) 3
| Dijkstra数组实现O(N^2) 3
| Dijkstra O(E * log E) 4
| BellmanFord单源最短路O(VE) 4
| SPFA(Shortest Path Faster Algorithm) 4
| 第K短路(Dijkstra) 5
| 第K短路(A*) 5
| Prim求MST 6
| 次小生成树O(V^2) 6
| 最小生成森林问题(k颗树)O(mlogm). 6
| 有向图最小树形图 6
| Minimal Steiner Tree 6
| Tarjan强连通分量 7
| 弦图判断 7
| 弦图的perfect elimination点排列 7
| 稳定婚姻问题 O(n^2) 7
| 拓扑排序 8
| 无向图连通分支(dfs/bfs邻接阵) 8
| 有向图强连通分支(dfs/bfs邻接阵)O(n^2) 8
| 有向图最小点基(邻接阵)O(n^2) 9
| Floyd求最小环 9
| 2-sat问题 9
Network 网络流 11
| 二分图匹配(匈牙利算法DFS实现) 11
| 二分图匹配(匈牙利算法BFS实现) 11
| 二分图匹配(Hopcroft-Carp的算法) 11
| 二分图最佳匹配(kuhn munkras算法O(m*m*n)) 11
| 无向图最小割 O(N^3) 12
| 有上下界的最小(最大)流 12
| Dinic最大流 O(V^2 * E) 12
| HLPP最大流 O(V^3) 13
| 最小费用流 O(V * E * f) 13
| 最小费用流 O(V^2 * f) 14
| 最佳边割集 15
| 最佳点割集 15
| 最小边割集 15
| 最小点割集(点连通度) 16
| 最小路径覆盖O(n^3) 16
| 最小点集覆盖 16
Structure 数据结构 17
| 求某天是星期几 17
| 左偏树 合并复杂度O(log N) 17
| 树状数组 17
| 二维树状数组 17
| Trie树(k叉) 17
| Trie树(左儿子又兄弟) 18
| 后缀数组 O(N * log N) 18
| 后缀数组 O(N) 18
| RMQ离线算法 O(N*logN)+O(1) 19
| RMQ(Range Minimum/Maximum Query)-st算法(O(nlogn + Q)) 19
| RMQ离线算法 O(N*logN)+O(1)求解LCA 19
| LCA离线算法 O(E)+O(1) 20
| 带权值的并查集 20
| 快速排序 20
| 2台机器工作调度 20
| 比较高效的大数 20
| 普通的大数运算 21
| 最长公共递增子序列 O(n^2) 22
| 0-1分数规划 22
| 最长有序子序列(递增/递减/非递增/非递减) 22
| 最长公共子序列 23
| 最少找硬币问题(贪心策略-深搜实现) 23
| 棋盘分割 23
| 汉诺塔 23
| STL中的priority_queue 24
| 堆栈 24
| 区间最大频率 24
| 取第k个元素 25
| 归并排序求逆序数 25
| 逆序数推排列数 25
| 二分查找 25
| 二分查找(大于等于v的第一个值) 25
| 所有数位相加 25
Number 数论 26
|递推求欧拉函数phi(i) 26
|单独求欧拉函数phi(x) 26
| GCD 最大公约数 26
| 快速 GCD 26
| 扩展 GCD 26
| 模线性方程 a * x = b (% n) 26
| 模线性方程组 26
| 筛素数 [1..n] 26
| 高效求小范围素数 [1..n] 26
| 随机素数测试(伪素数原理) 26
| 组合数学相关 26
| Polya计数 27
| 组合数C(n, r) 27
| 最大1矩阵 27
| 约瑟夫环问题(数学方法) 27
| 约瑟夫环问题(数组模拟) 27
| 取石子游戏1 27
| 集合划分问题 27
| 大数平方根(字符串数组表示) 28
| 大数取模的二进制方法 28
| 线性方程组a[][]x[]=b[] 28
| 追赶法解周期性方程 28
| 阶乘最后非零位,复杂度O(nlogn) 29
递归方法求解排列组合问题 30
| 类循环排列 30
| 全排列 30
| 不重复排列 30
| 全组合 31
| 不重复组合 31
| 应用 31
模式串匹配问题总结 32
| 字符串Hash 32
| KMP匹配算法O(M+N) 32
| Karp-Rabin字符串匹配 32
| 基于Karp-Rabin的字符块匹配 32
| 函数名: strstr 32
| BM算法的改进的算法Sunday Algorithm 32
| 最短公共祖先(两个长字符串) 33
| 最短公共祖先(多个短字符串) 33
Geometry 计算几何 34
| Graham求凸包 O(N * logN) 34
| 判断线段相交 34
| 求多边形重心 34
| 三角形几个重要的点 34
| 平面最近点对 O(N * logN) 34
| Liuctic的计算几何库 35
| 求平面上两点之间的距离 35
| (P1-P0)*(P2-P0)的叉积 35
| 确定两条线段是否相交 35
| 判断点p是否在线段l上 35
| 判断两个点是否相等 35
| 线段相交判断函数 35
| 判断点q是否在多边形内 35
| 计算多边形的面积 35
| 解二次方程 Ax^2+Bx+C=0 36
| 计算直线的一般式 Ax+By+C=0 36
| 点到直线距离 36
| 直线与圆的交点,已知直线与圆相交 36
| 点是否在射线的正向 36
| 射线与圆的第一个交点 36
| 求点p1关于直线ln的对称点p2 36
| 两直线夹角(弧度) 36
ACM/ICPC竞赛之STL 37
ACM/ICPC竞赛之STL简介 37
ACM/ICPC竞赛之STL--pair 37
ACM/ICPC竞赛之STL--vector 37
ACM/ICPC竞赛之STL--iterator简介 38
ACM/ICPC竞赛之STL--string 38
ACM/ICPC竞赛之STL--stack/queue 38
ACM/ICPC竞赛之STL--map 39
ACM/ICPC竞赛之STL--algorithm 40
STL in ACM 41
头文件 42
线段树 43
求矩形并的面积(线段树+离散化+扫描线) 43
求矩形并的周长(线段树+离散化+扫描线) 43
/*==================================================*\
| INIT: edge[][]邻接矩阵; pre[], post[], tag全置0;
| CALL: dfstag(i, n); pre/post:开始/结束时间
\*==================================================*/
int edge[V][V], pre[V], post[V], tag;
void dfstag(int cur, int n)
{ // vertex: 0 ~ n-1
pre[cur] = ++tag;
for (int i=0; i if (0 == pre[i]) { printf("Tree Edge!\n"); dfstag(i, n); } else { if (0 == post[i]) printf("Back Edge!\n"); else if (pre[i] > pre[cur]) printf("Down Edge!\n"); else printf("Cross Edge!\n"); } } post[cur] = ++tag; } /*==================================================*\ | INIT: edge[][]邻接矩阵;vis[],pre[],anc[],bridge 置0; | CALL: dfs(0, -1, 1, n); \*==================================================*/ int bridge, edge[V][V], anc[V], pre[V], vis[V]; void dfs(int cur, int father, int dep, int n) { // vertex: 0 ~ n-1 if (bridge) return; vis[cur] = 1; pre[cur] = anc[cur] = dep; for (int i=0; i if (i != father && 1 == vis[i]) { if (pre[i] < anc[cur]) anc[cur] = pre[i];//back edge } if (0 == vis[i]) { //tree edge dfs(i, cur, dep+1, n); if (bridge) return; if (anc[i] < anc[cur]) anc[cur] = anc[i]; if (anc[i] > pre[cur]) { bridge = 1; return; } } } vis[cur] = 2; } /*==================================================*\ | INIT: edge[][]邻接矩阵;vis[],pre[],anc[],deg[]置为0; | CALL: dfs(0, -1, 1, n); | k=deg[0], deg[i]+1(i=1…n-1)为删除该节点后得到的连通图个数 | 注意:0作为根比较特殊! \*==================================================*/ int edge[V][V], anc[V], pre[V], vis[V], deg[V]; void dfs(int cur, int father, int dep, int n) {// vertex: 0 ~ n-1 int cnt = 0; vis[cur] = 1; pre[cur] = anc[cur] = dep; for (int i=0; i if (i != father && 1 == vis[i]) { if (pre[i] < anc[cur]) anc[cur] = pre[i];//back edge } if (0 == vis[i]) { //tree edge dfs(i, cur, dep+1, n); ++cnt; // 分支个数 if (anc[i] < anc[cur]) anc[cur] = anc[i]; if ((cur==0 && cnt>1) || (cnt!=0 && anc[i]>=pre[cur])) ++deg[cur]; // link degree of a vertex } } vis[cur] = 2; } /*==================================================*\ | INIT: g[][]邻接矩阵; | CALL: res = clique(n); \*==================================================*/ int g[V][V], dp[V], stk[V][V], mx; int dfs(int n, int ns, int dep){ if (0 == ns) { if (dep > mx) mx = dep; return 1; } int i, j, k, p, cnt; for (i = 0; i < ns; i++) { k = stk[dep][i]; cnt = 0; if (dep + n - k <= mx) return 0; if (dep + dp[k] <= mx) return 0; for (j = i + 1; j < ns; j++) { p = stk[dep][j]; if (g[k][p]) stk[dep + 1][cnt++] = p; } dfs(n, cnt, dep + 1); } return 1; } int clique(int n){ int i, j, ns; for (mx = 0, i = n - 1; i >= 0; i--) { // vertex: 0 ~ n-1 for (ns = 0, j = i + 1; j < n; j++) if (g[i][j]) stk[1][ ns++ ] = j; dfs(n, ns, 1); dp[i] = mx; } return mx; } /*==================================================*\ | INIT: adj[][]置为图的邻接表; cnt[a]为a点的邻接点个数; | CALL: elpath(0); 注意:不要有自向边 \*==================================================*/ int adj[V][V], idx[V][V], cnt[V], stk[V], top; int path(int v){ for (int w ; cnt[v] > 0; v = w) { stk[ top++ ] = v; w = adj[v][ --cnt[v] ]; adj[w][ idx[w][v] ] = adj[w][ --cnt[w] ]; // 处理的是无向图—-边是双向的,删除v->w后,还要处理删除w->v } return v; } void elpath (int b, int n){ // begin from b int i, j; for (i = 0; i < n; ++i) // vertex: 0 ~ n-1 for (j = 0; j < cnt[i]; ++j) idx[i][ adj[i][j] ] = j; printf("%d", b); for (top = 0; path(b) == b && top != 0; ) { b = stk[ --top ]; printf("-%d", b); } printf("\n"); } /*==================================================*\ | Dijkstra --- 数组实现(在此基础上可直接改为STL的Queue实现) | lowcost[] --- beg到其他点的最近距离 | path[] -- beg为根展开的树,记录父亲结点 \*==================================================*/ #define INF 0x03F3F3F3F const int N; int path[N], vis[N]; void Dijkstra(int cost[][N], int lowcost[N], int n, int beg){ int i, j, min; memset(vis, 0, sizeof(vis)); vis[beg] = 1; for (i=0; i lowcost[i] = cost[beg][i]; path[i] = beg; } lowcost[beg] = 0; path[beg] = -1; // 树根的标记 int pre = beg; for (i=1; i min = INF; for (j=0; j // 下面的加法可能导致溢出,INF不能取太大 if (vis[j]==0 && lowcost[pre]+cost[pre][j] lowcost[j] = lowcost[pre] + cost[pre][j]; path[j] = pre; } for (j=0; j if (vis[j] == 0 && lowcost[j] < min){ min = lowcost[j]; pre = j; } vis[pre] = 1; } } /*==================================================*\ | INIT: 调用init(nv, ne)读入边并初始化; | CALL: dijkstra(n, src); dist[i]为src到i的最短距离 \*==================================================*/ #define typec int // type of cost const typec inf = 0x3f3f3f3f; // max of cost typec cost[E], dist[V]; int e, pnt[E], nxt[E], head[V], prev[V], vis[V]; struct qnode { int v; typec c; qnode (int vv = 0, typec cc = 0) : v(vv), c(cc) {} bool operator < (const qnode& r) const { return c>r.c; } }; void dijkstra(int n, const int src){ qnode mv; int i, j, k, pre; priority_queue vis[src] = 1; dist[src] = 0; que.push(qnode(src, 0)); for (pre = src, i=1; i for (j = head[pre]; j != -1; j = nxt[j]) { k = pnt[j]; if (vis[k] == 0 && dist[pre] + cost[j] < dist[k]){ dist[k] = dist[pre] + cost[j]; que.push(qnode(pnt[j], dist[k])); prev[k] = pre; } } while (!que.empty() && vis[que.top().v] == 1) que.pop(); if (que.empty()) break; mv = que.top(); que.pop(); vis[pre = mv.v] = 1; } } inline void addedge(int u, int v, typec c){ pnt[e] = v; cost[e] = c; nxt[e] = head[u]; head[u] = e++; } void init(int nv, int ne){ int i, u, v; typec c; e = 0; memset(head, -1, sizeof(head)); memset(vis, 0, sizeof(vis)); memset(prev, -1, sizeof(prev)); for (i = 0; i < nv; i++) dist[i] = inf; for (i = 0; i < ne; ++i) { scanf("%d%d%d", &u, &v, &c);// %d: type of cost addedge(u, v, c); // vertex: 0 ~ n-1, 单向边 } } /*==================================================*\ | 能在一般情况下,包括存在负权边的情况下,解决单源最短路径问题 | INIT: edge[E][3]为边表 | CALL: bellman(src);有负环返回0;dist[i]为src到i的最短距 | 可以解决差分约束系统: 需要首先构造约束图,构造不等式时>=表示求最小值, 作为最长路,<=表示求最大值, 作为最短路 (v-u <= c:a[u][v] = c) \*==================================================*/ #define typec int // type of cost const typec inf=0x3f3f3f3f; // max of cost int n, m, pre[V], edge[E][3]; typec dist[V]; int relax (int u, int v, typec c){ if (dist[v] > dist[u] + c) { dist[v] = dist[u] + c; pre[v] = u; return 1; } return 0; } int bellman (int src){ int i, j; for (i=0; i dist[i] = inf; pre[i] = -1; } dist[src] = 0; bool flag; for (i=1; i flag = false; // 优化 for (j=0; j if( 1 == relax(edge[j][0], edge[j][1], edge[j][2]) ) flag = true; } if( !flag ) break; } for (j=0; j if (1 == relax(edge[j][0], edge[j][1], edge[j][2])) return 0; // 有负圈 } return 1; } /*==================================================*\ Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。 它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径,可以处理负边。 原理:只有那些在前一遍松弛中改变了距离估计值的点,才可能引起他们的邻接点的距离估计值的改变。 判断负权回路:记录每个结点进队次数,超过|V|次表示有负权。 \*==================================================*/ // POJ 3159 Candies const int INF = 0x3F3F3F3F; const int V = 30001; const int E = 150001; int pnt[E], cost[E], nxt[E]; int e, head[V]; int dist[V]; bool vis[V]; int main(void){ int n, m; while( scanf("%d%d", &n, &m) != EOF ){ int i, a, b, c; e = 0; memset(head, -1, sizeof(head)); for( i=0; i < m; ++i ) {// b-a <= c, 有向边(a, b):c ,边的方向!!! scanf("%d%d%d", &a, &b, &c); addedge(a, b, c); } printf("%d\n", SPFA(1, n)); } return 0; } int relax(int u, int v, int c){ if( dist[v] > dist[u] + c ) { dist[v] = dist[u] + c; return 1; } return 0; } inline void addedge(int u, int v, int c){ pnt[e] = v; cost[e] = c; nxt[e] = head[u]; head[u] = e++; } int SPFA(int src, int n) { // 此处用堆栈实现,有些时候比队列要快 int i; for( i=1; i <= n; ++i ){ // 顶点1...n vis[i] = 0; dist[i] = INF; } dist[src] = 0; int Q[E], top = 1; Q[0] = src; vis[src] = true; while( top ){ int u, v; u = Q[--top]; vis[u] = false; for( i=head[u]; i != -1; i=nxt[i] ){ v = pnt[i]; if( 1 == relax(u, v, cost[i]) && !vis[v] ) { Q[top++] = v; vis[v] = true; } } } return dist[n]; } // 队列实现,而且有负权回路判断—POJ 3169 Layout #define swap(t, a, b) (t=a, a=b, b=t) const int INF = 0x3F3F3F3F; const int V = 1001; const int E = 20001; int pnt[E], cost[E], nxt[E]; int e, head[V], dist[V]; bool vis[V]; int cnt[V]; // 入队列次数 int main(void){ int n, ml, md; while( scanf("%d%d%d", &n, &ml, &md) != EOF ){ int i, a, b, c, t; e = 0; memset(head, -1, sizeof(head)); for( i=0; i < ml; ++i ) // 边方向!!! {// 大-小<=c, 有向边(小, 大):c scanf("%d%d%d", &a, &b, &c); if( a > b) swap(t, a, b); addedge(a, b, c); } for( i=0; i < md; ++i ) {// 大-小>=c ==> 小-大<=-c, 有向边(大, 小):-c scanf("%d%d%d", &a, &b, &c); if( a < b ) swap(t, a, b); addedge(a, b, -c); } //for( i=1; i <= n; ++i ) printf("%d\n", dist[i]); printf("%d\n", SPFA(1, n)); } return 0; } int relax(int u, int v, int c){ if( dist[v] > dist[u] + c ) { dist[v] = dist[u] + c; return 1; } return 0; } inline void addedge(int u, int v, int c){ pnt[e] = v; cost[e] = c; nxt[e] = head[u]; head[u] = e++; } int SPFA(int src, int n){// 此处用队列实现 int i; memset(cnt, 0, sizeof(cnt)); // 入队次数 memset(vis, false, sizeof(vis)); for( i=1; i <= n; ++i ) dist[i] = INF; dist[src] = 0; queue Q.push(src); vis[src] = true; ++cnt[src]; while( !Q.empty() ){ int u, v; u = Q.front(); Q.pop(); vis[u] = false; for( i=head[u]; i != -1; i=nxt[i] ){ v = pnt[i]; if( 1 == relax(u, v, cost[i]) && !vis[v] ) { Q.push(v); vis[v] = true; if( (++cnt[v]) > n ) return -1; // cnt[i]为入队列次数,用来判断是否存在负权回路 } } } if( dist[n] == INF ) return -2; // src与n不可达,有些题目可省!!! return dist[n]; // 返回src到n的最短距离,根据题意不同而改变 } /*==================================================*\ | dij变形,可以证明每个点经过的次数为小于等于K,所有把dij的数组dist | 由一维变成2维,记录经过该点1次,2次。。。k次的最小值。 | 输出dist[n-1][k]即可 \*==================================================*/ //WHU1603 int g[1010][1010]; int n,m,x; const int INF=1000000000; int v[1010]; int dist[1010][20]; int main(){ while (scanf("%d%d%d",&n,&m,&x)!=EOF){ for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) g[i][j]=INF; for (int i=0;i int p,q,r; scanf("%d%d%d",&p,&q,&r); if (r } for (int i=1;i<=n;i++){ v[i]=0; for (int j=0;j<=x;j++) dist[i][j]=INF; } dist[1][0]=0; dist[0][0]=INF; while (1){ int k=0; for (int i=1;i<=n;i++) if (v[i] k=i; if (k==0) break; if (k==n && v[n]==x-1) break; for (int i=1;i<=n;i++){ if (v[i] dist[i][x]=dist[k][v[k]]+g[k][i]; for (int j=x;j>0;j--) if (dist[i][j] swap(dist[i][j],dist[i][j-1]); } } v[k]++; } if (dist[n][x-1] else printf("-1\n"); } return 0; } /*==================================================*\ | A* 估价函数 fi为到当前点走过的路经长度, hi为该点到终点的长度 | gi=hi+fi; \*==================================================*/ //WHU1603 int n,m,x,ct; int g[1010][1010],gr[1010][1010]; int dist[1010],v[1010]; const int INF=1000000000; struct node{ int id,fi,gi; friend bool operator <(node a,node b){ if (a.gi==b.gi) return a.fi>b.fi; return a.gi>b.gi; } }s[2000010]; int init(){ for (int i=0;i<=n;i++){ dist[i]=INF; v[i]=1; } dist[n-1]=0; for (int i=0;i int k=n; for (int j=0;j if (v[j] && dist[j] if (k==n) break; v[k]=0; for (int j=0;j if (v[j] && dist[k]+gr[k][j] dist[j]=dist[k]+gr[k][j]; } return 1; } int solve(){ if (dist[0]==INF) return -1; ct=0; s[ct].id=0; s[ct].fi=0; s[ct++].gi=dist[0]; int cnt=0; while (ct){ int id=s[0].id,fi=s[0].fi,gi=s[0].gi; if (id==n-1) cnt++; if (cnt==x) return fi; pop_heap(s,s+ct); ct--; for (int j=0;j if (g[id][j] s[ct].id=j; s[ct].fi=fi+g[id][j]; s[ct++].gi=s[ct].fi+dist[j]; push_heap(s,s+ct); } } return -1; } int main(){ while (scanf("%d%d%d",&n,&m,&x)!=EOF){ for (int i=0;i for (int j=0;j gr[i][j]=g[i][j]=INF; for (int i=0;i int x,y,z; scanf("%d%d%d",&x,&y,&z); x--,y--; g[x][y]=z; gr[y][x]=z; } init(); printf("%d\n",solve()); } return 0; } /*==================================================*\ | INIT: cost[][]耗费矩阵(inf为无穷大); | CALL: prim(cost, n); 返回-1代表原图不连通; \*==================================================*/ #define typec int // type of cost const typec inf = 0x3f3f3f3f; // max of cost int vis[V]; typec lowc[V]; typec prim(typec cost[][V], int n) // vertex: 0 ~ n-1 { int i, j, p; typec minc, res = 0; memset(vis, 0, sizeof(vis)); vis[0] = 1; for (i=1; i for (i=1; i minc = inf; p = -1; for (j=0; j if (0 == vis[j] && minc > lowc[j]) { minc = lowc[j]; p = j; } if (inf == minc) return -1; // 原图不连通 res += minc; vis[p] = 1; for (j=0; j if (0 == vis[j] && lowc[j] > cost[p][j]) lowc[j] = cost[p][j]; } return res; } /*==================================================*\ \*==================================================*/ 结论 次小生成树可由最小生成树换一条边得到. 证明: 可以证明下面一个强一些的结论: 算法:只要充分利用以上结论, 即得V^2的算法. 具体如下: /*==================================================*\ \*==================================================*/ 数据结构:并查集 算法:改进Kruskal 根据Kruskal算法思想,图中的生成树在连完第n-1条边前,都是一个最小生成森林,每次贪心的选择两个不属于同一连通分量的树(如果连接一个连通分量,因为不会减少块数,那么就是不合算的)且用最“便宜”的边连起来,连接n-1次后就形成了一棵MST,n-2次就形成了一个两棵树的最小生成森林,n-3,……,n-k此后就形成了k颗树的最小生成森林,就是题目要求求解的。 /*==================================================*\ | INIT: eg置为边表; res置为0; cp[i]置为i; | CALL: dirtree(root, nv, ne); res是结果; \*==================================================*/ #define typec int // type of res const typec inf = 0x3f3f3f3f; // max of res typec res, dis[V]; int to[V], cp[V], tag[V]; struct Edge { int u, v; typec c; } eg[E]; int iroot(int i){ if (cp[i] == i) return i; return cp[i] = iroot(cp[i]); } int dirtree(int root, int nv, int ne) // root: 树根 { // vertex: 0 ~ n-1 int i, j, k, circle = 0; memset(tag, -1, sizeof(tag)); memset(to, -1, sizeof(to)); for (i = 0; i < nv; ++i) dis[i] = inf; for (j = 0; j < ne; ++j) { i = iroot(eg[j].u); k = iroot(eg[j].v); if (k != i && dis[k] > eg[j].c) { dis[k] = eg[j].c; to[k] = i; } } to[root] = -1; dis[root] = 0; tag[root] = root; for (i = 0; i < nv; ++i) if (cp[i] == i && -1 == tag[i]){ j = i; for ( ; j != -1 && tag[j] == -1; j = to[j]) tag[j] = i; if (j == -1) return 0; if (tag[j] == i) { circle = 1; tag[j] = -2; for (k = to[j]; k != j; k = to[k]) tag[k] = -2; } } if (circle) { for (j = 0; j < ne; ++j) { i = iroot(eg[j].u); k = iroot(eg[j].v); if (k != i && tag[k] == -2) eg[j].c -= dis[k]; } for (i = 0; i < nv; ++i) if (tag[i] == -2) { res += dis[i]; tag[i] = 0; for (j = to[i]; j != i; j = to[j]) { res += dis[j]; cp[j] = i; tag[j] = 0; } } if (0 == dirtree(root, nv, ne)) return 0; } else { for (i = 0; i < nv; ++i) if (cp[i] == i) res += dis[i]; } return 1; // 若返回0代表原图不连通 } /*==================================================*\ | G(V, E), A是V的一个子集, 求至少包含A中所有点的最小子树. | 时间复杂度: O(N^3 + N * 2^A * (2^A + N)) | INIT: d[][]距离矩阵; id[]置为集合A中点的标号; | CALL: steiner(int n, int a); | main()函数解决的题目: Ticket to Ride, NWERC 2006/2007 | 给4个点对(a1, b1) ... (a4, b4), | 求min(sigma(dist[ai][bi])),其中重复的路段只能算一次. | 这题要找出一个steiner森林, 最后要对森林中树的个数进行枚举 \*==================================================*/ #define typec int // type of cost const typec inf = 0x3f3f3f3f; // max of cost int vis[V], id[A]; //id[]: A中点的标号 typec d[V][V], dp[1<点v到点集i的最短距离 void steiner(int n, int a){ int i, j, k, mx, mk, top = (1 << a); for (k = 0; k < n; k++) for (i = 0; i < n; i++) for (j = 0; j < n; j++) if (d[i][j] > d[i][k] + d[k][j]) d[i][j] = d[i][k] + d[k][j]; for (i = 0; i < a; i++) { // vertex: 0 ~ n-1 for (j = 0; j < n; j++) dp[1 << i][j] = d[j][ id[i] ]; } for (i = 1; i < top; i++) { if ( 0 == (i & (i - 1)) ) continue; memset(vis, 0, sizeof(vis)); for (k = 0; k < n; k++) { // init for (dp[i][k] = inf, j = 1; j < i; j++) if ((i | j) == i && dp[i][k] > dp[j][k] + dp[i - j][k]) dp[i][k] = dp[j][k] + dp[i - j][k]; } for (j = 0; mx = inf, j < n; j++) { // update for (k = 0; k < n; k++) if (dp[i][k] <= mx && 0 == vis[k]) mx = dp[i][mk = k]; for (k = 0, vis[mk] = 1; k < n; k++) if (dp[i][mk] > dp[i][k] + d[k][mk]) dp[i][mk] = dp[i][k] + d[k][mk]; } } } int main(void){ int n, a = 8; // TODO: read data; steiner(n, a); // enum to find the result for (i = 0, b = inf; z = 0, i < 256; b>z ? b=z : b, i++) for (j = 0; y = 0, j < 4; z += !!y * dp[y][x], j++) for (k = 0; k < 8; k += 2) if ((i >> k & 3) == j) y += 3 << k, x = id[k]; // TODO: cout << b << endl; return 0; } /*==================================================*\ | INIT: vec[]为邻接表; stop, cnt, scnt置0; pre[]置-1; | CALL: for(i=0; i \*==================================================*/ vector int id[V], pre[V], low[V], s[V], stop, cnt, scnt; void tarjan(int v, int n) // vertex: 0 ~ n-1 { int t, minc = low[v] = pre[v] = cnt++; vector s[stop++] = v; for (pv = vec[v].begin(); pv != vec[v].end(); ++pv) { if(-1 == pre[*pv]) tarjan(*pv, n); if(low[*pv] < minc) minc=low[*pv]; } if(minc < low[v]) { low[v] = minc; return; } do { id[t = s[--stop]] = scnt; low[t] = n; } while(t != v); ++scnt; // 强连通分量的个数 } /*==================================================*\ | INIT: g[][]置为邻接矩阵; | CALL: mcs(n); peo(n); | 第一步: 给节点编号 mcs(n) | 设已编号的节点集合为A, 未编号的节点集合为B | 开始时A为空, B包含所有节点. | for num=n-1 downto 0 do { | 在B中找节点x, 使与x相邻的在A集合中的节点数最多, | 将x编号为num, 并从B移入A. | } | 第二步: 检查 peo(n) | for num=0 to n-1 do { | 对编号为num的点x, 设所有编号>num且与x相邻的点集为C | 在C中找出编号最小的节点y, | 若C中存在点z!=y, 使得y与z之间无边, 则此图不是弦图. | } | 检查完了, 则此图是弦图. \*==================================================*/ int g[V][V], order[V], inv[V], tag[V]; void mcs(int n){ int i, j, k; memset(tag, 0, sizeof(tag)); memset(order, -1, sizeof(order)); for (i = n - 1; i >= 0; i--) { // vertex: 0 ~ n-1 for (j = 0; order[j] >= 0; j++) ; for (k = j + 1; k < n; k++) if (order[k] < 0 && tag[k] > tag[j]) j = k; order[j] = i, inv[i] = j; for (k = 0; k < n; k++) if (g[j][k]) tag[k]++; } } int peo(int n){ int i, j, k, w, min; for (i = n - 2; i >= 0; i--) { j = inv[i], w = -1, min = n; for (k = 0; k < n; k++) if (g[j][k] && order[k] > order[j] && order[k] < min) min = order[k], w=k; if (w < 0) continue; for (k = 0; k < n; k++) if (g[j][k] && order[k] > order[w] && !g[k][w]) return 0; // no } return 1; // yes } /*==================================================*\ | INIT: g[][]置为邻接矩阵; | CALL: cardinality(n); tag[i]为排列中第i个点的标号; | The graph with the property mentioned above | is called chordal graph. A permutation s = [v1 , v2, | ..., vn] of the vertices of such graph is called a | perfect elimination order if each vi is a simplicial | vertex of the subgraph of G induced by {vi ,..., vn}. | A vertex is called simplicial if its adjacency set | induces a complete subgraph, that is, a clique (not | necessarily maximal). The perfect elimination order | of a chordal graph can be computed as the following: \*==================================================*/ procedure maximum cardinality search(G, s) for all vertices v of G do set label[v] to zero end for for all i from n downto 1 do choose an unnumbered vertex v with largest label set s(v) to i{number vertex v} for all unnumbered vertices w adjacent to vertex v do increment label[w] by one end for end for end procedure int tag[V], g[V][V], deg[V], vis[V]; void cardinality(int n) { int i, j, k; memset(deg, 0, sizeof(deg)); memset(vis, 0, sizeof(vis)); for (i = n - 1; i >= 0; i--) { for (j = 0, k = -1; j < n; j++) if (0 == vis[j]) { if (k == -1 || deg[j] > deg[k]) k = j; } vis[k] = 1, tag[i] = k; for (j = 0; j if (0 == vis[j] && g[k][j]) deg[j]++; } } /*==================================================*\ \*==================================================*/ const int N = 1001; struct People{ bool state; int opp, tag; int list[N]; // man使用 int priority[N]; // woman使用, 有必要的话可以和list合并,以节省空间 void Init(){ state = tag = 0; } }man[N], woman[N]; struct R{ int opp; int own; }requst[N]; int n; void Input(void); void Output(void); void stableMatching(void); int main(void){ //... Input(); stableMatching(); Output(); //... return 0; } void Input(void){ scanf("%d\n", &n); int i, j, ch; for( i=0; i < n; ++i ) { man[i].Init(); for( j=0; j < n; ++j ){ //按照man的意愿递减排序 scanf("%d", &ch); man[i].list[j] = ch-1; } } for( i=0; i < n; ++i ) { woman[i].Init(); for( j=0; j < n; ++j ){ //按照woman的意愿递减排序,但是,存储方法与man不同!!!! scanf("%d", &ch); woman[i].priority[ch-1] = j; } } } void stableMatching(void){ int k; for( k=0; k < n; +k ){ int i, id = 0; for( i=0; i < n; ++i ) if( man[i].state == 0 ){ requst[id].opp = man[i].list[ man[i].tag ]; requst[id].own = i; man[i].tag += 1; ++id; } if( id == 0 ) break; for( i=0; i < id; ++i ){ if( woman[requst[i].opp].state == 0 ){ woman[requst[i].opp].opp = requst[i].own; woman[requst[i].opp].state = 1; man[requst[i].own].state = 1; man[requst[i].own].opp = requst[i].opp; } else{ if( woman[requst[i].opp].priority[ woman[requst[i].opp].opp ] > woman[requst[i].opp].priority[requst[i].own] ){ // man[ woman[requst[i].opp].opp ].state = 0; woman[ requst[i].opp ].opp = requst[i].own; man[requst[i].own].state = 1; man[requst[i].own].opp = requst[i].opp; } } } } } void Output(void){ for( int i=0; i < n; ++i ) printf("%d\n", man[i].opp+1); } /*==================================================*\ | INIT:edge[][]置为图的邻接矩阵;count[0…i…n-1]:顶点i的入度. \*==================================================*/ void TopoOrder(int n){ int i, top = -1; for( i=0; i < n; ++i ) if( count[i] == 0 ){ // 下标模拟堆栈 count[i] = top; top = i; } for( i=0; i < n; ++i ) if( top == -1 ) { printf("存在回路\n"); return ; } else{ int j = top; top = count[top]; printf("%d", j); for( int k=0; k < n; ++k ) if( edge[j][k] && (--count[k]) == 0 ){ count[k] = top; top = k; } } } /*==================================================*\ | DFS / BFS / 并查集 \*==================================================*/ /*==================================================*\ \*==================================================*/ //返回分支数,id返回1..分支数的值 //传入图的大小n和邻接阵mat,不相邻点边权0 #define MAXN 100 void search(int n,int mat[][MAXN],int* dfn,int* low,int now,int& cnt,int& tag,int* id,int* st,int& sp){ int i,j; dfn[st[sp++]=now]=low[now]=++cnt; for (i=0;i if (mat[now][i]){ if (!dfn[i]){ search(n,mat,dfn,low,i,cnt,tag,id,st,sp); if (low[i] low[now]=low[i]; } else if (dfn[i]
for (j=0;j if (j low[now]=dfn[i]; } } if (low[now]==dfn[now]) for (tag++;st[sp]!=now;id[st[--sp]]=tag); } int find_components(int n,int mat[][MAXN],int* id){ int ret=0,i,cnt,sp,st[MAXN],dfn[MAXN],low[MAXN]; for (i=0;i for (sp=cnt=i=0;i if (!dfn[i]) search(n,mat,dfn,low,i,cnt,ret,id,st,sp); return ret; } //有向图强连通分支,bfs邻接阵形式,O(n^2) //返回分支数,id返回1..分支数的值 //传入图的大小n和邻接阵mat,不相邻点边权0 #define MAXN 100 int find_components(int n,int mat[][MAXN],int* id){ int ret=0,a[MAXN],b[MAXN],c[MAXN],d[MAXN],i,j,k,t; for (k=0;k for (k=0;k if (!id[k]){ for (i=0;i a[i]=b[i]=c[i]=d[i]=0; a[k]=b[k]=1; for (t=1;t;) for (t=i=0;i if (a[i]&&!c[i]) for (c[i]=t=1,j=0;j if (mat[i][j]&&!a[j]) a[j]=1; if (b[i]&&!d[i]) for (d[i]=t=1,j=0;j if (mat[j][i]&&!b[j]) b[j]=1; } for (ret++,i=0;i if (a[i]&b[i]) id[i]=ret; } return ret; } /*==================================================*\ | 点基B满足:对于任意一个顶点Vj,一定存在B中的一个Vi,使得Vi是Vj | 的前代。 \*==================================================*/ //返回点基大小和点基 //传入图的大小n和邻接阵mat,不相邻点边权0 //需要调用强连通分支 #define MAXN 100 int base_vertex(int n,int mat[][MAXN],int* sets){ int ret=0,id[MAXN],v[MAXN],i,j; j=find_components(n,mat,id); for (i=0;i for (i=0;i for (j=0;j if (id[i]!=id[j]&&mat[i][j]) v[id[j]-1]=0; for (i=0;i if (v[id[i]-1]) v[id[sets[ret++]=i]-1]=0; return ret; } /*==================================================*\ \*==================================================*/ 朴素算法 令e(u,v)表示u和v之间的连边, 令min(u,v)表示删除u和v之间的连边之后u和v之间的最短路, 最小环则是min(u, v) + e(u, v). 时间复杂度是O(EV^2). 改进算法 在floyd的同时,顺便算出最小环 g[i][j]=i, j之间的边长 dist:=g; for k:=1 to n do begin for i:=1 to k-1 do for j:=i+1 to k-1 do answer:=min(answer, dist[i][j]+g[i][k]+g[k][j]); for i:=1 to n do for j:=1 to n do dist[i][j]:=min(dist[i][j],dist[i][k]+dist[k][j]); end; 最小环改进算法的证明 一个环中的最大结点为k(编号最大), 与他相连的两个点为i, j, 这个环的最短长度为g[i][k]+g[k][j]+i到j的路径中所有结点编号都小于k的最短路径长度. 根据floyd的原理, 在最外层循环做了k-1次之后, dist[i][j]则代表了i到j的路径中所有结点编号都小于k的最短路径 综上所述,该算法一定能找到图中最小环. const int INF = 1000000000; const int N = 110; int n, m; // n:节点个数, m:边的个数 int g[N][N]; // 无向图 int dist[N][N]; // 最短路径 int r[N][N]; // r[i][j]: i到j的最短路径的第一步 int out[N], ct; // 记录最小环 int solve(int i, int j, int k){// 记录最小环 ct = 0; while ( j != i ){ out[ct++] = j; j = r[i][j]; } out[ct++] = i; out[ct++] = k; return 0; } int main(void){ while( scanf("%d%d", &n, &m) != EOF ){ int i, j, k; for ( i=0; i < n; i++ ) for ( j=0; j < n; j++ ){ g[i][j] = INF; r[i][j] = i; } for ( i=0; i < m; i++ ){ int x, y, l; scanf("%d%d%d", &x, &y, &l); --x; --y; if ( l < g[x][y] ) g[x][y] = g[y][x] = l; } memmove(dist, g, sizeof(dist)); int Min = INF; // 最小环 for ( k=0; k < n; k++ ){//Floyd for ( i=0; i < k; i++ )// 一个环中的最大结点为k(编号最大) if ( g[k][i] < INF ) for ( j=i+1; j < k; j++ ) if ( dist[i][j] < INF && g[k][j] < INF && Min > dist[i][j]+g[k][i]+g[k][j] ){ Min = dist[i][j]+g[k][i]+g[k][j]; solve(i, j, k); // 记录最小环 } for ( i=0; i < n; i++ ) if ( dist[i][k] < INF ) for ( j=0; j < n; j++ ) if ( dist[k][j] < INF && dist[i][j] > dist[i][k]+dist[k][j] ){ dist[i][j] = dist[i][k]+dist[k][j]; r[i][j] = r[k][j]; } } if ( Min < INF ){ for ( ct--; ct >= 0; ct-- ){ printf("%d", out[ct]+1); if ( ct ) printf(" "); } } else printf("No solution."); printf("\n"); } return 0; } /*==================================================*\ * N个集团,每个集团2个人,现在要想选出尽量多的人, * 且每个集团只能选出一个人。如果两人有矛盾,他们不能同时被选中 * 问最多能选出多少人 \*==================================================*/ const int MAXN=3010; int n,m; int g[3010][3010],ct[3010],f[3010]; int x[3010],y[3010]; int prev[MAXN], low[MAXN], stk[MAXN], sc[MAXN]; int cnt[MAXN]; int cnt0, ptr, cnt1; void dfs(int w){ int min(0); prev[w] = cnt0++; low[w] = prev[w]; min = low[w]; stk[ptr++] = w; for(int i = 0; i < ct[w]; ++i){ int t = g[w][i]; if(prev[t] == -1) dfs(t); if(low[t] < min) min = low[t]; } if(min < low[w]){ low[w] = min; return; } do{ int v = stk[--ptr]; sc[v] = cnt1; low[v] = MAXN; }while(stk[ptr] != w); ++cnt1; } void Tarjan(int N){ //传入N为点数,结果保存在sc数组中,同一标号的点在同一个强连通分量内, //强连通分量数为cnt1 cnt0 = cnt1 = ptr = 0; int i; for(i = 0; i < N; ++i) prev[i] = low[i] = -1; for(i = 0; i < N; ++i) if(prev[i] == -1) dfs(i); } int solve(){ Tarjan(n); for (int i=0;i if (sc[i]==sc[f[i]]) return 0; } return 1; } int check(int Mid){ for (int i=0;i ct[i]=0; for (int i=0;i g[f[x[i]]][ct[f[x[i]]]++]=y[i]; g[f[y[i]]][ct[f[y[i]]]++]=x[i]; } return solve(); } int main(){ while (scanf("%d%d",&n,&m)!=EOF && n+m){ for (int i=0;i int p,q; scanf("%d%d",&p,&q); f[p]=q,f[q]=p; } for (int i=0;i n*=2; int Min=0,Max=m+1; while (Min+1 int Mid=(Min+Max)/2; if (check(Mid)) Min=Mid; else Max=Mid; } printf("%d\n",Min); } return 0; } /*==================================================*\ | INIT: g[][]邻接矩阵; | CALL: res = MaxMatch(); | 优点:实现简洁容易理解,适用于稠密图,DFS找增广路快。 | 找一条增广路的复杂度为O(E),最多找V条增广路,故时间复杂度为O(VE) \*==================================================*/ const int MAXN = 1000; int uN, vN; // u, v数目,要初始化!!! bool g[MAXN][MAXN]; // g[i][j] 表示xi与yj相连 int xM[MAXN], yM[MAXN]; // 输出量 bool chk[MAXN]; // 辅助量检查某轮y[v]是否被check bool SearchPath(int u){ int v; for(v = 0; v < vN; v++) if(g[u][v] && !chk[v]) { chk[v] = true; if(yM[v] == -1 || SearchPath(yM[v])) { yM[v] = u; xM[u] = v; return true ; } } return false ; } int MaxMatch(){ int u, ret = 0 ; memset(xM, -1, sizeof (xM)); memset(yM, -1, sizeof (yM)); for(u = 0; u < uN; u++) if(xM[u] == -1){ memset(chk, false, sizeof (chk)); if(SearchPath(u)) ret++; } return ret; } /*==================================================*\ | INIT: g[][]邻接矩阵; | CALL: res = MaxMatch();Nx, Ny初始化!!! | 优点:适用于稀疏二分图,边较少,增广路较短。 | 匈牙利算法的理论复杂度是O(VE) \*==================================================*/ const int MAXN = 1000; int g[MAXN][MAXN], Mx[MAXN], My[MAXN], Nx, Ny; int chk[MAXN], Q[MAXN], prev[MAXN]; int MaxMatch(void) { int res = 0; int qs, qe; memset(Mx, -1, sizeof(Mx)); memset(My, -1, sizeof(My)); memset(chk, -1, sizeof(chk)); for (int i = 0; i < Nx; i++){ if (Mx[i] == -1){ qs = qe = 0; Q[qe++] = i; prev[i] = -1; bool flag = 0; while (qs < qe && !flag){ int u = Q[qs]; for (int v = 0; v < Ny && !flag; v++) if (g[u][v] && chk[v] != i) { chk[v] = i; Q[qe++] = My[v]; if (My[v] >= 0) prev[My[v]] = u; else { flag = 1; int d = u, e = v; while (d != -1) { int t = Mx[d]; Mx[d] = e; My[e] = d; d = prev[d]; e = t; } } } qs++; } if (Mx[i] != -1) res++; } } return res; } | INIT: g[][]邻接矩阵; | CALL: res = MaxMatch(); Nx, Ny要初始化!!! | 时间复杂度为O(V^0.5 E) \*==================================================*/ const int INF = 1 << 28; int g[MAXN][MAXN], Mx[MAXN], My[MAXN], Nx, Ny; int dx[MAXN], dy[MAXN], dis; bool vst[MAXN]; bool searchP(void){ queue dis = INF; memset(dx, -1, sizeof(dx)); memset(dy, -1, sizeof(dy)); for (int i = 0; i < Nx; i++) if (Mx[i] == -1){ Q.push(i); dx[i] = 0; } while (!Q.empty()) { int u = Q.front(); Q.pop(); if (dx[u] > dis) break; for (int v = 0; v < Ny; v++) if (g[u][v] && dy[v] == -1) { dy[v] = dx[u]+1; if (My[v] == -1) dis = dy[v]; else{ dx[My[v]] = dy[v]+1; Q.push(My[v]); } } } return dis != INF; } bool DFS(int u){ for (int v = 0; v < Ny; v++) if (!vst[v] && g[u][v] && dy[v] == dx[u]+1) { vst[v] = 1; if (My[v] != -1 && dy[v] == dis) continue; if (My[v] == -1 || DFS(My[v])) { My[v] = u; Mx[u] = v; return 1; } } return 0; } int MaxMatch(void){ int res = 0; memset(Mx, -1, sizeof(Mx)); memset(My, -1, sizeof(My)); while (searchP()) { memset(vst, 0, sizeof(vst)); for (int i = 0; i < Nx; i++) if (Mx[i] == -1 && DFS(i)) res++; } return res; } /*=================================================*\ | 邻接距阵形式,复杂度O(m*m*n) 返回最佳匹配值,传入二分图大小m,n | 邻接距阵mat,表示权, match1,match2返回一个最佳匹配,未匹配顶点 | match值为-1, 一定注意m<=n,否则循环无法终止,最小权匹配可将权值 | 取相反数 | 初始化:for( i=0 ; i < MAXN ; ++i ) for( j=0 ; j < MAXN ; ++j ) mat[i][j] = -inf; | 对于存在的边:mat[i][j] = val ; // 注意,不能有负值 \*==================================================*/ #include #define MAXN 310 #define inf 1000000000 #define _clr(x) memset(x,-1,sizeof(int)*MAXN) int kuhn_munkras(int m,int n,int mat[][MAXN],int* match1,int* match2){ int s[MAXN],t[MAXN],l1[MAXN],l2[MAXN],p,q,ret=0,i,j,k; for (i=0;i for (l1[i]=-inf,j=0;j l1[i]=mat[i][j]>l1[i]?mat[i][j]:l1[i]; if( l1[i] == -inf ) return -1;// 无法匹配! } for (i=0;i for (_clr(match1),_clr(match2),i=0;i for (_clr(t),s[p=q=0]=i;p<=q&&match1[i]<0;p++) for (k=s[p],j=0;j if (l1[k]+l2[j]==mat[k][j]&&t[j]<0){ s[++q]=match2[j],t[j]=k; if (s[q]<0) for (p=j;p>=0;j=p) match2[j]=k=t[j],p=match1[k],match1[k]=j; } if (match1[i]<0){ for (i--,p=inf,k=0;k<=q;k++) for (j=0;j if (t[j]<0&&l1[s[k]]+l2[j]-mat[s[k]][j]
p=l1[s[k]]+l2[j]-mat[s[k]][j]; for (j=0;j for (k=0;k<=q;l1[s[k++]]-=p); } } for (i=0;i {// if处理无匹配的情况!! if( match[i] < 0 ) return -1; if( mat[i][match[i]] <= -inf ) return -1; ret+=mat[i][match1[i]]; } return ret; } /*==================================================*\ | INIT: 初始化邻接矩阵g[][]; | CALL: res = mincut(n); | 注: Stoer-Wagner Minimum Cut; | 找边的最小集合,若其被删去则图变得不连通(我们把这种形式称为最小 | 割问题) \*==================================================*/ #define typec int // type of res const typec inf = 0x3f3f3f3f; // max of res const typec maxw = 1000; // maximum edge weight typec g[V][V], w[V]; int a[V], v[V], na[V]; typec mincut(int n){ int i, j, pv, zj; typec best = maxw * n * n; for (i = 0; i < n; i++) v[i] = i; // vertex: 0 ~ n-1 while (n > 1) { for (a[v[0]] = 1, i = 1; i < n; i++) { a[v[i]] = 0; na[i - 1] = i; w[i] = g[v[0]][v[i]]; } for (pv = v[0], i = 1; i < n; i++ ) { for (zj = -1, j = 1; j < n; j++ ) if (!a[v[j]] && (zj < 0 || w[j] > w[zj])) zj = j; a[v[zj]] = 1; if (i == n - 1) { if (best > w[zj]) best = w[zj]; for (i = 0; i < n; i++) g[v[i]][pv] = g[pv][v[i]] += g[v[zj]][v[i]]; v[zj] = v[--n]; break; } pv = v[zj]; for (j = 1; j < n; j++) if(!a[v[j]]) w[j] += g[v[zj]][v[j]]; } } return best; } /*==================================================*\ | INIT: up[][]为容量上界; low[][]为容量下界; | CALL: mf = limitflow(n,src,sink); flow[][]为流量分配; | 另附: 循环流问题 | 描述: 无源无汇的网络N, 设N是具有基础有向图D=(V,A)的网络. | l和c分别为容量下界和容量上界. 如果定义在A上的函数 | f满足: f(v, V) = f(V, v). V中任意顶点v, | l(a)<=f(a)<=c(a), 则称f为网络N的循环流. | 解法: 添加一个源s和汇t,对于每个下限容量l不为0的边(u, v), | 将其下限去掉, 上限改为c-l, 增加两条边(u, t), (s, v), | 容量均为l. 原网络存在循环流等价于新网络最大流是满流. \*==================================================*/ int up[N][N], low[N][N], flow[N][N]; int pv[N], que[N], d[N]; void maxflow(int n, int src, int sink) { // BFS增广, O(E * maxflow) int p, q, t, i, j; do{ for (i = 0; i < n; pv[i++] = 0) ; pv[t = src] = src + 1; d[t] = inf; for (p=q=0; p<=q && !pv[sink]; t=que[p++]) for (i=0; i if(!pv[i]&&up[t][i]&&(j=up[t][i]-flow[t][i])>0) pv[que[q++]=i]=+t+1, d[i]=d[t] else if (!pv[i]&&up[i][t]&&(j=flow[i][t])>0) pv[que[q++]=i]=-t-1, d[i]=d[t] } for (i=sink; pv[i] && i!=src; ) { if(pv[i]>0)flow[pv[i]-1][i]+=d[sink],i=pv[i]-1; else flow[i][-pv[i]-1]-=d[sink], i=-pv[i]-1; } } while (pv[sink]); } int limitflow(int n, int src, int sink) { int i, j, sk, ks; if (src == sink) return inf; up[n][n+1] = up[n+1][n] = up[n][n] = up[n+1][n+1] = 0; for (i = 0; i < n; i++) { up[n][i] = up[i][n] = up[n+1][i] = up[i][n+1] = 0; for (j = 0; j < n; j++) { up[i][j] -= low[i][j]; up[n][i] += low[j][i]; up[i][n+1] += low[i][j]; } } sk = up[src][sink]; ks = up[sink][src]; up[src][sink] = up[sink][src] = inf; maxflow(n+2, n, n+1); for (i = 0; i < n; i++) if (flow[n][i] < up[n][i]) return -1; flow[src][sink] = flow[sink][src] = 0; up[src][sink] = sk; up[sink][src] = ks; // ! min: src <- sink; max: src -> sink; maxflow(n, sink, src); for (i = 0; i < n; i++) for (j = 0; j < n; j++) { up[i][j] += low[i][j]; flow[i][j] += low[i][j]; } for (j = i = 0; i < n; j += flow[src][i++]) ; return j; } /*==================================================*\ | INIT: ne=2; head[]置为0; addedge()加入所有弧; | CALL: flow(n, s, t); \*==================================================*/ #define typec int // type of cost const typec inf = 0x3f3f3f3f; // max of cost struct edge { int x, y, nxt; typec c; } bf[E]; int ne, head[N], cur[N], ps[N], dep[N]; void addedge(int x, int y, typec c) { // add an arc(x -> y, c); vertex: 0 ~ n-1; bf[ne].x = x; bf[ne].y = y; bf[ne].c = c; bf[ne].nxt = head[x]; head[x] = ne++; bf[ne].x = y; bf[ne].y = x; bf[ne].c = 0; bf[ne].nxt = head[y]; head[y] = ne++; } typec flow(int n, int s, int t) { typec tr, res = 0; int i, j, k, f, r, top; while (1) { memset(dep, -1, n * sizeof(int)); for (f = dep[ps[0] = s] = 0, r = 1; f != r; ) for (i = ps[f++], j = head[i]; j; j = bf[j].nxt) { if (bf[j].c && -1 == dep[k = bf[j].y]){ dep[k] = dep[i] + 1; ps[r++] = k; if (k == t) { f = r; break; } } } if (-1 == dep[t]) break; memcpy(cur, head, n * sizeof(int)); for (i = s, top = 0; ; ) { if (i == t) { for (k = 0, tr = inf; k < top; ++k) if (bf[ps[k]].c < tr) tr = bf[ps[f = k]].c; for (k = 0; k < top; ++k) bf[ps[k]].c -= tr, bf[ps[k]^1].c += tr; res += tr; i = bf[ps[top = f]].x; } for (j=cur[i]; cur[i]; j = cur[i] = bf[cur[i]].nxt) if (bf[j].c && dep[i]+1 == dep[bf[j].y]) break; if (cur[i]) { ps[top++] = cur[i]; i = bf[cur[i]].y; } else { if (0 == top) break; dep[i] = -1; i = bf[ps[--top]].x; } } } return res; } /*=================================================*\ | INIT: network g; g.build(nv, ne); | CALL: res = g.maxflow(s, t); | 注意: 不要加入指向源点的边, 可能死循环. \*==================================================*/ #define typef int // type of flow const typef inf = 0x3f3f3f3f; // max of flow typef minf(typef a, typef b) { return a < b ? a : b; } struct edge { int u, v; typef cuv, cvu, flow; edge (int x=0, int y=0, typef cu=0, typef cv=0, typef f=0) : u(x), v(y), cuv(cu), cvu(cv), flow(f) {} int other(int p) { return p == u ? v : u; } typef cap(int p) { return p == u ? cuv-flow : cvu+flow; } void addflow(int p, typef f) { flow += (p == u ? f : -f); } }; struct vlist { int lv, next[N], idx[2 * N], v; void clear(int cv) { v = cv; lv = -1; memset(idx, -1, sizeof(idx)); } void insert(int n, int h) { next[n] = idx[h]; idx[h] = n; if (lv < h) lv = h; } int remove() { int r = idx[lv]; idx[lv] = next[idx[lv]]; while (lv >= 0 && idx[lv] == -1) lv--; return r; } bool empty() { return lv < 0; } }; struct network { vector vector vlist list; typef e[N]; int v, s, t, h[N], hn[2 * N], cur[N]; void push(int); void relabel(int); void build(int, int); typef maxflow(int, int); }; void network::push(int u) { edge* te = net[u][cur[u]]; typef ex = minf(te->cap(u), e[u]); int p = te->other(u); if (e[p] == 0 && p != t) list.insert(p, h[p]); te->addflow(u, ex); e[u] -= ex; e[p] += ex; } void network::relabel(int u) { int i, p, mh = 2 * v, oh = h[u]; for (i = net[u].size()-1; i >= 0; i--) { p = net[u][i]->other(u); if (net[u][i]->cap(u) != 0 && mh > h[p] + 1) mh = h[p] + 1; } hn[h[u]]--; hn[mh]++; h[u] = mh; cur[u] = net[u].size()-1; if (hn[oh] != 0 || oh >= v + 1) return; for (i = 0; i < v; i++) if (h[i] > oh && h[i] <= v && i != s) { hn[h[i]]--; hn[v+1]++; h[i] = v + 1; } } typef network::maxflow(int ss, int tt) { s = ss; t = tt; int i, p, u; typef ec; for (i = 0; i < v; i++) net[i].clear(); for (i = eg.size()-1; i >= 0; i--) { net[eg[i].u].push_back(&eg[i]); net[eg[i].v].push_back(&eg[i]); } memset(h, 0, sizeof(h)); memset(hn, 0, sizeof(hn)); memset(e, 0, sizeof(e)); e[s] = inf; for (i = 0; i < v; i++) h[i] = v; queue while (!q.empty()) { p = q.front(); q.pop(); for (i = net[p].size()-1; i >= 0; i--) { u = net[p][i]->other(p); ec = net[p][i]->cap(u); if (ec != 0 && h[u] == v && u != s) { h[u] = h[p] + 1; q.push(u); } } } for (i = 0; i < v; i++) hn[h[i]]++; for (i = 0; i < v; i++) cur[i] = net[i].size()-1; list.clear(v); for (; cur[s] >= 0; cur[s]--) push(s); while (!list.empty()) { for (u = list.remove(); e[u] > 0; ) { if (cur[u] < 0) relabel(u); else if (net[u][cur[u]]->cap(u) > 0 && h[u] == h[net[u][cur[u]]->other(u)]+1) push(u); else cur[u]--; } } return e[t]; } void network::build(int n, int m) { v = n; eg.clear(); int a, b, i; typef l; for (i = 0; i < m; i++) { cin >> a >> b >> l; eg.push_back(edge(a, b, l, 0)); // vertex: 0 ~ n-1 } } /*==================================================*\ | INIT: network g; g.build(v, e); | CALL: g.mincost(s, t); flow=g.flow; cost=g.cost; | 注意: SPFA增广, 实际复杂度远远小于O(V * E); \*==================================================*/ #define typef int // type of flow #define typec int // type of dis const typef inff = 0x3f3f3f3f; // max of flow const typec infc = 0x3f3f3f3f; // max of dis struct network { int nv, ne, pnt[E], nxt[E]; int vis[N], que[N], head[N], pv[N], pe[N]; typef flow, cap[E]; typec cost, dis[E], d[N]; void addedge(int u, int v, typef c, typec w) { pnt[ne] = v; cap[ne] = c; dis[ne] = +w; nxt[ne] = head[u]; head[u] = (ne++); pnt[ne] = u; cap[ne] = 0; dis[ne] = -w; nxt[ne] = head[v]; head[v] = (ne++); } int mincost(int src, int sink) { int i, k, f, r; typef mxf; for (flow = 0, cost = 0; ; ) { memset(pv, -1, sizeof(pv)); memset(vis, 0, sizeof(vis)); for (i = 0; i < nv; ++i) d[i] = infc; d[src] = 0; pv[src] = src; vis[src] = 1; for (f = 0, r = 1, que[0] = src; r != f; ) { i = que[f++]; vis[i] = 0; if (N == f) f = 0; for (k = head[i]; k != -1; k = nxt[k]) if(cap[k] && dis[k]+d[i] < d[pnt[k]]) { d[pnt[k]] = dis[k] + d[i]; if (0 == vis[pnt[k]]) { vis[pnt[k]] = 1; que[r++] = pnt[k]; if (N == r) r = 0; } pv[pnt[k]]=i; pe[pnt[k]]=k; } } if (-1 == pv[sink]) break; for (k = sink, mxf = inff; k != src; k = pv[k]) if (cap[pe[k]] < mxf) mxf = cap[pe[k]]; flow += mxf; cost += d[sink] * mxf; for (k = sink; k != src; k = pv[k]) { cap[pe[k]] -= mxf; cap[pe[k] ^ 1] += mxf; } } return cost; } void build(int v, int e) { nv = v; ne = 0; memset(head, -1, sizeof(head)); int x, y; typef f; typec w; for (int i = 0; i < e; ++i) { cin >> x >> y >> f >> w; // vertex: 0 ~ n-1 addedge(x, y, f, w);// add arc (u->v, f, w) } } } g; /*==================================================*\ | INIT: network g; g.build(nv, ne); | CALL: g.mincost(s, t); flow=g.flow; cost=g.cost; | 注意: 网络中弧的cost需为非负. 若存在负权, 进行如下转化: | 首先如果原图有负环, 则不存在最小费用流. 那么可以用Johnson | 重标号技术把所有边变成正权, 以后每次增广后进行维护, 算法如 | 下: | 1. 用bellman-ford求s到各点的距离phi[]; | 2. 以后每求一次最短路, 设s到各点的最短距离为dis[]: | for i=1 to v do | phi[v] += dis[v]; | 下面的代码已经做了第二步, 如果原图有负权, 添加第一步即可. \*==================================================*/ #define typef int // type of flow #define typec int // type of cost const typef inff = 0x3f3f3f3f; // max of flow const typec infc = 0x3f3f3f3f; // max of cost struct edge { int u, v; typef cuv, cvu, flow; typec cost; edge (int x, int y, typef cu, typef cv, typec cc) :u(x), v(y), cuv(cu), cvu(cv), flow(0), cost(cc){} int other(int p) { return p == u ? v : u; } typef cap(int p) { return p == u ? cuv-flow : cvu+flow; } typec ecost(int p) { if (flow == 0) return cost; else if(flow > 0) return p == u ? cost : -cost; else return p == u ? -cost : cost; } void addFlow(int p, typef f) { flow += (p == u ? f : -f); } }; struct network { vector vector edge *prev[N]; int v, s, t, pre[N], vis[N]; typef flow; typec cost, dis[N], phi[N]; bool dijkstra(); void build(int nv, int ne); typec mincost(int, int); }; bool network::dijkstra() { // 使用O(E * logV)的Dij可降低整体复杂度至 O(E * logV * f) int i, j, p, u; typec md, cw; for (i = 0; i < v; i++) dis[i] = infc; dis[s] = 0; prev[s] = 0; pre[s] = -1; memset(vis, 0, v * sizeof(int)); for (i = 1; i < v; i++) { for (md = infc, j = 0; j < v; j++) if (!vis[j] && md > dis[j]) { md = dis[j]; u = j; } if (md == infc) break; for (vis[u] = 1, j = net[u].size()-1; j >= 0; j--) { edge *ce = net[u][j]; if(ce->cap(u) > 0) { p = ce->other(u); cw = ce->ecost(u) + phi[u] - phi[p]; // !! assert(cw >= 0); if(dis[p] > dis[u]+cw) { dis[p] = dis[u] + cw; prev[p] = ce; pre[p] = u; } } } } return infc != dis[t]; } typec network::mincost(int ss, int tt) { s = ss; t = tt; int i, c; typef ex; flow = cost = 0; memset(phi, 0, sizeof(phi)); // !! 若原图含有负消费的边, 在此处运行Bellmanford // 将phi[i](0<=i<=n-1)置为mindist(s, i). for (i = 0; i < v; i++) net[i].clear(); for (i = eg.size()-1; i >= 0; i--) { net[eg[i].u].push_back(&eg[i]); net[eg[i].v].push_back(&eg[i]); } while(dijkstra()) { for (ex = inff, c = t; c != s; c = pre[c]) if (ex > prev[c]->cap(pre[c])) ex = prev[c]->cap(pre[c]); for (c = t; c != s; c = pre[c]) prev[c]->addFlow(pre[c], ex); flow += ex; cost += ex * (dis[t] + phi[t]); for (i = 0; i < v; i++) phi[i] += dis[i]; } return cost; } void network::build(int nv, int ne) { eg.clear(); v = nv; int x, y; typef f; typec c; for (int i = 0; i < ne; ++i) { cin >> x >> y >> f >> c; eg.push_back(edge(x, y, f, 0, c)); } } /*=================================================*\ \*==================================================*/ #define MAXN 100 #define inf 1000000000 int max_flow(int n,int mat[][MAXN],int source,int sink){ int v[MAXN],c[MAXN],p[MAXN],ret=0,i,j; for (;;){ for (i=0;i v[i]=c[i]=0; for (c[source]=inf;;){ for (j=-1,i=0;i if (!v[i]&&c[i]&&(j==-1||c[i]>c[j])) j=i; if (j<0) return ret; if (j==sink) break; for (v[j]=1,i=0;i if (mat[j][i]>c[i]&&c[j]>c[i]) c[i]=mat[j][i] } for (ret+=j=c[i=sink];i!=source;i=p[i]) mat[p[i]][i]-=j, mat[i][p[i]]+=j; } } int best_edge_cut(int n,int mat[][MAXN],int source,int sink,int set[][2],int& mincost){ int m0[MAXN][MAXN],m[MAXN][MAXN],i,j,k,l,ret=0,last; if (source==sink) return -1; for (i=0;i for (j=0;j m0[i][j]=mat[i][j]; for (i=0;i for (j=0;j m[i][j]=m0[i][j]; mincost=last=max_flow(n,m,source,sink); for (k=0;k for (l=0;l if (m0[k][l]){ for (i=0;i for (j=0;j m[i][j]=m0[i][j]; m[k][l]=0; if (max_flow(n,m,source,sink)==last-mat[k][l]){ set[ret][0]=k; set[ret++][1]=l; m0[k][l]=0; last-=mat[k][l]; } } return ret; } /*=================================================*\ \*==================================================*/ #define MAXN 100 #define inf 1000000000 int max_flow(int n,int mat[][MAXN],int source,int sink){ int v[MAXN],c[MAXN],p[MAXN],ret=0,i,j; for (;;){ for (i=0;i v[i]=c[i]=0; for (c[source]=inf;;){ for (j=-1,i=0;i if (!v[i]&&c[i]&&(j==-1||c[i]>c[j])) j=i; if (j<0) return ret; if (j==sink) break; for (v[j]=1,i=0;i if (mat[j][i]>c[i]&&c[j]>c[i]) c[i]=mat[j][i] } for (ret+=j=c[i=sink];i!=source;i=p[i]) mat[p[i]][i]-=j,mat[i][p[i]]+=j; } } int best_vertex_cut(int n,int mat[][MAXN],int* cost,int source,int sink,int* set,int& mincost){ int m0[MAXN][MAXN],m[MAXN][MAXN],i,j,k,ret=0,last; if (source==sink||mat[source][sink]) return -1; for (i=0;i for (j=0;j m0[i][j]=0; for (i=0;i for (j=0;j if (mat[i][j]) m0[i][n+j]=inf; for (i=0;i m0[n+i][i]=cost[i]; for (i=0;i for (j=0;j m[i][j]=m0[i][j]; mincost=last=max_flow(n+n,m,source,n+sink); for (k=0;k if (k!=source&&k!=sink){ for (i=0;i for (j=0;j m[i][j]=m0[i][j]; m[n+k][k]=0; if (max_flow(n+n,m,source,n+sink)==last-cost[k]){ set[ret++]=k; m0[n+k][k]=0; last-=cost[k]; } } return ret; } /*=================================================*\ \*==================================================*/ #define MAXN 100 #define inf 1000000000 int max_flow(int n,int mat[][MAXN],int source,int sink){ int v[MAXN],c[MAXN],p[MAXN],ret=0,i,j; for (;;){ for (i=0;i v[i]=c[i]=0; for (c[source]=inf;;){ for (j=-1,i=0;i if (!v[i]&&c[i]&&(j==-1||c[i]>c[j])) j=i; if (j<0) return ret; if (j==sink) break; for (v[j]=1,i=0;i if (mat[j][i]>c[i]&&c[j]>c[i]) c[i]=mat[j][i] } for (ret+=j=c[i=sink];i!=source;i=p[i]) mat[p[i]][i]-=j,mat[i][p[i]]+=j; } } int min_edge_cut(int n,int mat[][MAXN],int source,int sink,int set[][2]){ int m0[MAXN][MAXN],m[MAXN][MAXN],i,j,k,l,ret=0,last; if (source==sink) return -1; for (i=0;i for (j=0;j m0[i][j]=(mat[i][j]!=0); for (i=0;i for (j=0;j m[i][j]=m0[i][j]; last=max_flow(n,m,source,sink); for (k=0;k for (l=0;l if (m0[k][l]){ for (i=0;i for (j=0;j m[i][j]=m0[i][j]; m[k][l]=0; if (max_flow(n,m,source,sink) set[ret][0]=k; set[ret++][1]=l; m0[k][l]=0; last--; } } return ret; } /*=================================================*\ \*==================================================*/ #define MAXN 100 #define inf 1000000000 int max_flow(int n,int mat[][MAXN],int source,int sink){ int v[MAXN],c[MAXN],p[MAXN],ret=0,i,j; for (;;){ for (i=0;i v[i]=c[i]=0; for (c[source]=inf;;){ for (j=-1,i=0;i if (!v[i]&&c[i]&&(j==-1||c[i]>c[j])) j=i; if (j<0) return ret; if (j==sink) break; for (v[j]=1,i=0;i if (mat[j][i]>c[i]&&c[j]>c[i]) c[i]=mat[j][i] } for (ret+=j=c[i=sink];i!=source;i=p[i]) mat[p[i]][i]-=j,mat[i][p[i]]+=j; } } int min_vertex_cut(int n,int mat[][MAXN],int source,int sink, int* set){ int m0[MAXN][MAXN],m[MAXN][MAXN],i,j,k,ret=0,last; if (source==sink||mat[source][sink]) return -1; for (i=0;i for (j=0;j m0[i][j]=0; for (i=0;i for (j=0;j if (mat[i][j]) m0[i][n+j]=inf; for (i=0;i m0[n+i][i]=1; for (i=0;i for (j=0;j m[i][j]=m0[i][j]; last=max_flow(n+n,m,source,n+sink); for (k=0;k if (k!=source&&k!=sink){ for (i=0;i for (j=0;j | 无向图找桥
| 无向图连通度(割)
| 最大团问题 DP + DFS
| 欧拉路径O(E)
| Dijkstra数组实现O(N^2)
| Dijkstra O(E * log E)
| BellmanFord单源最短路O(VE)
| SPFA(Shortest Path Faster Algorithm)
| 第K短路(Dijkstra)
| 第K短路(A*)
| Prim求MST
| 次小生成树O(V^2)
T是某一棵最小生成树,T0是任一棵异于T的树,通过变换T0 --> T1 --> T2 --> ... --> Tn (T) 变成最小生成树.所谓的变换是,每次把T_i中的某条边换成T中的一条边, 而且树T_(i+1)的权小于等于T_i的权.
具体操作是:
step 1. 在T_i中任取一条不在T中的边u_v.
step 2. 把边u_v去掉,就剩下两个连通分量A和B,在T中,必有唯一的边u'_v' 连结A和B.
step 3. 显然u'_v'的权比u_v小 (否则,u_v就应该在T中).把u'_v'替换u_v即得树T_(i+1).
特别地:取Tn为任一棵次小生成树,T_(n-1) 也就是次小生成树且跟T差一条边. 结论得证.
step 1. 先用prim求出最小生成树T. 在prim的同时,用一个矩阵max[u][v] 记录在T中连结任意两点u,v的唯一的路中权值最大的那条边的权值. (注意这里). 这是很容易做到的,因为prim是每次增加一个结点s, 而设已经标号了的结点集合为W, 则W中所有的结点到s的路中的最大权值的边就是当前加入的这条边. step 1 用时 O(V^2).
step 2. 枚举所有不在T中的边u_v, 加入边u_v替换权为max[u][v]的边. 不断更新求最小值,即次小生成树. step 2 用时 O(E).故总时间为O(V^2). | 最小生成森林问题(k颗树)O(mlogm).
| 有向图最小树形图
| Minimal Steiner Tree
| Tarjan强连通分量
| 弦图判断
| 弦图的perfect elimination点排列
| 稳定婚姻问题 O(n^2)
| 拓扑排序
| 无向图连通分支(dfs/bfs邻接阵)
| 有向图强连通分支(dfs/bfs邻接阵)O(n^2)
| 有向图最小点基(邻接阵)O(n^2)
| Floyd求最小环
| 2-sat问题
Network 网络流
| 二分图匹配(匈牙利算法DFS实现)
| 二分图匹配(匈牙利算法BFS实现)
/*==================================================*\ | 二分图匹配(Hopcroft-Carp的算法)
const int MAXN = 3001; | 二分图最佳匹配(kuhn munkras算法O(m*m*n))
| 无向图最小割 O(N^3)
| 有上下界的最小(最大)流
| Dinic最大流 O(V^2 * E)
| HLPP最大流 O(V^3)
| 最小费用流 O(V * E * f)
| 最小费用流 O(V^2 * f)
| 最佳边割集
| 最佳点割集
| 最小边割集
| 最小点割集(点连通度)