图论模板整理

模板大部分来自LRJ


连通性

割点

//带重边处理
int tarjan(int u, int fa)
{
    bool f = false;			
    int lowu = dfn[u] = ++dfs_c;
    REP(i, G[u].size())
    {
        Edge& e = edges[G[u][i]];
        if (e.to == fa && !f)		///////第一次为访问父节点不算, 其他则为重边
        {
            f = 1;
            continue;
        }
        if (!dfn[e.to])
        {
            int lowv = tarjan(e.to, u);
            lowu = min(lowu, lowv);
            if (lowv > dfn[u])
            {
                edges[G[u][i]].flag = true;
                edges[G[u][i] ^ 1].flag = true;
            }
        }
        else
            lowu = min(lowu, dfn[e.to]);
    }
    low[u] = lowu;
    return lowu;
}

vector G[MAXN];
int pre[MAXN], low[MAXN], iscut[MAXN];
///pre[u]表示dfs中u的次序编号
///low[u]表示u及u的子树中能追溯到的最早的节点的次序编号值(即pre值)
///iscut[u]表示u是否为割点
int dfs_clock = 0;


///调用前需将pre数组赋0,首次调用fa为-1
int dfs(int u, int fa)///fa表示u的父节点
{
    int lowu = pre[u] = ++dfs_clock;
    int child = 0;              ///表示子节点个数
    for (int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        if (!pre[v])            ///若没被访问
        {
            int lowv = dfs(v, u);
            lowu = min(lowv, lowu); ///用子节点的low值更新lowu
            if (lowv >= pre[u] && fa >= 0)
            { 
                iscut[u] = 1;
            }
        }///若被访问,则用反向变(返祖边)更新当前u的low值
        else if (v != fa && pre[v] < pre[u])///切记v != fa
        {
            lowu = min(lowu, pre[v]);
        }
    }
    if (fa < 0 && child > 1)   ///根节点为割点当且仅当子节点数大于一个
        iscut[u] = 1;
    low[u] = lowu;
    return lowu;
}

求删割点后剩余联通分量数时,只用将iscut[u] = 1改为iscut[u]++,则非根节点剩余分量数为iscut[u]+1,根节点为iscut[u]
(若u为割点,记iscut[u]为u的子节点数,则去掉u后,图被分成iscut[u]+1个部分(每个子节点的部分和u的祖先的部分),若u为dfs树的根,则分成iscut[u]个部分(根节点没有祖先))


 
   

点双连通分量

//当u为割点时,把边从栈顶依次取出,直到遇到边(u,v),取出的边与其关联的点,组成点双连通分支。
//割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支
//基本算法与求割点一致

int dfn[MAXN], iscut[MAXN], bccno[MAXN];
int dfs_clock, bcc_cnt;
vector G[MAXN], bcc[MAXN];

struct Edge{
    int u, v;
};

stack S;

int dfs(int u, int fa)
{
    int lowu = dfn[u] = ++dfs_clock;
    int child = 0;
    for (int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        Edge e = (Edge){u, v};
        if (!dfn[v])
        {
            S.push(e);
            child++;
            int lowv = dfs(v, u);
            lowu = min(lowu, lowv);
            if (lowv >= dfn[u])
            {
                iscut[u] = 1;
                bcc_cnt++;
                bcc[bcc_cnt].clear();
                while (1)
                {   
                    Edge x = S.top();
                    S.pop();
                    if (bccno[x.u] != bcc_cnt)
                    {
                        bcc[bcc_cnt].push_back(x.u);
                        bccno[x.u] = bcc_cnt;
                    }
                    if (bccno[x.v] != bcc_cnt)
                    {
                        bcc[bcc_cnt].push_back(x.v);
                        bccno[x.v] = bcc_cnt;
                    }
                    if (x.u == u && x.v == v)///最后才判断跳出,因为(u,v)这条变也要加入
                        break;
                }
            }
        }
        else if (v != fa && dfn[v] < dfn[u])
        {
            S.push(e);
            lowu = min(lowu, dfn[v]);
        }
    }
    if (fa < 0 && child == 1)
        iscut[u] = 0;
    return lowu;
}

void find_bcc(int n)
{
    memset(dfn, 0, sizeof(dfn));
    memset(iscut, 0, sizeof(iscut));
    memset(bccno, 0, sizeof(bccno));
    dfs_clock = bcc_cnt = 0;
    for (int i = 1; i <= n; i++)
        if (!dfn[i])
            dfs(i, -1);
}
 
   

边双连通分量

struct Edge{
    int from, to;
    bool flag;
};
vector edges;
VI G[maxn];
int dfn[maxn], bccno[maxn], out[maxn];
int dfs_c, bcc_cnt, n, m;
bool vis[maxn];

inline void add(int x, int y)
{
    edges.PB((Edge){x, y, false});
    edges.PB((Edge){y, x, false});
    int sz = edges.size();
    G[x].PB(sz - 2);
    G[y].PB(sz - 1);
}

int tarjan(int u, int fa)
{
    int lowu = dfn[u] = ++dfs_c;
    REP(i, SZ(G[u]))
    {
        Edge& e = edges[G[u][i]];
        if (!dfn[e.to])
        {
            int lowv = tarjan(e.to, u);
            lowu = min(lowu, lowv);
            if (lowv > dfn[u])
            {
                e.flag = true;
                edges[G[u][i] ^ 1].flag = 1;
            }
        }
        else if (dfn[e.to] < dfn[u] && e.to != fa)
            lowu = min(lowu, dfn[e.to]);
    }
    return lowu;
}

void find_bcc()
{
    CLR(dfn, 0), CLR(bccno, 0);
    CLR(out, 0);
    dfs_c = bcc_cnt = 0;
    FE(i, 1, n)
        if (!dfn[i])
            tarjan(i, -1);
}

void init()
{
    FE(i, 0, n + 1)
        G[i].clear();
    edges.clear();
    CLR(vis, 0);
}

void dfs(int u, int cnt)
{
    vis[u] = 1, bccno[u] = cnt;
    REP(i, SZ(G[u]))
    {
        Edge& e = edges[G[u][i]];
        if (e.flag)
        {
            //printf("Bridge:   %d   %d\n", e.from, e.to);
            continue;
        }
        if (!vis[e.to])
            dfs(e.to, cnt);
    }
}

void solve()
{
    FE(i, 1, n)
        if (!vis[i])
        {
            bcc_cnt++;
            dfs(i, bcc_cnt);
        }
    REP(i, SZ(edges))
    {
        int u = edges[i].from, v = edges[i].to;
        if (bccno[u] != bccno[v])
            out[bccno[u]]++;
    }
    int leaf = 0;
    FE(i, 1, bcc_cnt)
        if (out[i] == 1)
            leaf++;
    //cout << bcc_cnt << "  " <<  leaf << endl;
    int ans = (leaf + 1) / 2;
    if (bcc_cnt == 1)   ans = 0;
    WI(ans);
}

int main()
{
    int x, y;
    while (~RII(n, m))
    {
        init();
        REP(i, m)
        {
            RII(x, y);
            add(x, y);
        }
        find_bcc();
        solve();
    }
}

重边处理

//对于有重边的图,求边双连通分量
struct EDGE
{
	int u, v;
	int next;
};

int first[MAXN], rear;
EDGE edge[MAXE];

void init()
{
	memset(first, -1, sizeof(first));
	rear = 0;
}

void insert(int tu, int tv)
{
	edge[rear].u = tu;
	edge[rear].v = tv;
	edge[rear].next = first[tu];
	first[tu] = rear++;
	edge[rear].u = tv;
	edge[rear].v = tu;
	edge[rear].next = first[tv];
	first[tv] = rear++;
}

int pre[MAXN], low[MAXN];
bool vis_e[MAXE];
bool is_cut_e[MAXE];
int dfs_clock;

int dfs(int cur, int fa)
{
	pre[cur] = low[cur] = ++dfs_clock;
	int child(0);
	for(int i = first[cur]; i != -1; i = edge[i].next)
	{
		int tv = edge[i].v;
		if(!pre[tv])
		{
			++child;
			vis_e[i] = true;
			vie_e[i^1] = true;
			st.push(i);
			int lowv = dfs(tv, cur);
			low[cur] = min(low[cur], lowv);
		}
		else
			if(pre[tv] < pre[cur] && !vis_e[i])
			{
				vis_e[i] = true;
				vis_e[i^1] = true;
				low[cur] = min(low[cur], pre[edge[i].v]);
			}
	}
	return low[cur];
}

void find_e_bccn(int n)
{
	dfs_clock = 0;
	memset(pre, 0, sizeof(pre));
	memset(low, 0, sizeof(low));
	memset(vis_e, 0, sizeof(vis_e));
	memset(is_cut_e, 0, sizeof(is_cut_e));	
	for(int i = 1; i <= n; ++i)
		if(!pre[i])
			dfs(i);
	for(int i = 0; i < rear; ++i)
		if(low[i].v < pre[edge[i].u])
		{
			is_cut_e[i] = true;
			is_cut_e[i^1] = true;
		}
}

//接着在不经过桥的情况下dfs求出所有双强连通分量即可

强连通分量

vector G[MAXN];
int dfn[MAXN], low[MAXN], sccno[MAXN], dfs_clock, scc_cnt;
stack S;

void dfs(int u)
{
    dfn[u] = low[u] = ++dfs_clock;
    S.push(u);
    for (int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        if (!dfn[u])
        {
            dfs(v);
            low[u] = min(low[u], low[v]);
        }
        else if (!sccno[v])         ///只能通过当前SCC中的点更新
            low[u] = min(low[u], low[v]);
    }
    if (low[u] == dfn[u])       ///当u是此连通分量中最先被遍历的点
    {
        scc_cnt++;
        while (1)
        {
            int x = S.top();
            S.pop();
            sccno[x] = scc_cnt;
            if (x == u)
                break;
        }
    }
}

void find_scc(int n)
{
    dfs_clock = scc_cnt = 0;
    memset(sccno, 0, sizeof(sccno));
    memset(dfn, 0, sizeof(dfn));
    for (int i = 0; i < n; i++)
        if (!dfn[i])
            dfs(i);
}

//求至少需要选择多少个点才能是全图都能被访问到
//至少加多少条边才能使全图连通
//解:缩点,行成一个DAG图,则A为DAG中入度为0的点(块)的个数
//    B为入度或出度为0的点的个数的最大值

最短路

dijkstra算法(优先队列优化)

struct Edge{
    int from, to, dist;
};

struct Node{
    int d, u;
    bool operator < (const Node& rhs) const
    {
        return d > rhs.d;
    }
};

struct Dijkstra{
    int n, m;
    vector edges;
    vector G[MAXN];
    bool done[MAXN];
    int d[MAXN];
    int p[MAXN];

    void init(int n)
    {
        this->n = n;
        for (int i = 0; i < n; i++)
            G[i].clear();
        edges.clear();
    }

    void addedge(int from, int to, int dist)
    {
        edges.push_back((Edge){from, to, dist});
        m = edges.size();
        G[from].push_back(m - 1);
    }

    void dijkstra(int s)
    {
        priority_queue Q;
        for (int i = 0; i < n; i++)
            d[i] = INF;
        d[s] = 0;
        memset(done, 0, sizeof(done));
        Q.push((Node){0, s});
        while (!Q.empty())
        {
            Node x = Q.top();
            Q.pop();
            int u = x.u;
            if (done[u])
                continue;
            done[u] = 1;
            for (int i = 0; i < G[u].size(); i++)
            {
                Edge& e = edges[G[u][i]];
                if (d[e.to] > d[u] + e.dist)
                {
                    d[e.to] = d[u] + e.dist;
                    p[e.to] = G[u][i];
                    Q.push((Node){d[e.to], e.to});
                }
            }
        }
    }
};

spfa算法判负环

struct Edge{
    int from, to, dist;
};

struct BellmanFord{
    int n, m;
    vector edges;
    vector G[MAXN];
    bool inq[MAXN];
    int d[MAXN];
    int p[MAXN];
    int cnt[MAXN];

    void init(int n)
    {
        this->n = n;
        for (int i = 0; i < n; i++)
            G[i].clear();
        edges.clear();
    }

    void addedge(int from, int to, int dist)
    {
        edges.push_back((Edge){from, to, dist});
        m = edges.size();
        G[from].push_back(m - 1);
    }

    bool negativeCycle()
    {
        queue Q;
        memset(inq, 0, sizeof(inq));
        memset(cnt, 0, sizeof(cnt));
        for (int i = 0; i < n; i++)///若使用spfa算法,则修改为源点入队,d[]数组赋值INF,d[s]=0
        {
            d[i] = 0;
            inq[i] = true;
            Q.push(i);
        }
        while (!Q.empty())
        {
            int u = Q.front();
            Q.pop();
            inq[u] = 0;
            for (int i = 0; i < G[u].size(); i++)
            {
                Edge& e = edges[G[u][i]];
                if (d[e.to] > d[u] + e.dist)
                {
                    d[e.to] = d[u] + e.dist;
                    p[e.to] = G[u][i];
                    if (!inq[e.to])
                    {
                        Q.push(e.to);
                        inq[e.to] = 1;
                        if (++cnt[e.to] > n)
                            return true;
                    }
                }
            }
        }
        return false;
    }
};

//DFS版本判负环
struct BellmanFord{
int n, m;
vector edges;
VI G[maxn];
bool inq[maxn];
double d[maxn];
int cnt[maxn];

void init(int n)
{
    this-> n = n;
    REP(i, n + 1)
        G[i].clear();
    edges.clear();
}

void add(int from, int to, double dist)
{
    Edge e1 = Edge(from, to, dist);
    edges.PB(e1);
    m = edges.size();
    G[from].PB(m - 1);
}

bool dfs(int u)
{
    inq[u] = 1;
    REP(i, SZ(G[u]))
    {
        Edge &e = edges[G[u][i]];
        if (d[e.to] > d[u] * e.dist)
        {
            d[e.to] = d[u] * e.dist;
            if (inq[e.to] || dfs(e.to)) return 1;
        }
    }
    inq[u] = 0;
    return 0;
}

bool spfa(int s)
{
    queue Q;
    CLR(inq, false), CLR(cnt, 0);
    REP(i, n)
        d[i] = INF;

    d[s] = 1.0, inq[s] = 1;
    Q.push(s);

    REP(i, n)
        if (dfs(i))
            return 1;
    return 0;
}
}neg;

K短路模版

const int INF = 0x3f3f3f3f;
const int MAX = 1005;
int n,m;
int start,end,k;
struct Edge
{
    int w;
    int to;
    int next;
};
Edge e[100005];
int head[MAX],edgeNum;
int dis[MAX];   //dis[i]表示从i点到end的最短距离
bool vis[MAX];
int cnt[MAX];
vector opp_Graph[MAX];

struct Node
{
    int f,g;    //f = g+dis[v]
    int v;      //当前到达的节点
    Node(int a, int b,int c):f(a),g(b),v(c){}
    bool operator < (const Node& a) const
    {
        return a.f < f;
    }
};

void addEdge(int from, int to, int w)
{
    e[edgeNum].to = to;
    e[edgeNum].w = w;
    e[edgeNum].next = head[from];
    head[from] = edgeNum++;
}

void dijikastra(int start)
{
    int i;
    memset(vis,0,sizeof(vis));
    for(i = 1; i <= n; i++)
        dis[i] = INF;
    dis[start] = 0;
    priority_queue que;
    que.push(Node(0,0,start));
    Node next(0,0,0);
    while(!que.empty())
    {
        Node now = que.top();
        que.pop();
        if(vis[now.v])              //从集合T中选取具有最短距离的节点
            continue;
        vis[now.v] = true;          //标记节点已从集合T加入到集合S中
        for(i = 0; i < opp_Graph[now.v].size(); i++)    //更新从源点到其它节点(集合T中)的最短距离
        {
            Edge edge = opp_Graph[now.v][i];
            if(!vis[edge.to] && dis[now.v] + edge.w < dis[edge.to])  //加不加前面的判断无所谓
            {
                dis[edge.to] = dis[now.v] + edge.w;
                next.f = dis[edge.to];
                next.v = edge.to;
                que.push(next);
            }
        }
    }
}

int A_Star()
{
    int i;
    priority_queue que;
    if(dis[start] == INF)
        return -1;
    que.push(Node(dis[start],0,start));
    Node next(0,0,0);
    while(!que.empty())
    {
        Node now = que.top();
        que.pop();
        cnt[now.v]++;
        if(cnt[end] == k)
            return now.f;
        if(cnt[now.v] > k)
            continue;
        for(i = head[now.v]; i != -1; i = e[i].next)
        {
            next.v = e[i].to;
            next.g = now.g + e[i].w;
            next.f = next.g + dis[e[i].to];
            que.push(next);
        }
    }
    return -1;
}

int main()
{
    int i;
    int from,to,w;
    edgeNum = 0;
    memset(head,-1,sizeof(head));
    memset(opp_Graph,0,sizeof(opp_Graph));
    memset(cnt,0,sizeof(cnt));
    scanf("%d %d",&n,&m);
    Edge edge;
    for(i = 1; i <= m; i++)
    {
        scanf("%d %d %d",&from,&to,&w);
        addEdge(from,to,w);
        edge.to = from;
        edge.w = w;
        opp_Graph[to].push_back(edge);
    }
    scanf("%d %d %d",&start,&end,&k);
    if(start == end)
        k++;
    dijikastra(end);
    int result = A_Star();
    printf("%d\n",result);
    return 0;
}

匹配问题

3个重要结论:

最小点覆盖数: 最小覆盖要求用最少的点(X集合或Y集合的都行)让每条边都至少和其中一个点关联。可以证明:最少的点(即覆盖数)=最大匹配数
最小路径覆盖=最小路径覆盖=|N|-最大匹配数
用尽量少的不相交简单路径覆盖有向无环图G的所有结点。解决此类问题可以建立一个二分图模型。把所有顶点i拆成两个:X结点集中的i和Y结点集中的i',如果有边i->j,则在二分图中引入边i->j',设二分图最大匹配为m,则结果就是n-m。
二分图最大独立集=顶点数-二分图最大匹配
在N个点的图G中选出m个点,使这m个点两两之间没有边,求m最大值。
如果图G满足二分图条件,则可以用二分图匹配来做.最大独立集点数 = N - 最大匹配数。


最大匹配(吉大版)

int uN, vN;			///切记初始化!!
bool g[MAXN][MAXN], visit[MAXN];
int xM[MAXN], yM[MAXN];

int searchpath(int u)
{
    FE(v, 1, vN)
        if (g[u][v] && !visit[v])
        {
            visit[v] = 1;
            if (yM[v] == -1 || searchpath(yM[v]))
            {
                xM[u] = v;
                yM[v] = u;
                return 1;
            }
        }
    return 0;
}

int maxmatch()
{
    int ret = 0;
    CLR(xM, -1);
    CLR(yM, -1);
    FE(u, 1, uN)
        if (xM[u] == -1)
        {
            CLR(visit, 0);
            ret += searchpath(u);
        }
    return ret;
}

二分图最大基数匹配,邻接矩阵写法

struct BPM
{
  int n, m;               // 左右顶点个数
  int G[maxn][maxn];      // 邻接表
  int left[maxn];       // left[i]为右边第i个点的匹配点编号,-1表示不存在
  bool T[maxn];           // T[i]为右边第i个点是否已标记
  void init(int n, int m) {
    this->n = n;
    this->m = m;
    memset(G, 0, sizeof(G));
  }

  bool match(int u){
    for(int v = 0; v < m; v++) if(G[u][v] && !T[v]) {
      T[v] = true;
      if (left[v] == -1 || match(left[v])){
        left[v] = u;
        return true;
      }
    }
    return false;
  }
  // 求最大匹配
  int solve() {
    memset(left, -1, sizeof(left));
    int ans = 0;
    for(int u = 0; u < n; u++) { // 从左边结点u开始增广
      memset(T, 0, sizeof(T));
      if(match(u)) ans++;
    }
    return ans;
  }
};

二分图最大基数匹配求覆盖集

struct BPM {
  int n, m;               // 左右顶点个数
  vector G[maxn];    // 邻接表
  int left[maxn];         // left[i]为右边第i个点的匹配点编号,-1表示不存在
  bool T[maxn];           // T[i]为右边第i个点是否已标记
  int right[maxn];        // 求最小覆盖用
  bool S[maxn];           // 求最小覆盖用

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

  void AddEdge(int u, int v) {
    G[u].push_back(v);
  }

  bool match(int u){
    S[u] = true;
    for(int i = 0; i < G[u].size(); i++) {
      int v = G[u][i];
      if (!T[v]){
        T[v] = true;
        if (left[v] == -1 || match(left[v])){
          left[v] = u;
          right[u] = v;
          return true;
        }
      }
    }
    return false;
  }

  // 求最大匹配
  int solve() {
    memset(left, -1, sizeof(left));
    memset(right, -1, sizeof(right));
    int ans = 0;
    for(int u = 0; u < n; u++) { // 从左边结点u开始增广
      memset(S, 0, sizeof(S));
      memset(T, 0, sizeof(T));
      if(match(u)) ans++;
    }
    return ans;
  }

  // 求最小覆盖。X和Y为最小覆盖中的点集
  int mincover(vector& X, vector& Y) {
    int ans = solve();
    memset(S, 0, sizeof(S));
    memset(T, 0, sizeof(T));
    for(int u = 0; u < n; u++)
      if(right[u] == -1) match(u); // 从所有X未盖点出发增广
    for(int u = 0; u < n; u++)
      if(!S[u]) X.push_back(u); // X中的未标记点
    for(int v = 0; v < m; v++)
      if(T[v]) Y.push_back(v);  // Y中的已标记点
   return ans;
  }
};

二分匹配(邻接表 + 时间戳优化)

struct Edge
{
	int to,next;
}edge[MAXM];
int head[MAXN],tot;
void init()
{
	tot = 0;
	CLR(head, -1);
}
void addEdge(int u,int v)
{
	edge[tot].to = v;
	edge[tot].next = head[u];
	head[u] = tot++;
}
int linker[MAXN];
int used[MAXN];
//时间戳when优化
int uN, when;

bool dfs(int u)
{
	for(int i = head[u];i != -1;i = edge[i].next)
	{
		int v = edge[i].to;
		if(used[v] != when)
		{
			used[v] = when;
			if(linker[v] == -1 || dfs(linker[v]))
			{
				linker[v] = u;
				return true;
			}
		}
	}
	return false;
}
bool hungary()
{
	memset(linker,-1,sizeof(linker));
	when = 0;
	for(int u = 0; u < uN;u++)
	{
		when++;
		if(!dfs(u))return false;
	}
	return true;
}

最佳完美匹配(最大权值)

////KM算法
int W[maxn][maxn], n;
int Lx[maxn], Ly[maxn];
int Left[maxn];
bool S[maxn], T[maxn];
int in[10100], stu[10100];

bool match(int i)
{
    S[i] = 1;
    FE(j, 1, n) if (Lx[i] + Ly[j] == W[i][j] && !T[j])
    {
        T[j] = 1;
        if (!Left[j] || match(Left[j]))
        {
            Left[j] = i;
            return 1;
        }
    }
    return 0;
}


void update()
{
    int a = 1 << 30;
    FE(i, 1, n) if (S[i])
        FE(j, 1, n) if (!T[j])
            a = min(a, Lx[i] +Ly[j] - W[i][j]);
    FE(i, 1, n)
    {
        if (S[i])   Lx[i] -= a;
        if (T[i])   Ly[i] += a;
    }
}

void KM()
{
    FE(i, 1, n)
    {
        Left[i] = Lx[i] = Ly[i] = 0;
        FE(j, 1, n)
            Lx[i] = max(Lx[i], W[i][j]);
    }
    FE(i, 1, n)
    {
        while (1)
        {
            FE(j, 1, n)
                S[j] = T[j] = 0;
            if (match(i))   break; else update();
        }
    }
}


//稳定婚姻问题
// LA3989 Ladies' Choice
#include
#include
using namespace std;

const int maxn = 1000 + 10;
int pref[maxn][maxn], order[maxn][maxn], next[maxn], future_husband[maxn], future_wife[maxn];
queue q; // 未订婚的男士队列

void engage(int man, int woman) {
  int m = future_husband[woman];
  if(m) {
    future_wife[m] = 0; // 抛弃现任未婚夫(如果有的话)
    q.push(m); // 加入未订婚男士队列
  }
  future_wife[man] = woman;
  future_husband[woman] = man;
}

int main() {
  int T;
  scanf("%d", &T);
  while(T--) {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
      for(int j = 1; j <= n; j++)
        scanf("%d", &pref[i][j]); // 编号为i的男士第j喜欢的人
      next[i] = 1; // 接下来应向排名为1的女士求婚
      future_wife[i] = 0; // 没有未婚妻
      q.push(i);
    }

    for(int i = 1; i <= n; i++) {
      for(int j = 1; j <= n; j++) {
        int x;
        scanf("%d", &x);
        order[i][x] = j; // 在编号为i的女士心目中,编号为x的男士的排名
      }
      future_husband[i] = 0; // 没有未婚夫
    }

    while(!q.empty()) {
      int man = q.front(); q.pop();
      int woman = pref[man][next[man]++];
      if(!future_husband[woman]) engage(man, woman); // woman没有未婚夫,直接订婚
      else if(order[woman][man] < order[woman][future_husband[woman]]) engage(man, woman); // 换未婚夫
      else q.push(man); // 下次再来
    }
    while(!q.empty()) q.pop();

    for(int i = 1; i <= n; i++) printf("%d\n", future_wife[i]);
    if(T) printf("\n");
  }
  return 0;
}

生成树问题

次小生成树-----最佳替代边

int dfs1(int u, int fa, int rt) //求点rt到 以u为根的数及其子树的最小距离
{
    REP(i, SZ(tree[u]))
    {
        int v = tree[u][i];
        if (v != fa)
            dp[rt][u] = min(dp[rt][u], dfs1(v, u, rt));
    }
    if (fa != rt)   dp[rt][u] = min(dp[rt][u], dis[rt][u]);
    return dp[rt][u];
}

int dfs2(int u, int fa, int rt)
{// 求 以rt为根的数及其子树 到 以u为根的数及其子树的最小距离
    int ans = dp[u][rt];
    REP(i, SZ(tree[u]))
    {
        int v = tree[u][i];
        if (v != fa)
            ans = min(ans, dfs2(v, u, rt));
    }
    return ans;
}

void solve()
{
    REP(i, n)
        dfs1(i, -1, i);
    REP(i, n)
    {
        REP(j, SZ(tree[i]))
        {
            int v = tree[i][j];
            if (d[i][v] != INF)
                continue;
            d[i][v] = d[v][i] = dfs2(v, i, i);
        }
    }
}


//////一次dfs版本
int dfs(int u, int fa, int rt)
{//用同层节点去更新
    int ans = INF;
    REP(i, SZ(G[u]))
    {
        int v  = G[u][i];
        if (v != fa)
        {
            int t = dfs(v, u, rt);
            ans = min(ans, t);
            dp[u][v] = dp[v][u] = min(dp[u][v], t);
        }
    }
    if (fa != rt)   ans = min(ans, dis[rt][u]);
    return ans;
}

prim();
    REP(i, n)
        dfs(i, -1, i);

次小生成树-----瓶颈路

void prim()
{
    mst = 0;
    REP(i, N)
        dis[i] = INF, fa[i] = -1;
    dis[0] = 0;
    CLR(cost, 0);
    int mi; int pos;
    REP(i, N)
    {
        mi = INF;
        REP(j, N)
            if (!vis[j] && dis[j] < mi)
            {
                mi = dis[j];
                pos = j;
            }
        mst += mi;
        REP(j, N)
            if (vis[j] && fa[j] != -1)
            {
                cost[j][pos] = max(cost[j][fa[pos]], g[fa[pos]][pos]);
                cost[pos][j] = cost[j][pos];
            }
        if (fa[pos] != -1)
            used[fa[pos]][pos] = used[pos][fa[pos]]= 1;
        vis[pos] = 1;
        REP(j, N)
            if (!vis[j] && dis[j] > g[pos][j])
            {
                dis[j] = g[pos][j];
                fa[j] = pos;
            }
    }

}


kruskal求次小生成树 + 瓶颈路

 //maxcost[i][j]为i->j的瓶颈路
//对于MST边u, v maxcost[u][v] = 0
int n, m, x[maxn], y[maxn], p[maxn];
int pa[maxn];
int findset(int x) { return pa[x] != x ? pa[x] = findset(pa[x]) : x; }
//G保存MST C保存MST边权
vector G[maxn];
vector C[maxn];
struct Edge {
  int x, y;
  double d;
  bool operator < (const Edge& rhs) const {
    return d < rhs.d;
  }
};

Edge e[maxn*maxn];
double maxcost[maxn][maxn];
vector nodes;

void dfs(int u, int fa, double facost) {
  for(int i = 0; i < nodes.size(); i++) {
    int x = nodes[i];
    maxcost[u][x] = maxcost[x][u] = max(maxcost[x][fa], facost);
  }
  nodes.push_back(u);
  for(int i = 0; i < G[u].size(); i++) {
    int v = G[u][i];
    if(v != fa) dfs(v, u, C[u][i]);
  }
}

double MST() {
  sort(e, e+m);
  for(int i = 0; i < n; i++) { pa[i] = i; G[i].clear(); C[i].clear(); }
  int cnt = 0;
  double ans = 0;
  for(int i = 0; i < m; i++) {
    int x = e[i].x, y = e[i].y, u = findset(x), v = findset(y);
    double d = e[i].d;
    if(u != v) {
      pa[u] = v;
      G[x].push_back(y); C[x].push_back(d);
      G[y].push_back(x); C[y].push_back(d);
      ans += d;
      if(++cnt == n-1) break;
    }
  }
  return ans;
}


prim求次小生成树

//use[u][v] = 2时,边在MST上
//use[u][v] = 1时,原图存在边。
//f[u][v]表示u->v的最小瓶颈路 初始化为0
double prim()
{
    int pre[maxn] = {-1};
    bool vis[maxn] = {0};
    double d[maxn], ret = 0;
    FF(i, 1, n+1) d[i] = INF;   d[1] = 0;
    FF(i, 1, n+1)
    {
        int pos;
        double tmp = INF;
        FF(j, 1, n+1) if(!vis[j] && d[j] < tmp) tmp = d[j], pos = j;
        if(pre[pos] != -1)
        {
            use[pre[pos]][pos] = use[pos][pre[pos]] = 2;
            FF(j, 1, n+1) if(vis[j]) f[pos][j] = f[j][pos] = max(f[j][pre[pos]], g[pre[pos]][pos]);
        }
        vis[pos] = 1;
        ret += d[pos];
        FF(j, 1, n+1) if(!vis[j] && use[pos][j] && g[pos][j] < d[j]) d[j] = g[pos][j], pre[j] = pos;
    }
    return ret;
}


斯坦纳树--spfa+状态压缩

hdu4085 Peach Blossom Spring

int n, m, k, st;
int mask[maxn], dp[Maxs], dis[maxn][Maxs];
bool inq[maxn][Maxs];
struct Edge{
    int u, v, w;
};
vector edges;
VI G[maxn];
queue Q;

void add(int u, int v, int w)
{
    edges.PB((Edge){u, v, w});
    edges.PB((Edge){v, u, w});
    int sz = edges.size();
    G[u].PB(sz - 2);
    G[v].PB(sz - 1);
}

void init()
{
    CLR(dp, 0x3f), CLR(dis, 0x3f);
    CLR(mask, 0), CLR(inq, 0);
    RIII(n, m, k);
    edges.clear();
    FE(i, 0, n + 1)   G[i].clear();
    st = 1 << 2 * k;
    REP(i, m)
    {
        int u, v, w;
        RIII(u, v, w);
        add(u, v, w);
    }
}

void spfa()
{
    while (!Q.empty())
    {
        int u = Q.front() / 10000, s0 = Q.front() % 10000;
        inq[u][s0] = 0;
        Q.pop();
        REP(i, SZ(G[u]))
        {
            Edge e = edges[G[u][i]];
            int v = e.v, ns = s0 | mask[e.v];
            if (dis[v][ns] > dis[u][s0] + e.w)
            {
                dis[v][ns] = dis[u][s0] + e.w;
                if (ns == s0 && !inq[v][ns])
                {
                    inq[v][ns] = 1;
                    Q.push(v * 10000 + ns);
                }
            }
        }
    }
}

bool check(int s)
{
    int a = 0;
    REP(i, k)
    {
        if (s & (1 << i))   a++;
        if (s & (1 << (k + i))) a--;
    }
    return a == 0;
}


void solve()
{
    FE(i, 1, k)						////初始化
    {
        mask[i] = 1 << (i - 1), dis[i][mask[i]] = 0;
        mask[n - i + 1] = 1 << (k + i - 1), dis[n - i + 1][mask[n - i + 1]] = 0;
    }
    REP(s, st)							////枚举集合
    {
        FE(i, 1, n)
        {
            for (int s0 = (s - 1) & s ; s0; s0 = (s0 - 1) & s)///枚举子集,通过子树划分转移状态
                dis[i][s] = min(dis[i][s], dis[i][s0 | mask[i]]+dis[i][(s - s0) | mask[i]]);
            if (dis[i][s] < INF && !inq[i][s])
            {
                inq[i][s] = 1;
                Q.push(i * 10000 + s);
            }
        }
        spfa();				///spfa转移状态
    }
    REP(s, st)
        FE(i, 1, n)
            dp[s] = min(dp[s], dis[i][s]);
    REP(s, st)
        if (check(s))
        {
            for (int s0 = s & (s - 1); s0; s0 = s & (s0 - 1))
                if (check(s0))
                    dp[s] = min(dp[s0] + dp[s - s0], dp[s]);
        }
    if (dp[st - 1] >= INF)
        puts("No solution");
    else
        WI(dp[st - 1]);
}

int main()
{
    int T;
    RI(T);
    while (T--)
    {
        init();
        solve();
    }
}

生成树计数问题


////给定一个无向图G,求它生成树的个数t
int degree[N];
LL C[N][N];
LL det(LL a[][N], int n)//生成树计数:Matrix-Tree定理
{
    LL ret=1;
    for(int i=1; i

固定根的最小树型图,邻接矩阵写法

struct MDST {
  int n;
  int w[maxn][maxn]; // 边权
  int vis[maxn];     // 访问标记,仅用来判断无解
  int ans;           // 计算答案
  int removed[maxn]; // 每个点是否被删除
  int cid[maxn];     // 所在圈编号
  int pre[maxn];     // 最小入边的起点
  int iw[maxn];      // 最小入边的权值
  int max_cid;       // 最大圈编号

  void init(int n) {
    this->n = n;
    for(int i = 0; i < n; i++)
      for(int j = 0; j < n; j++) w[i][j] = INF;
  }

  void AddEdge(int u, int v, int cost) {
    w[u][v] = min(w[u][v], cost); // 重边取权最小的
  }

  // 从s出发能到达多少个结点
  int dfs(int s) {
    vis[s] = 1;
    int ans = 1;
    for(int i = 0; i < n; i++)
      if(!vis[i] && w[s][i] < INF) ans += dfs(i);
    return ans;
  }

  // 从u出发沿着pre指针找圈
  bool cycle(int u) {
    max_cid++;
    int v = u;
    while(cid[v] != max_cid) { cid[v] = max_cid; v = pre[v]; }
    return v == u;
  }

  // 计算u的最小入弧,入弧起点不得在圈c中
  void update(int u) {
    iw[u] = INF;
    for(int i = 0; i < n; i++)
      if(!removed[i] && w[i][u] < iw[u]) {
        iw[u] = w[i][u];
        pre[u] = i;
      }
  }

  // 根结点为s,如果失败则返回false
  bool solve(int s) {
    memset(vis, 0, sizeof(vis));
    if(dfs(s) != n) return false;

    memset(removed, 0, sizeof(removed));
    memset(cid, 0, sizeof(cid));
    for(int u = 0; u < n; u++) update(u);
    pre[s] = s; iw[s] = 0; // 根结点特殊处理
    ans = max_cid = 0;
    for(;;) {
      bool have_cycle = false;
      for(int u = 0; u < n; u++) if(u != s && !removed[u] && cycle(u)){
        have_cycle = true;
        // 以下代码缩圈,圈上除了u之外的结点均删除
        int v = u;
        do {
          if(v != u) removed[v] = 1;
          ans += iw[v];
          // 对于圈外点i,把边i->v改成i->u(并调整权值);v->i改为u->i
          // 注意圈上可能还有一个v'使得i->v'或者v'->i存在,因此只保留权值最小的i->u和u->i
          for(int i = 0; i < n; i++) if(cid[i] != cid[u] && !removed[i]) {
            if(w[i][v] < INF) w[i][u] = min(w[i][u], w[i][v]-iw[v]);
            w[u][i] = min(w[u][i], w[v][i]);
            if(pre[i] == v) pre[i] = u;
          }
          v = pre[v];
        } while(v != u);
        update(u);
        break;
      }
      if(!have_cycle) break;
    }
    for(int i = 0; i < n; i++)
      if(!removed[i]) ans += iw[i];
    return true;
  }
};

TwoSAT问题

两数AND为1
sat.add_clause(x, 1, x, 1);
sat.add_clause(y, 1, y, 1);

AND为0
sat.add_clause(x, 0, y, 0);

两数OR为1
sat.add_clause(x, 1, y, 1);
两数OR为0
sat.add_clause(x, 0, x, 0);
sat.add_clause(y, 0, y, 0);

两数XOR为1
sat.add_clause(x, 0, y, 0);
sat.add_clause(x, 1, y, 1);
两数XOR为0
sat.add_clause(x, 1, y, 0);
sat.add_clause(x, 0, y, 1);

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

    bool dfs(int x)
    {
        if (mark[x ^ 1])
            return false;
        if (mark[x])
            return true;
        mark[x] = true;
        S[c++] = x;
        for (int i = 0; i < G[x].size(); i++)
            if (!dfs[G[x][i]])
                return false;
        return true;
    }

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

void add_clause(intx, int xval, int y, int yval)	
{								/////x = xval OR y = yval 即不能同时取!val与!yval

        x = x * 2 + xval;
        y = y * 2 + yval;
        G[x ^ 1].push_back(y);
        G[y ^ 1].push_back(x);
    }

    bool solve()
    {
        for (int i = 0; i < n * 2; i += 2)
            if (!mark[i] && !mark[i + 1])
            {
                c = 0;
                if (!dfs(i))
                {
                    while (c > 0)
                        mark[S[--c]] = false;
                    if (!dfs(i + 1))
                        return false;
                }
            }
    }
    return true;
};



你可能感兴趣的:(ACM,图论)