图论模板整理

来源:互联网 发布:linux 文件所有者 编辑:程序博客网 时间:2024/05/14 23:36

模板大部分来自LRJ


连通性

割点

<pre name="code" class="cpp">//带重边处理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<int> 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为-1int 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]个部分(根节点没有祖先))



点双连通分量

<pre name="code" class="cpp">//当u为割点时,把边从栈顶依次取出,直到遇到边(u,v),取出的边与其关联的点,组成点双连通分支。//割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支//基本算法与求割点一致int dfn[MAXN], iscut[MAXN], bccno[MAXN];int dfs_clock, bcc_cnt;vector<int> G[MAXN], bcc[MAXN];struct Edge{    int u, v;};stack<Edge> 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<Edge> 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);}elseif(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<int> G[MAXN];int dfn[MAXN], low[MAXN], sccno[MAXN], dfs_clock, scc_cnt;stack<int> 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<Edge> edges;    vector<int> 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<Node> 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<Edge> edges;    vector<int> 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<int> 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<Edge> 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<int> 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<Edge> 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<Node> 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<Node> 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<int> 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<int>& X, vector<int>& 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<cstdio>#include<queue>using namespace std;const int maxn = 1000 + 10;int pref[maxn][maxn], order[maxn][maxn], next[maxn], future_husband[maxn], future_wife[maxn];queue<int> 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;            }    }<p>}</p>

kruskal求次小生成树 + 瓶颈路

 //maxcost[i][j]为i->j的瓶颈路//对于MST边u, v maxcost[u][v] = 0int 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<int> G[maxn];vector<double> 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<int> 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时,边<u, v>在MST上//use[u][v] = 1时,原图存在边<u, v>。//f[u][v]表示u->v的最小瓶颈路 初始化为0double 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 Springint n, m, k, st;int mask[maxn], dp[Maxs], dis[maxn][Maxs];bool inq[maxn][Maxs];struct Edge{    int u, v, w;};vector<Edge> edges;VI G[maxn];queue<int> 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,求它生成树的个数tint degree[N];LL C[N][N];LL det(LL a[][N], int n)//生成树计数:Matrix-Tree定理{    LL ret=1;    for(int i=1; i<n; i++)    {        for(int j=i+1; j<n; j++)            while(a[j][i])            {                LL t=a[i][i]/a[j][i];                for(int k=i; k<n; k++)                    a[i][k]=(a[i][k]-a[j][k]*t);                for(int k=i; k<n; k++)                    swap(a[i][k],a[j][k]);                ret=-ret;            }        if(a[i][i]==0)            return 0;        ret=ret*a[i][i];    }    if(ret<0)        ret=-ret;    return ret;}void solve(){    memset(degree,0,sizeof(degree));    memset(C,0,sizeof(C));    scanf("%d%d",&n,&m);    while(m--)    {        scanf("%d%d",&u,&v);        u--;        v--;        C[u][v]=C[v][u]=-1;        degree[u]++;        degree[v]++;    }    for(int i=0; i<n; ++i)        C[i][i]=degree[i];    printf("%lld\n",det(C,n));}

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

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<int> 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;};



5 0
原创粉丝点击