ACM-图论完全总结(知识点+模板)

目录

  1. 图的类型与性质
    1.1 欧拉图
    1.2 哈密尔顿图

  2. 拓扑排序

  3. 最短路
    3.1 Dijkstra
        3.1.1 优先队列优化
        3.1.2 堆优化
        3.1.3 路径还原
    3.2 Bellman-Ford
        3.2.1 判断负环
    3.3 Floyd
    3.4 SPFA
    3.5 K短路
    3.6 差分约束系统

  4. 最小生成树
    4.1 Prmie
    4.2 Kruskal

  5. 二分图
    5.1 二分图判断
    5.2 二分图匹配(匈牙利算法)
    5.3 带权二分图匹配(KM算法)

  6. 最大团
    6.1 Bron-Kerbosch

  7. 连通图
    7.1 Tarjan

  8. 2-SAT

  9. 网络流
    9.1 最大流(Dicnic)
    9.2 最小费用流(spfa费用流)
    9.3 有界网络流
        9.3.1 无源汇上下界可行流
        9.3.2 有源汇上下界可行流
        9.3.3 有源汇上下界最大流
        9.3.4 有源汇上下界最小流
        9.3.5 有源汇上下界费用流


一.图的类型与性质

1.欧拉图

定义:

欧拉图是指通过图(无向图或有向图)中所有边且每边仅通过一次通路,相应的回路称为欧拉回路

性质:

1.无向连通图 G 是欧拉图,当且仅当 G 不含奇数度结点( G 的所有结点度数为偶数);
2.无向连通图G 含有欧拉通路,当且仅当 G 有零个或两个奇数度的结点;
3.有向连通图 D 是欧拉图,当且仅当该图为连通图且 D 中每个结点的入度=出度;
4.有向连通图 D 含有欧拉通路,当且仅当该图为连通图且 D 中除两个结点外,其余每个结点的入度=出度,且此两点满足 deg-(u)-deg+(v)=±1 。(起始点s的入度=出度-1,结束点t的出度=入度-1 或两个点的入度=出度);
5.一个非平凡连通图是欧拉图当且仅当它的每条边属于奇数个环;
6.如果图G是欧拉图且 H = G-uv,则 H 有奇数个 u,v-迹仅在最后访问 v ;同时,在这一序列的 u,v-迹中,不是路径的迹的条数是偶数。

判断欧拉图

void dfs(int now)
{
     int k;
     for(k=p[now];k!=-1;k=e[k].next)
     {
         if(!vst[k])
         {
             vst[k]=true;
             vst[k^1]=true;
             dfs(e[k].to);
             ans[ansi++]=k;
         }
     }
}

//dfs结束后,ans中存储的就是欧拉图,可通过vst判断图的联通性,每个点都被更新则全联通

2.哈密尔顿图

定义:

通过图G的每个结点一次,且仅一次的通路(回路),就是哈密顿通路(回路)。存在哈密顿回路的图就是哈密顿图。

性质:

(1)若图的最小度不小于顶点数的一半,则图是哈密顿图;
(2)若图中每一对不相邻的顶点的度数之和不小于顶点数,则图是哈密顿图。

//求汉密尔顿回路函数
int Hanmilton(){
    int path[1000] = {0};
    int cur_vertex = 0;     //作为保存当前结点
    int length = 0;         //汉密尔顿回路长度
    int min = 10000;        //最小长度
    for(int i = 1 ; i < this->Nv+1 ; i++){//对每个顶点为初始点进行比遍历寻找汉密尔顿回路
        length = 0;     //重新设置最端长度为0
        memset(this->isvisited,0,sizeof(this->isvisited[0])*(this->Nv+1));  //重新初始化访问数组为0
        this->isvisited[i] = 1;     //标记当前结点为已访问
        path[1] = i;        //保存到临时路径数组的第一个
        cur_vertex = i;     //保存当前顶点
        for(int j = 2 ; j < this->Nv+1 ; j++){//访问剩余的结点
            int k = 0;
            //寻找到第一个未访问的结点
            for(k = 2 ; k < this->Nv+1 ; k++){
                if(this->isvisited[k] == 0){
                    break;
                }
            }
            int tmp = this->data[cur_vertex][k];        //保存当前顶点到该结点的路径长度
            for(int m = k+1 ; m < this->Nv+1 ; m++){//向后寻找有没有路径更短的节点
                if((!this->isvisited[m]) && (tmp > this->data[cur_vertex][m])){
                    tmp = this->data[cur_vertex][m];//更新当前最短路径
                    k = m;//更新第一个未被访问的结点
                }
            }
            path[j] = k;    //保存路径上的结点
            this->isvisited[k] = 1; //标记为已访问
            cur_vertex = k;     //跟新当前结点
            length += tmp;      //跟新长度
            if(length > min){   //当前长度大于最小长度,则改路径无效,跳出循环
                break;
            }
        }   
        length += this->data[cur_vertex][i];
        if(min > length){       //更新最小长度并保存最佳路径
            min = length;
            for(int m = 0 ; m < this->Nv+1 ; m++){
            this->best_path[m] = path[m];
            }
        }
    }
            //返回最小长度
        return min;
}

        //打印最佳汉密尔顿回路
void Print_Best_Path(){
    cout<<this->best_path[1];
    for(int i = 2 ; i < this->Nv+1 ; i++){
        cout<<" -> "<<this->best_path[i];
    }
    cout<<" -> "<<this->best_path[1];
}

二.拓扑排序

可求图的拓扑序、判断是否有环、判断是否有孤立点

#include <iostream>
#include <string.h>
#include <queue>
using namespace std;
const int MAX_N=10000;
struct edge
{
    int to,next;
}e[MAX_N];
int p[MAX_N],eid;
void init()
{
    eid=0;
    memset(p,-1,sizeof(p));
}
void insert(int u,int v)
{
    e[eid].to=v;
    e[eid].next=p[u];
    p[u]=eid++;
}
int indegree[MAX_N];
int n;
int topo()
{
    queue<int>Q;
    for(int i=1;i<=n;i++)
    {
        if(indegree[i]==0)
            Q.push(i);
    }
    while(!Q.empty())
    {
        int now=Q.front();
        cout<<"visting"<<now<<endl;
        Q.pop();
        for(int i=p[now];i!=-1;i=e[i].next)
        {
            int v=e[i].to;
            indegree[v]--;
            if(indegree[v]==0)
                Q.push(v);
        }
    }
}

三.最短路

1.Dijkstra

1.1 优先队列优化

不能处理负权边,O(E*logV)

#include <bits/stdc++.h>
#include <vector>
#include <queue>
using namespace std;

const int MAX_N=1000;
const int MAX_V=1000;
const int INF=0x3f3f3f3f;
struct edge
{
    int to,cost;
};
typedef pair<int,int>P;    //first是最短距离,second是顶点编号
int V;
vector<edge>G[MAX_N];
int d[MAX_V];

void Dijstra(int s)
{
    //通过指定greater<P>参数,堆按照first从小到大顺序取出值
    priority_queue< P,vector<P>,greater<P> > que;
    fill(d,d+V,INF);
    d[s]=0;
    que.push(P(0,s));
    while(!que.empty())
    {
        P now=que.top();
        que.pop();
        int v=now.second;
        if(d[v]<now.first) continue;
        for(int i=0;i<G[v].size();i++)
        {
            edge e=G[v][i];
            if(d[e.to]>d[v]+e.cost)
            {
                d[e.to]=d[v]+e.cost;
                que.push(P(d[e.to],e.to));
            }
        }
    }
}

1.2 堆优化:O((V+E)logV) 推荐使用!

const int MAX_N = 10000;
const int MAX_M = 100000;
const int inf = 0x3f3f3f3f;
struct edge {
    int v, w, next;
} e[MAX_M];
int p[MAX_N], eid;
int n;                        //顶点数设为全局变量
void mapinit() {
    memset(p, -1, sizeof(p));
    eid = 0;
}
void insert(int u, int v, int w) {  // 插入带权有向边
    e[eid].v = v;
    e[eid].w = w;
    e[eid].next = p[u];
    p[u] = eid++;
}
void insert2(int u, int v, int w) {  // 插入带权双向边
    insert(u, v, w);
    insert(v, u, w);
}
typedef pair<int, int> PII;
set<PII, less<PII> > min_heap;  // 用 set 来伪实现一个小根堆,并具有映射二叉堆的功能。堆中 pair<int, int> 的 second 表示顶点下标,first 表示该顶点的 dist 值
set<PII,less<PII> >:: iterator iter;
int dist[MAX_N];  // 存储单源最短路的结果
bool vst[MAX_N];  // 标记每个顶点是否在集合 U 中
bool dijkstra(int s) {
    // 初始化 dist、小根堆和集合 U
    memset(vst, 0, sizeof(vst));
    memset(dist, 0x3f, sizeof(dist));
    min_heap.insert(make_pair(0, s));
    dist[s] = 0;
    for (int i = 0; i < n; ++i) {
        if (min_heap.size() == 0) {  // 如果小根堆中没有可用顶点,说明有顶点无法从源点到达,算法结束
            return false;
        }
        // 获取堆顶元素,并将堆顶元素从堆中删除
        iter = min_heap.begin();
        int v = iter->second;
        min_heap.erase(*iter);
        vst[v] = true;
        // 进行和普通 dijkstra 算法类似的松弛操作
        for (int j = p[v]; j != -1; j = e[j].next) {
            int x = e[j].v;
            if (!vst[x] && dist[v] + e[j].w < dist[x]) {
                // 先将对应的 pair 从堆中删除,再将更新后的 pair 插入堆
                min_heap.erase(make_pair(dist[x], x));
                dist[x] = dist[v] + e[j].w;
                min_heap.insert(make_pair(dist[x], x));
            }
        }
    }
    return true;  // 存储单源最短路的结果
}

1.3路径还原:基于Dijkstra,使用邻接矩阵

int prev[MAX_V];
int d[MAX_V];
int V;
int cost[MAX_V][MAX_V];
bool used[MAX_V]
void Dijkstra(int s)
{
    fill(d,d+v,INF);
    fill(used,used+V,false);
    fill(prev,prev+V,-1);
    d[s]=0;

    while(true)
    {
        int v=-1;
        for(int u=0;u<V;u++)
            if(!used[u]&& (v==-1 || d[u]<d[v]) )
            v=u;
        if(v==-1) break;
        used[v]=true;
        for(int u=0;u<V;u++)
        {
            if(d[u]>d[v]+cost[v][u])
                d[u]=d[v]+cost[v][u];
                prev[u]=v;
        }
    }
}
vector <int>get_path(int t)
{
    vector<int>path;
    for(;t!=-1;t=prev[t])
        path.push_back(t);
    reverse(path.begin(),path.end());
    return path;
}

2.Bellman-Ford

可处理负权边,O(V*E)

struct edge{int from,to,cost};
edge es[N];  //边
int d[N],V,S;    //最短距离,顶点数,边数

void bellman_ford(int s)
{
     memset(d,INF,sizeof(d));
     d[s]=0;
     while(true)
     {
          bool update = false;
          for(int i=0;i<E;i++)
          {
               edge e = es[i];
               if(d[e.from] != INF && d[e.to]>d[e.from]+e.cost)
               {
                     d[e.to]=d[e.from]+e.cost;
                     update = true;
               }
            }
               if(!update) break;
        }
}

2.1 检测负环

bool find_negative_loop()   //返回true表示存在负圈
{
    memset(d,0,sizeof(d));
    for(int i=0;i<V;i++)
    {
          for(int j=0;j<E;j++)
        {
            edge e=es[j];
            if(d[e.to]>d[e.from]+e.cost)
                d[e.to]=d[e.from]+e.cost;
            if(i==V-1)           //如果第n次仍然更新了,则存在负圈
                return truel
        }
    }
    return false;
}

3.Floyd

计算任意两点间最短路,O(v^3)

const int inf = 0x3f3f3f3f;
int g[MAX_N][MAX_N];  // 算法中的 G 矩阵
// 初始化 g 矩阵
void init() {
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (i == j) {
                g[i][j] = 0;
            } else {
                g[i][j] = inf;
            }
        }
    }    
}
// 插入一条带权有向边
void insert(int u, int v, int w) {
    g[u][v] = w;
}
// 核心代码
void floyd() {
    for (int k = 0; k < n; ++k) {
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (g[i][k] + g[k][j] < g[i][j]) {
                    g[i][j] = g[i][k] + g[k][j];
                }
            }
        }
    }    
}

4.SPFA

可处理负权,但不能处理负环,能判断负环,稀疏图O(E),稠密图O(VE)

bool inq[MAX_N];
int cnt[MAX_N];  //记录每个顶点入队次数,若某点入队超过n次,则存在负环
int d[MAX_N];  // 如果到顶点 i 的距离是 0x3f3f3f3f,则说明不存在源点到 i 的最短路
void spfa(int s) 
{
    memset(inq, 0, sizeof(inq));
    memset(d, 0x3f, sizeof(d));
    d[s] = 0;
    inq[s] = true;
    queue<int> q;
    q.push(s);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        if(cnt[u]>n)
            cout<<存在负环;
        inq[u] = false;
        for (int i = p[u]; i != -1; i = e[i].next) {
            int v = e[i].v;
            if (d[u] + e[i].w < d[v]) {
                d[v] = d[u] + e[i].w;
                if (!inq[v]) {
                    q.push(v);
                    cnt[v]++;
                    inq[v] = true;
                }
            }
        }
    }
}

5.K短路

SPFA+A*

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <queue>
using namespace std;

int const maxn=10005;
int const maxm=1005;
int const INF=0x3f3f3f3f;

int n,m,S,E,T,k;
int p1[maxn],p2[maxn],eid1,eid2;
bool vis[maxm];
int dis[maxm];
struct node
{
    int to,next,cost;
}e1[maxn],e2[maxn];

struct node2
{
    int to,g,f;
    bool operator<(const node2 &r ) const
    {
        if(r.f==f)
            return r.g<g;
        return r.f<f;
    }
};
void init()
{
    eid1=1;
    eid2=1;
    memset(p1,-1,sizeof(p1));
    memset(p2,-1,sizeof(p2));
}

void insert1(int u,int v,int w)       //正向存图
{
    e1[eid1].to=v;
    e1[eid1].cost=w;
    e1[eid1].next=p1[u];
    p1[u]=eid1++;
}
void insert2(int u,int v,int w)       //反向存图
{
    e2[eid2].to=v;
    e2[eid2].cost=w;
    e2[eid2].next=p2[u];
    p2[u]=eid2++;
}


void spfa(int s)               //处理出从终点到所有点的最短路
{
    queue<int>Q;
    memset(vis,0,sizeof(vis));
    memset(dis,INF,sizeof(dis));
    dis[s]=0;
    vis[s]=1;
    Q.push(s);
    while(!Q.empty())
    {
        int u=Q.front();
        vis[u]=0;
        Q.pop();
        for(int i=p2[u];i!=-1;i=e2[i].next)
        {
            int v=e2[i].to;
            if(dis[v]>dis[u]+e2[i].cost)
            {
                dis[v]=dis[u]+e2[i].cost;
                if(!vis[v])
                {
                    vis[v]=1;
                    Q.push(v);
                }
            }
        }

    }
}

int Astar()                 //A*求k短路
{
    node2 e,now;
    int cnt=0;
    priority_queue<node2>Q;
    if(S==E)
        k++;
    if(dis[S]==INF)
        return -1;
    e.to=S;
    e.g=0;
    e.f=e.g+dis[e.to];
    Q.push(e);
    while(!Q.empty())
    {
        e=Q.top();
        Q.pop();
        if(e.to==E)
            cnt++;
        if(cnt==k)
            return e.g;
        int u=e.to;
        for(int i=p1[u];i!=-1;i=e1[i].next)
        {
            now.to=e1[i].to;
            now.g=e.g+e1[i].cost;
            now.f=now.g+dis[now.to];
            Q.push(now);
            if(now.g>T) return -1;
        }
    }
    return -1;
}
int main()
{
    //std::ios::sync_with_stdio(false);
    while(~scanf("%d%d",&n,&m))
    {
        init();
        scanf("%d%d%d%d",&S,&E,&k,&T);      //起点,终点,k短路,限定条件
        for(int i=1;i<=m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            insert1(u,v,w);
            insert2(v,u,w);
        }
        spfa(E);
        Astar();
        int ans=Astar();
        printf("%d",ans);   //输出k短路长度
    }
}

6.差分约束系统

即利用最短路算法解不等式,形如a-b<=k,则建立有向边a->b,权值为k
若a=b,即相当a<=b && a>=b ,即建立a->b 和 b->a 两条权值为0的边
加入超级源点,到达所有顶点,且权值为0
利用spfa算法,判断负环,若不存在,则该系统有解,若存在,则该系统无解


四.最小生成树

1.Prmie

O(n^2)

     int cost[MAX_N][MAX_N]; //cost[u][v] 表示边e=(u,v)的权值,不存在为INF
     int mincost[MAX_N];  //从集合T出发的边到每个顶点的最小权值
     bool used[MAX_N];    //顶点i是否包含在集合T中
     int n;           //顶点数

     int prim()
     {
         for(int i=0;i<n;++i)         //初始化
          {
              mincost[i]=INF;
               used[i]=false;
          }
          mincost[0]=0;
          int res=0;

          while(true)
          {
               int v=-1;    //从不属于T的顶点中选取T到其权值最小的顶点
               for(int u=0;u<n;u++)
                   if(!used[u] && (v==-1 || mincost[u]<mincost[v]))
                        v=u;
               if(v==-1) break;  //没有可达点
               used[v]=true;   //把顶点v加入x
               res += mincost[v];  //更新结果
               for(int u=0;u<V;u++)
                   mincost[u]=min(mincost[u],cost[v][u]);
          }
          return res;    //返回最小生成树总权值
     }

2.Kruskal

O(eloge)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX_N = 100000;  // 最大顶点数
const int MAX_M = 100000;  // 最大边数
struct edge {
    int u, v, w;
}e[MAX_M];
int fa[MAX_N], n, m;  // fa 数组记录了并查集中结点的父亲
bool cmp(edge a,edge b) {
    return a.w < b.w;
}
// 并查集相关代码
int ancestor(int x) {  // 在并查集森林中找到 x 的祖先,也是所在连通块的标识
    if(fa[x] == x) return fa[x];
    else return fa[x] = ancestor(fa[x]);
}
int same(int x, int y) {  // 判断两个点是否在一个连通块(集合)内
    return ancestor(x) == ancestor(y);
}
void merge(int x, int y) {  // 合并两个连通块(集合)
    int fax = ancestor(x), fay = ancestor(y);
    fa[fax] = fay;
}
int Kruskal()
{
    for (int i = 1; i <= n; i++) {
        fa[i] = i;
    }
    int rst = n, ans = 0;  // rst 表示还剩多少个集合,ans 保存最小生成树上的总边权
    for (int i = 1; i <= m && rst > 1; i++) {
        int x = e[i].u, y = e[i].v;
        if (same(x, y)) {
            continue;  // same 函数是查询两个点是否在同一集合中
        } else {
            merge(x, y);  // merge 函数用来将两个点合并到同一集合中
            rst--;  // 每次将两个不同集合中的点合并,都将使 rst 值减 1
            ans += e[i].w;  // 这条边是最小生成树中的边,将答案加上边权
        }
    }
    return ans;
}
int main() {
    scanf("%d%d", &n, &m);  // n 为点数,m 为边数
    for (int i = 1; i <= m; i++) {
        scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);  // 用边集数组存放边,方便排序和调用
        }
    sort(e + 1, e + m + 1, cmp);  // 对边按边权进行升序排序
    cout<<Kruskal()<<endl;
    return 0;
}

五.二分图

定义

设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图

性质

(1)二分图的最大匹配数等于最小覆盖数,即求最少的点使得每条边都至少和其中的一个点相关联,很显然直接取最大匹配的一段节点即可。
(2)二分图的独立数等于顶点数减去最大匹配数,很显然的把最大匹配两端的点都从顶点集中去掉这个时候剩余的点是独立集,这是|V|-2*|M|,同时必然可以从每条匹配边的两端取一个点加入独立集并且保持其独立集性质。
(3)DAG的最小路径覆盖,将每个点拆点后作最大匹配,结果为n-m,求具体路径的时候顺着匹配边走就可以,匹配边i→j’,j→k’,k→l’….构成一条有向路径。
(4)最大匹配数=左边匹配点+右边未匹配点。因为在最大匹配集中的任意一条边,如果他的左边没标记,右边被标记了,那么我们就可找到一条新的增广路,所以每一条边都至少被一个点覆盖。
(5)最小边覆盖=图中点的个数-最大匹配数=最大独立集。

1.二分图判断

bool check()
{
    memset(used,-1,sizeof(used));
    queue<int>Q;
    Q.push(1);
    used[1]=0;
    while(!Q.empty())
    {
        int now=Q.front();
        for(int i=1;i<=n;i++)    //遍历所有点
        {
            if(map[now][i]==0)    //邻接矩阵存图
                continue;
            int v=i;
            if(used[v]==-1)
            {
                used[v]=(used[now]+1)%2;
                Q.push(v);
            }
            else
            {
                if(used[v]==used[now])
                    return false;
            }
        }
        Q.pop();
    }
    return true;
}

2.二分图匹配(匈牙利算法)

const int MAX_N = 100;  // X 集合中的顶点数上限
const int MAX_M = 10000;  // 总的边数上限
struct edge {
    int v, next;
} e[MAX_M];
int p[MAX_N], eid;
void init() {
    memset(p, -1, sizeof(p));
    eid = 0;
}
void insert(int u, int v) {  // 从 X 集合顶点 u 到 Y 集合顶点 v 连一条边,注意 u 和 v 的编号无关
    e[eid].v = v;
    e[eid].next = p[u];
    p[u] = eid++;
}
bool vst[MAX_N];  // 标记一次 dfs 过程中,Y 集合中的顶点是否已访问
int ans[MAX_N];  // 标记 Y 集合中的顶点匹配的 X 集合中的顶点编号
int n, m;  // n 表示 X 集合中的顶点数,假设顶点编号为 0..n-1
bool dfs(int u) {
    for (int i = p[u]; i != -1; i = e[i].next) {
        int v = e[i].v;
        if (!vst[v]) {  // 如果 Y 集合中的 v 还没有被访问
            vst[v] = true;
            if (ans[v] == -1 || dfs(ans[v])) {  // 如果 v 没有匹配点,或 v 的匹配点能找到一条到一个未匹配点的增广路,则将 v 的匹配点设为 u
                ans[v] = u;
                return true;
            }
        }
    }
    return false;  // 没找到增广路
}
int maxmatch() {
    int cnt = 0;
    memset(ans, -1, sizeof(ans));  // 初始将所有 Y 集合中顶点的匹配编号设为 -1
    for (int i = 0; i < n; ++i) {
        memset(vst, 0, sizeof(vst));  // 进行 dfs 前,将 vst 清空
        cnt += dfs(i);  // 如果找到增广路,则将 cnt 累加 1
    }
    return cnt;  // cnt 是找到增广路的次数,也是总的最大匹配数
}

3.带权二分图匹配(KM算法)

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;
const int MAXN = 305;
const int INF = 0x3f3f3f3f;

int love[MAXN][MAXN];   // 记录每个妹子和每个男生的好感度
int ex_girl[MAXN];      // 每个妹子的期望值
int ex_boy[MAXN];       // 每个男生的期望值
bool vis_girl[MAXN];    // 记录每一轮匹配匹配过的女生
bool vis_boy[MAXN];     // 记录每一轮匹配匹配过的男生
int match[MAXN];        // 记录每个男生匹配到的妹子 如果没有则为-1
int slack[MAXN];        // 记录每个汉子如果能被妹子倾心最少还需要多少期望值

int N;


bool dfs(int girl)
{
    vis_girl[girl] = true;

    for (int boy = 0; boy < N; ++boy) {

        if (vis_boy[boy]) continue; // 每一轮匹配 每个男生只尝试一次

        int gap = ex_girl[girl] + ex_boy[boy] - love[girl][boy];

        if (gap == 0) {  // 如果符合要求
            vis_boy[boy] = true;
            if (match[boy] == -1 || dfs( match[boy] )) {    // 找到一个没有匹配的男生 或者该男生的妹子可以找到其他人
                match[boy] = girl;
                return true;
            }
        } else {
            slack[boy] = min(slack[boy], gap);  // slack 可以理解为该男生要得到女生的倾心 还需多少期望值 取最小值
        }
    }

    return false;
}

int KM()
{
    memset(match, -1, sizeof match);    // 初始每个男生都没有匹配的女生
    memset(ex_boy, 0, sizeof ex_boy);   // 初始每个男生的期望值为0

    // 每个女生的初始期望值是与她相连的男生最大的好感度
    for (int i = 0; i < N; ++i) {
        ex_girl[i] = love[i][0];
        for (int j = 1; j < N; ++j) {
            ex_girl[i] = max(ex_girl[i], love[i][j]);
        }
    }

    // 尝试为每一个女生解决归宿问题
    for (int i = 0; i < N; ++i) {

        fill(slack, slack + N, INF);    // 因为要取最小值 初始化为无穷大

        while (1) {
            // 为每个女生解决归宿问题的方法是 :如果找不到就降低期望值,直到找到为止

            // 记录每轮匹配中男生女生是否被尝试匹配过
            memset(vis_girl, false, sizeof vis_girl);
            memset(vis_boy, false, sizeof vis_boy);

            if (dfs(i)) break;  // 找到归宿 退出

            // 如果不能找到 就降低期望值
            // 最小可降低的期望值
            int d = INF;
            for (int j = 0; j < N; ++j)
                if (!vis_boy[j]) d = min(d, slack[j]);

            for (int j = 0; j < N; ++j) {
                // 所有访问过的女生降低期望值
                if (vis_girl[j]) ex_girl[j] -= d;

                // 所有访问过的男生增加期望值
                if (vis_boy[j]) ex_boy[j] += d;
                // 没有访问过的boy 因为girl们的期望值降低,距离得到女生倾心又进了一步!
                else slack[j] -= d;
            }
        }
    }
    // 匹配完成 求出所有配对的好感度的和
    int res = 0;
    for (int i = 0; i < N; ++i)
        res += love[ match[i] ][i];

    return res;
}
int main()
{
    while (~scanf("%d", &N)) {

        for (int i = 0; i < N; ++i)
            for (int j = 0; j < N; ++j)
                scanf("%d", &love[i][j]);

        printf("%d\n", KM());
    }
    return 0;
}        

六.最大团/极大团

定义

团:表示N 个点的集合,这N个点彼此两两连接,即有N(N-1)/2条边
极大团: 表示无法是其他团的子团。
最大团:点最多的极大团.

1.Bron–Kerbosch

#include<cstdio>  
#include<cstring>  
#define N 1010  
/*
最大团 = 补图G的最大独立集数
———>最大独立集数 = 补图G'最大团
*/  
//最大团模板  
bool a[N][N];//a为图的邻接表(从1开始)   
int ans, cnt[N], group[N], n, m, vis[N];//ans表示最大团,cnt[N]表示当前最大团的节点数,group[N]用以寻找一个最大团集合   
bool dfs( int u, int pos )//u为当从前顶点开始深搜,pos为深搜深度(即当前深搜树所在第几层的位置)   
{  
    int i, j;  
    for( i = u+1; i <= n; i++)//按递增顺序枚举顶点   
    {  
        if( cnt[i]+pos <= ans ) return 0;//剪枝   
        if( a[u][i] )   
        {  
             // 与目前团中元素比较,取 Non-N(i)   
            for( j = 0; j < pos; j++ ) if( !a[i][ vis[j] ] ) break;   
            if( j == pos )  
            {     // 若为空,则皆与 i 相邻,则此时将i加入到 最大团中   
                vis[pos] = i;//深搜层次也就是最大团的顶点数目,vis[pos] = i表示当前第pos小的最大团元素为i(因为是按增顺序枚举顶点 )   
                if( dfs( i, pos+1 ) ) return 1;      
            }      
        }  
    }      
    if( pos > ans )  
    {  
            for( i = 0; i < pos; i++ )  
                group[i] = vis[i]; // 更新最大团元素   
            ans = pos;  
            return 1;      
    }      
    return 0;  
}   
void maxclique()//求最大团   
{  
    ans=-1;  
    for(int i=n;i>0;i--)  
    {  
        vis[0]=i;  
        dfs(i,1);  
        cnt[i]=ans;  
    }  
}  
int main()  
{  
    //freopen("D:\in.txt","r",stdin);  
    int T;  
    //scanf("%d",&T);  
    while(~scanf("%d",&n))  
    {  
        if(n==0) break;  
        //scanf("%d%d",&n,&m );  
        int x, y;  
        memset( a, 0, sizeof(a));  
        /*for(int i = 0; i < m; i++)
        {
            scanf("%d%d",&x,&y);
            a[x][y] = a[y][x] = 1;
        }*/  
        //相邻顶点间有边相连,模型转换成求 无向图 最大独立集。   
        //要求原图的最大独立集,转化为求原图的补图的最大团(最大团顶点数量 = 补图的最大独立集)      
        for(int i = 1; i <= n; i++)//求原图的补图   
            for(int j = 1; j <= n; j++)  
                 scanf("%d",&a[i][j]);  
        maxclique();//求最大团   
        if( ans < 0 ) ans = 0;//ans表示最大团  
        printf("%d\n", ans );  
        /*for(int i = 0; i < ans; i++)
            printf( i == 0 ? "%d" : " %d", group[i]);//group[N]用以寻找一个最大团集合  
        if( ans > 0 ) puts("");*/  
    }          
}  

七.连通图

定义

图中任意两点都是连通的,那么图被称作连通图

1.Tarjan

O(V+E)

#include <iostream>
#include <string.h>
using namespace std;
const int MAX_N=10000;
struct edge
{
    int to,next;
}e[MAX_N];
int p[MAX_N],eid;
void init()
{
    eid=0;
    memset(p,-1,sizeof(p));
}
void insert(int u,int v)
{
    e[eid].to=v;
    e[eid].next=p[u];
    p[u]=eid++;
}
int dfn[MAX_N],low[MAX_N];     //当前时间戳,最早次序号,dfn只能初始化为0!
int idx=0;                                   //时间戳初始化为0
int belong[MAX_N],scc=0;         //belong记录每个顶点属于哪个强连通分量,scc为强连通分量总数
int s[MAX_N],top=0;                 //模拟栈
bool instack[MAX_N];              //是否在栈中
void tarjan(int u)
{
    dfn[u]=low[u]=++idx;
    s[top++]=u;
    instack[u]=true;
    for(int i=p[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
        {
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u])
    {
        ++scc;
        do
        {
            belong[s[--top]]=scc;
            instack[s[top]]=false;
        }
        while(s[top]!=u);
    }
}
int main()
{
    init();
    int n,m;
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        int a,b;
        cin>>a>>b;
        insert(a,b);
    }
 //   tarjan(0);
    for(int i=1;i<=n;i++)          //用这种方法更新全部点
        if(dfn[i]==0)
             tarjan(i);                    
    for(int i=1;i<=n;i++)
        cout<<i<<"->"<<belong[i]<<endl;
    return 0;
}

八.2-SAT

定义

有很多个集合,每个集合里面有若干元素,现给出一些取元素的规则,要判断是否可行,可行则给出一个可行方案。如果所有集合中,元素个数最多的集合有k个,那么我们就说这是一个k-sat问题,同理,2-sat问题就是k=2时的情况。

细节:

1.拆分后的点一定要用偶数作为编号起始,否则DFS中异或时会出错
2.DFS中只要有一个后继节点不成立,则返回falsefalse,这是因为后继节点们相当于可以被推导出来的条件,每一个都必须成立,原节点才有可能成立
3.DFS开头要判重
4.清空S数组时要注意下标
5.连接有向边时一定要注意,同时注意各个节点编号

struct TwoSAT
{
    int n;
    vector<int> G[N*2];
    bool mark[N*2];
    int S[N*2],c;

    int dfs(int x)
    {
        if (mark[x^1]) return 0;
        if (mark[x]) return 1;    //和假设的值一样
        mark[x]=1;
        S[c++]=x;
        for (int i=0;i<G[x].size;i++)
            if (!dfs(G[x][i])) return 0;
        return 1;
    }

    //x=xval or y=yval
    void add_clause(int x,int xv,int y,int yv)
    {
        x=x*2+xv;
        y=y*2+yv;
        G[x^1].push_back(y);
        G[y^1].push_back(x);
    }

    void init(int n)
    {
        this->n=n;
        for (int i=0;i<2*n;i++) G[i].clear();
        memset(mark,0,sizeof(mark));
    }

    bool solve()
    {
        for (int i=0;i<2*n;i+=2)   //枚举每一个点
            if (!mark[i]&&!mark[i+1])   //没有标记
            {  
                c=0;
                if (!dfs(i))
                {
                    while (c>0) mark[S[--c]]=0;   //清空标记
                    if (!dfs(i+1)) return 0;
                }
            }
        return 1;
    }
};

九.网络流

1.最大流(dicnic)

#include <iostream>
#include <queue>
#include <string.h>
using namespace std;

const int MAX_N=1000;
const int MAX_M=100000;
const int INF=0x3f3f3f3f;

struct edge
{
    int v,c,next;
}e[MAX_M];

int p[MAX_N],eid;
void init()
{
    memset(p,-1,sizeof(p));
    eid=0;
}
void insert(int u,int v,int c)
{
    e[eid].v=v;
    e[eid].next=p[u];
    e[eid].c=c;
    p[u]=eid++;
}
void addedge(int u,int v,int c)
{
    insert(u,v,c);
    insert(v,u,0);
}
int S,T;                 //源点和汇点
int d[MAX_N];       //d表示当前点的层数(level)
bool bfs()
{
    memset(d,-1,sizeof(d));
    queue<int>q;
    q.push(S);
    d[S]=0;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=p[u];i!=-1;i=e[i].next)
        {
            int v=e[i].v;
            if(e[i].c>0 && d[v]==-1)
            {
                q.push(v);
                d[v]=d[u]+1;
            }
        }
    }
    return (d[T]!=-1);
}

int dfs(int u,int f)
{
    if(u==T)
        return f;
    int res=0;
    for(int i=p[u];i!=-1;i=e[i].next)
    {
        int v=e[i].v;
        if(e[i].c>0 && d[u]+1 == d[v])
        {
            int tmp=dfs(v,min(f,e[i].c));      //递归计算顶点v,用c(u,v)更新当前流量上限
            f-=tmp;
            e[i].c-=tmp;
            res+=tmp;
            e[i^1].c+=tmp;                          //修改反向弧流量
            if(f==0)                                      //流量达到上限,不必继续搜索
                break;
        }
    }
    if(res==0)                    //当前没有经过顶点u的流,不必再搜索顶点u
        d[u]=-1;
    return res;
}

int maxf()                 //计算最大流
{
    int res=0;
    while(bfs())
    {
        res+=dfs(S,INF);           //初始流量上限为INF
    }
    return res;
}

int main()
{
    int t,cnt=1;
    cin>>t;
    while(t--)
    {
        init();
        int m,n;
        cin>>n>>m;
        for(int i=0;i<m;i++)
        {
            int a,b,c;
            cin>>a>>b>>c;
            addedge(a,b,c);
        }
     /*   for(int k=1;k<=n;k++)                       //输出图,用于检测图的读入是否正确
        for(int i=p[k];i!=-1;i=e[i].next)
        {
            cout<<k<<"->"<<e[i].v<<" "<<e[i].c<<endl;
        }*/

        S=1;
        T=n;
        cout<<"Case "<< cnt++ <<": ";
        cout<<maxf()<<endl;
    }
    return 0;
}

2.最小费用流(spfa费用流)

#include <iostream>
#include <string.h>
#include <queue>
using namespace std;

const int MAX_N = 10005;
const int MAX_M = 100005;
const int inf = 0x3f3f3f3f;

struct edge
{
    int v, c, w, next;  // v 表示边的另一个顶点,c 表示当前剩余容量,w 表示单位流量费用
} e[MAX_M];

int p[MAX_N], s, t, eid;  // s 表示源点,t 表示汇点,需要在进行 costflow 之前设置完毕

void init()
{
    memset(p, -1, sizeof(p));
    eid = 0;
}

void insert(int u, int v, int c, int w) {
    e[eid].v = v;
    e[eid].c = c;
    e[eid].w = w;
    e[eid].next = p[u];
    p[u] = eid++;
}
void addedge(int u, int v, int c, int w) {
    insert(u, v, c, w);
    insert(v, u, 0, -w);
}

bool inq[MAX_N];
int d[MAX_N];  // 如果到顶点 i 的距离是 0x3f3f3f3f,则说明不存在源点到 i 的最短路
int pre[MAX_N];  // 最短路中连向当前顶点的边的编号

bool spfa()
{  // 以源点 s 为起点计算单源最短路,如果不存在从 s 到 t 的路径则返回 false,否则返回 true
    memset(inq, 0, sizeof(inq));
    memset(d, 0x3f, sizeof(d));
    memset(pre, -1, sizeof(pre));
    d[s] = 0;
    inq[s] = true;
    queue<int> q;
    q.push(s);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        inq[u] = false;
        for (int i = p[u]; i != -1; i = e[i].next) {
            if (e[i].c) {
                int v = e[i].v;
                if (d[u] + e[i].w < d[v]) {
                    d[v] = d[u] + e[i].w;
                    pre[v] = i;
                    if (!inq[v]) {
                        q.push(v);
                        inq[v] = true;
                    }
                }
            }
        }
    }
    return pre[t] != -1;
}

int costflow() {  // 计算最小费用最大流
    int ret = 0;  // 累加和
    while(spfa()) {
        int flow = inf;
        for(int i = t; i != s; i = e[pre[i]^1].v) {
            flow = min(e[pre[i]].c, flow);  // 计算当前增广路上的最小流量
        }
        for(int i = t; i != s; i = e[pre[i]^1].v) {
            e[pre[i]].c -= flow;
            e[pre[i]^1].c += flow;
            ret += e[pre[i]].w * flow;
        }
    }
    return ret;
}

int main()         //以最短来回路为例
{
    int n,m;
    cin>>n>>m;
    init();
    s=0;
    t=n+1;
    addedge(s,1,2,0);
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        addedge(a,b,1,c);
        addedge(b,a,1,c);
    }
    addedge(n,t,2,0);
    int ans=costflow();
    cout<<ans<<endl;
    return 0;
}

3.有界网络流

https://www.cnblogs.com/mlystdcall/p/6734852.html

3.1无源汇上下界可行流

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=300,maxm=100000;
struct edge{
  int to,next,w,num;
}lst[maxm];int len=0,first[maxn],_first[maxn];
void addedge(int a,int b,int w,int num){
  lst[len].num=num;
  lst[len].to=b;lst[len].next=first[a];lst[len].w=w;first[a]=len++;
  lst[len].num=num;
  lst[len].to=a;lst[len].next=first[b];lst[len].w=0;first[b]=len++;
}
int vis[maxn],dis[maxn],q[maxn],head,tail,s,t,T;
bool bfs(){
  vis[s]=++T;dis[s]=1;head=tail=0;q[tail++]=s;
  while(head!=tail){
    int x=q[head++];
    for(int pt=first[x];pt!=-1;pt=lst[pt].next){
      if(lst[pt].w&&vis[lst[pt].to]!=T){
    vis[lst[pt].to]=T;dis[lst[pt].to]=dis[x]+1;q[tail++]=lst[pt].to;
      }
    }
  }
  if(vis[t]==T)memcpy(_first,first,sizeof(first));
  return vis[t]==T;
}
int dfs(int x,int lim){
  if(x==t){
    return lim;
  }
  int flow=0,a;
  for(int pt=_first[x];pt!=-1;pt=lst[pt].next){
    _first[x]=pt;
    if(lst[pt].w&&dis[lst[pt].to]==dis[x]+1&&(a=dfs(lst[pt].to,min(lst[pt].w,lim-flow)))){
      lst[pt].w-=a;lst[pt^1].w+=a;flow+=a;
      if(flow==lim)return flow;
    }
  }
  return flow;
}
int dinic(){
  int ans=0,x;
  while(bfs())
    while(x=dfs(s,0x7f7f7f7f))ans+=x;
  return ans;
}
int low[maxm],ans[maxm];
int totflow[maxn],n,m;

void work(){
  memset(totflow,0,sizeof(totflow));
  memset(first,-1,sizeof(first));len=0;
  scanf("%d%d",&n,&m);
  int u,v,b;
  s=0;t=n+1;
  for(int i=1;i<=m;++i){
    scanf("%d%d%d%d",&u,&v,&low[i],&b);
    addedge(u,v,b-low[i],i);totflow[u]-=low[i];totflow[v]+=low[i];
  }
  int sum=0;
  for(int i=1;i<=n;++i){
    if(totflow[i]<0){
      addedge(i,t,-totflow[i],0);
    }else{
      sum+=totflow[i];
      addedge(s,i,totflow[i],0);
    }
  }
  if(dinic()==sum){
    puts("YES");
    for(int i=1;i<=n;++i){
      for(int pt=first[i];pt!=-1;pt=lst[pt].next){
    if(lst[pt].num==0||pt%2==0)continue;
    ans[lst[pt].num]=lst[pt].w+low[lst[pt].num];
      }
    }
    for(int i=1;i<=m;++i)printf("%d\n",ans[i]);
  }else puts("NO");
}
int main(){
  int tests;scanf("%d",&tests);
  while(tests--){
    work();if(tests)printf("\n");
  }
  return 0;
}

3.2有源汇上下界可行流

#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<iostream>
using namespace std;
const int N=1e5+7;
const int inf=0x3f3f3f3f;

struct node
{
    int to,next,cost;
}e[30*N];
int eid,p[N],c[N];

void init()
{
    eid=0;
    memset(p,-1,sizeof(p));
    memset(c,-1,sizeof(c));
}
void insert(int u,int v,int w)
{
    e[eid].to=v;
    e[eid].cost=w;
    e[eid].next=p[u];
    p[u]=eid++;
}
void addedge(int u,int v,int w)
{
    insert(u,v,w);
    insert(v,u,0);
}

int n,m,sp,tp;
int d[N];
bool bfs()                      //构建层次
{
    memset(d,-1,sizeof(d));
    queue<int>Q;
    d[sp]=0;
    Q.push(sp);
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        for(int i=p[u];i!=-1;i=e[i].next)
        {
            int v=e[i].to;
            if(d[v]==-1&&e[i].cost){
                d[v]=d[u]+1;
                Q.push(v);
                if(v==tp) return true;
            }
        }
    }
    return ~d[tp];
}
int dfs(int u,int b)
{
    if(u==tp) return b;
    int r=0;
    for(int i=c[u];i!=-1;i=e[i].next){
        int v=e[i].to;
        if(e[i].cost&&d[v]==d[u]+1){
            int x=min(e[i].cost,b-r);
            c[u]=i;
            x=dfs(v,x);
            r+=x;
            e[i].cost-=x;
            e[i^1].cost+=x;
            if(r==b) break;
        }
    }
    if(!r)d[u]=-2;
    return r;
}
int dinic(){
    int total=0,tmp;
    while(bfs()){
        memcpy(c,p,sizeof(p));
        while(tmp=dfs(sp,inf))
        total+=tmp;
    }
    return total;
}
int x[N];
int main()
{
    int k,L,R,cs=0;
    while(scanf("%d%d%d",&n,&m,&k)!=EOF){
        init();
        sp=n+m+1,tp=n+m+2;          //附加源,附加汇
        int ss=n+m+3,tt=n+m+4;      //源点,汇点
        scanf("%d%d",&L,&R);
        int u,v;
        for(int i=1;i<=k;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v+n,1);       //构建网络,左边点编号为1~n,右边点编号为n+1~2*n
        }
        for(int i=1;i<=n;i++){
            addedge(ss,i,R-L);     //源点连向所有左侧点,修改流量为R-L
            addedge(sp,i,L);       //附加源连向所有左侧点,流量为L,表示必要弧
            addedge(ss,tp,L);      //源点连向附加汇
        }
        for(int i=1;i<=m;i++){
            addedge(i+n,tt,R-L);    //右侧点连向汇点,修改流量为R-L
            addedge(sp,tt,L);       //附加源连向汇点
            addedge(i+n,tp,L);      //右侧点连向附加汇,流量为L,表示必要弧
        }
        addedge(tt,ss,inf);      //汇点到源点连一条流量为inf的边,变为有源上下界网络流
        printf("Case %d: ",++cs);
        if(dinic()==(n+m)*L) puts("Yes");     //求附加源到附加汇的最大流,若满足附加源到附加汇的所有弧都满流,则有可行流
        else puts("No");
    }
    return 0;
}

3.3有源汇上下界最大流

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2005,maxm=100005;
const int inf=0x7f7f7f7f;
struct edge{
  int to,next,w,num;
}lst[maxm];int len=0,first[maxn],_first[maxn];
void addedge(int a,int b,int w,int num){
  lst[len].num=num;
  lst[len].to=b;lst[len].next=first[a];lst[len].w=w;first[a]=len++;
  lst[len].num=num;
  lst[len].to=a;lst[len].next=first[b];lst[len].w=0;first[b]=len++;
}
int q[maxn],vis[maxn],dis[maxn],T,s,t,head,tail,ss,tt;
bool bfs(){
  head=tail=0;vis[s]=++T;q[tail++]=s;
  while(head!=tail){
    int x=q[head++];
    for(int pt=first[x];pt!=-1;pt=lst[pt].next){
      if(lst[pt].w&&vis[lst[pt].to]!=T){
    vis[lst[pt].to]=T;dis[lst[pt].to]=dis[x]+1;q[tail++]=lst[pt].to;
      }
    }
  }
  if(vis[t]==T)memcpy(_first,first,sizeof(first));
  return vis[t]==T;
}
int dfs(int x,int lim){
  if(x==t)return lim;
  int flow=0,a;
  for(int pt=_first[x];pt!=-1;pt=lst[pt].next){
    _first[x]=pt;
    if(lst[pt].w&&dis[lst[pt].to]==dis[x]+1&&(a=dfs(lst[pt].to,min(lst[pt].w,lim-flow)))){
      lst[pt].w-=a;lst[pt^1].w+=a;flow+=a;
      if(flow==lim)return flow;
    }
  }
  return flow;
}
int dinic(){
  int ans=0,x;
  while(bfs())
    while(x=dfs(s,inf))ans+=x;
  return ans;
}
int totflow[maxn];
void Add(int a,int b,int lo,int hi,int num){
  totflow[a]-=lo;totflow[b]+=lo;
  addedge(a,b,hi-lo,num);
}
int low[maxm],ans[maxm];
int n,m,tot;
void bound_flow(){
  int sum=0;
  for(int i=s;i<=t;++i){
    if(totflow[i]<0){
      addedge(i,tt,-totflow[i],0);
    }else{
      sum+=totflow[i];
      addedge(ss,i,totflow[i],0);
    }
  }
  addedge(t,s,0x7f7f7f7f,0);
  int tmps=s,tmpt=t;
  s=ss;t=tt;
  if(dinic()==sum){
    for(int pt=first[ss];pt!=-1;pt=lst[pt].next){
      lst[pt].w=lst[pt^1].w=0;
    }
    for(int pt=first[tt];pt!=-1;pt=lst[pt].next){
      lst[pt].w=lst[pt^1].w=0;
    }
    int flow0=lst[len-1].w;
    lst[len-1].w=lst[len-2].w=0;
    s=tmps;t=tmpt;
    printf("%d\n",flow0+dinic());
    for(int i=1;i<=m;++i){
      for(int pt=first[i+n];pt!=-1;pt=lst[pt].next){
    if(lst[pt].num!=0){
      ans[lst[pt].num]=lst[pt].w+low[lst[pt].num];
    }
      }
    }
    for(int i=1;i<=tot;++i)printf("%d\n",ans[i]);
  }else{
    printf("-1\n");
  }
}

void work(){
  s=0;t=n+m+1;
  ss=n+m+2;tt=n+m+3;
  memset(first,-1,sizeof(first));len=0;
  memset(totflow,0,sizeof(totflow));
  int x,y;
  for(int i=1;i<=m;++i){
    scanf("%d",&x);
    Add(n+i,t,x,inf,0);
  }
  int l,h;
  tot=0;
  for(int i=1;i<=n;++i){
    scanf("%d%d",&x,&y);
    Add(s,i,0,y,0);
    for(int j=1;j<=x;++j){
      ++tot;
      scanf("%d%d%d",&y,&l,&h);
      Add(i,n+y+1,l,h,tot);low[tot]=l;
    }
  }
  bound_flow();printf("\n");
}
int main(){
  while(scanf("%d%d",&n,&m)!=EOF)work();
  return 0;
}

3.4有源汇上下界最小流

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=205,maxm=100005;
struct edge{
  int to,next,w;
}lst[maxm];int len=0,first[maxn],_first[maxn];
void addedge(int a,int b,int w){//printf("Add %d %d\n",a,b);
  lst[len].to=b;lst[len].next=first[a];lst[len].w=w;first[a]=len++;
  lst[len].to=a;lst[len].next=first[b];lst[len].w=0;first[b]=len++;
}
int q[maxn],vis[maxn],dis[maxn],head,tail,s,t,T,ss,tt;
bool bfs(){
  head=tail=0;vis[s]=++T;dis[s]=1;q[tail++]=s;
  while(head!=tail){
    int x=q[head++];
    for(int pt=first[x];pt!=-1;pt=lst[pt].next){
      if(lst[pt].w&&vis[lst[pt].to]!=T){
    vis[lst[pt].to]=T;dis[lst[pt].to]=dis[x]+1;q[tail++]=lst[pt].to;
      }
    }
  }
  if(vis[t]==T)memcpy(_first,first,sizeof(first));
  return vis[t]==T;
}
int dfs(int x,int lim){
  if(x==t)return lim;
  int flow=0,a;
  for(int pt=_first[x];pt!=-1;pt=lst[pt].next){
    _first[x]=pt;
    if(lst[pt].w&&dis[lst[pt].to]==dis[x]+1&&(a=dfs(lst[pt].to,min(lst[pt].w,lim-flow)))){
      lst[pt].w-=a;lst[pt^1].w+=a;flow+=a;
      if(flow==lim)return flow;
    }
  }
  return flow;
}
int dinic(){
  int ans=0,x;
  while(bfs()){
    while(x=dfs(s,0x7f7f7f7f))ans+=x;
  }
  return ans;
}
int totflow[maxn];
void del(int x){
  for(int pt=first[x];pt!=-1;pt=lst[pt].next)lst[pt].w=lst[pt^1].w=0;
}
int main(){
  int n;scanf("%d",&n);
  int x,y;
  memset(first,-1,sizeof(first));
  for(int i=1;i<=n;++i){
    scanf("%d",&x);
    for(int j=1;j<=x;++j){
      scanf("%d",&y);
      totflow[i]--;totflow[y]++;
      addedge(i,y,0x7f7f7f7f);
    }
  }

  s=0;t=n+1;ss=n+2,tt=n+3;
  for(int i=1;i<=n;++i){
    addedge(s,i,0x7f7f7f7f);
    addedge(i,t,0x7f7f7f7f);
  }
  for(int i=1;i<=n;++i){
    if(totflow[i]<0){
      addedge(i,tt,-totflow[i]);
    }else{
      addedge(ss,i,totflow[i]);
    }
  }
  addedge(t,s,0x7f7f7f7f);
  int tmps=s,tmpt=t;
  s=ss;t=tt;
  dinic();
  int flow0=lst[len-1].w;
  lst[len-1].w=lst[len-2].w=0;
  del(ss);del(tt);
  s=tmpt;t=tmps;
  printf("%d\n",flow0-dinic());
  return 0;
}

3.5有源汇上下界费用流

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstring>
#define N 50000
#define inf 1000000000
using namespace std;
int ans,n,m,tot,val[N];
int point[N],v[N],next[N],remain[N],c[N],dis[N],can[N],last[N];
void add(int x,int y,int z,int k)
{
    tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=z; c[tot]=k;
    tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0; c[tot]=-k;
    //if (z)  cout<<x<<" "<<y<<" "<<z<<" "<<k<<endl;
}
int addflow(int s,int t)
{
    int now=t; int ans=inf;
    while (now!=s) {
        //cout<<now<<" ";
        ans=min(ans,remain[last[now]]);
        now=v[last[now]^1];
    }
    //cout<<now<<endl;
    now=t;
    while (now!=s) {
        remain[last[now]]-=ans;
        remain[last[now]^1]+=ans;
        now=v[last[now]^1];
    }
    return ans;
}
bool spfa(int s,int t)
{
    for (int i=0;i<=t;i++) dis[i]=inf,can[i]=0;
    dis[s]=0; can[s]=1;
    queue<int> p; p.push(s);
    while (!p.empty()){
        int now=p.front(); p.pop();
        for (int i=point[now];i!=-1;i=next[i])
         if (remain[i]&&dis[v[i]]>dis[now]+c[i]){
            dis[v[i]]=dis[now]+c[i];
            last[v[i]]=i;
            if (!can[v[i]]) {
                can[v[i]]=1;
                p.push(v[i]);
             }
         }
        can[now]=0;
    }
    if (dis[t]==inf) return false;
    int mx=addflow(s,t);
    //cout<<dis[t]<<" "<<mx<<endl;
    ans+=mx*dis[t];
    return true;
}
void solve(int s,int t)
{
    while (spfa(s,t));
}
int main() 
{
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    while (scanf("%d%d",&n,&m)!=EOF) {
        tot=-1;
        memset(point,-1,sizeof(point));
        int t=2*(n+1)+1;
        for (int i=1;i<=n;i++) {
         int x; scanf("%d",&x);
         add(1,i+1,1,x); 
        }
        for (int i=1;i<=n;i++)
         for (int j=i+1;j<=n;j++){
          int x; scanf("%d",&x);
          add(i+n+1,j+1,1,x);
        }
        for (int i=1;i<=n;i++) 
         add(i+1,i+n+1,0,0),add(i+n+1,t,1,0);
        int S=t+1; int T=t+2;
        add(t,0,inf,0); add(0,1,m,0); //add(S,1,1,0); add(0,T,1,0);
        for (int i=2;i<=n+1;i++)
         add(S,i+n,1,0),add(i,T,1,0);
        solve(S,T);
        printf("%d\n",ans);
    }
}
posted @ 2018-09-10 20:28  floatingcloak  阅读(642)  评论(0编辑  收藏  举报