Day6 && Day7图论

并查集

A-How Many Answers Are Wrong

题意:已知区间[1,n],给出m组数据,即[l,r]区间内数据之和为s,求错误数据的数量。
思路:

  • 并查集中路径压缩的过程中如何更新关系域是关键
  • 根据数据定义一些合理的关系(如:集合中的根与集合中的元素的关系),才能对集合进行合并。这里可能会分一些情况讨论,最终应该是可以化简的
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 200020;
int f[maxn];
int sum[maxn];   //记录当前结点到根的距离
int find(int x){
//return x==f[x]?x:find(f[x]);
    if(x != f[x]){
        int roota = f[x];
        f[x] = find(f[x]);
        sum[x] += sum[roota];
        //x->rootb = x->roota + roota->rootb
    }
    return f[x];
}
int main(){
    //freopen("in.txt","r",stdin);
    int n,m;
    while(scanf("%d%d", &n, &m) != EOF){
        for(int i=0; i<=n; i++){
            f[i] = i;
            sum[i] = 0;
        }
        int ans = 0;
        while(m--){
            int a, b, v;
            scanf("%d%d%d", &a, &b, &v);
            a--;
            //join
            int roota = find(a);
            int rootb = find(b);
            if(roota == rootb){
                if(sum[a]-sum[b] != v)    ans++;
            }else{
                f[roota] = rootb;
                //定义大的数字是小的数字的根
                sum[roota] = -sum[a] + sum[b] + v;
                //变换成等式 sum[roota] + sum[a] = sum[b] + v;
                //a->roota + roota->rootb == b->rootb + a->b
            }
            //join
        }
        printf("%d\n",ans);
    }
    return 0;
}

B - 食物链

对于集合里的任意两个元素x,y而言,它们之间必定存在着某种联系,因为并查集中的元素均是有联系的(这点是并查集的实质),否则也不会被合并到当前集合中。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 50010;
int f[maxn], relation[maxn];
//relation[i]:i到根的偏移量
int sum;
void init(int n){
    for(int i=0; i<n; i++){
        f[i] = i;
        relation[i] = 0;
    }
    sum = 0;
}
int find(int x){
    if(x != f[x]){
        int rootx = f[x];
        f[x] = find(rootx);
        //路径压缩更新关系域//这是关键1
        relation[x] = (relation[x] + relation[rootx]) % 3;
    }
    return f[x];
}
void join(int x, int y, int d){
    int rootx = find(x);
    int rooty = find(y);
    if(rootx != rooty){
        f[rooty] = rootx;
        relation[rooty] = (3 + d-1 + relation[x] - relation[y]) % 3;
        //此处d-1题中给出的询问已知条件即0同类,1吃,2被吃
        /*先用等式表达:
        这里并非简单的加减,实质应该是关系的转移,可以用向量理解
             r->rx + x->y = y->ry + ry->rx
        ====>r[x] + k = r[y] + ry->rx
        ====>ry->rx = r[x] + k - r[y]
           k = d - 1
           同理下面两式也得
        */
    }else{
        if((d==1) && (relation[x] != relation[y])){
            sum++;
        }else if((d==2) && ((d-1) != (relation[y] - relation[x] + 3) % 3)){
            sum++;
        }
    }
}
int main(){
    //freopen("in.txt","r",stdin);
    int n, k;
    scanf("%d%d", &n, &k);
    init(n);
    for(int i=0; i<k; i++){
        int d, x, y;
        scanf("%d%d%d",&d, &x, &y);
        if(x > n || y > n){
            sum++;
        }else if((d==2) && (x == y)){
            sum++;
        }else{
            join(x, y, d);
        }
    }
    printf("%d\n",sum);
    return 0;
}

C - A Bug's Life

题意:给定n个bugs,编号依次1、2、……n。它们之间只有雄性和雌性之分,并给定m对交配的情况,根据这m对交配的情况,判断是否有同性恋出现的情况。

Thinking:

  • 定义relation[i]:结点i到根的距离:即相对与根节点的性别。
  • x,y在同一集合,他们相对根的距离即相对根的性别,而他们又同根,易得x,y的相对性别
  • x,y不再同一集合,思考向量得关系: y->x + x->rootx = y->rooty + rooty->rootx 其中y->x应为1。
#include <cstdio>
const int maxn = 2010;
int f[maxn];
int relation[maxn];
bool flag;
void init(int n){
    //这里n没有初始完( i < n )导致WA了半天
    for(int i=0; i<=n; i++){
        f[i] = i;
        relation[i] = 0;
    }
}
int find(int x){
    if(x != f[x]){
        int rootx = f[x];
        f[x] = find(rootx);
        relation[x] = (relation[rootx] + relation[x]) % 2;
    }
    return f[x];
}
void join(int x, int y){
    int rootx = find(x);
    int rooty = find(y);
    if(rootx == rooty){
        if(relation[x] == relation[y]){
            flag = true;
        }
    }else{
        /*A了这题后在网上看到很多解法中这里对x,y进行分类,
        即 x<y  :
                f[rooty] = rootx;
                relation[rooty] = ((relation[x]+1) + relation[y]) % 2;
          x>y    :
                   f[rootx] = rooty;
                relation[rootx] = ((relation[y]+1)%2 + relation[x]) % 2;
            这里我认为应该可以不需要,因为查找过程中进行了路径压缩
        */
        f[rooty] = rootx;
        relation[rooty] = ((relation[x]+1) - relation[y]) % 2;
            //这里+由向量y->x + x->rootx = y->rooty + rooty->rootx得应该是-,但%2好像算术上是等价的
            //这里我个人认为是减,当然%2时加和减结果一样, 这里留待思考?????
    }
}
int main(){
    //freopen("in.txt","r",stdin);
    int T;
    scanf("%d", &T);
    for(int t=1; t<=T; t++){
        int b, inter;
        scanf("%d%d",&b, &inter);
        init(b);
        flag = false;
        for(int i=0; i<inter; i++){
            int x, y;
            scanf("%d%d", &x, &y);
            if(flag){
                continue;
            }else{
                join(x, y);
            }
        }
        printf("Scenario #%d:\n", t);
        if(flag){
            printf("Suspicious bugs found!\n\n");
        }else{
            printf("No suspicious bugs found!\n\n");
        }
    }
    return 0;
}

无向图的割点

E - SPF

题意:一个网络中割点的个数,和割点可以将网络分成几部分。

Thinking:

  • 不用belong记录强连通分量集合的编号,需要加入parent[]记录父节点,child判断子树数量。这是为了分开计算割点是根 和非根的情况。
  • 割点的判断:number[u]<=low[v]即v必须通过u才能访问u的祖先,这里u就是割点。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1005;
struct Node{
    int to;
    int next;
}edge[maxn*maxn];
int head[maxn], E, V;
void add_edge(int u, int v){
    edge[E].to = v;    edge[E].next = head[u];    head[u] = E++;
    edge[E].to = u;    edge[E].next = head[v];    head[v] = E++;
    V = max(max(u,v), V);
}
int size_[maxn];
// 记录删除i结点后的连通分量个数
int low[maxn], number[maxn], parent[maxn];
////Let LOWPT(v) be the smallest vertex in the set ${v}\bigcap { w| v \xrightarrow{*} -\rightarrow w}$
///number[u]为结点u搜索的次序编号
///parent[i]:记录i的父亲结点
bool flag; ///是否有割点存在
int cnt;

void dfs(int u){
    int child = 0;
    //记录子树的数量
    low[u] = number[u] = ++cnt;
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v = edge[i].to;
        if(!number[v]){
            child++;
            parent[v] = u;
            dfs(v);
            ///dfs(v)更新low[v]
            low[u] = min(low[u], low[v]);
            //必须通过u,v才能访问u的祖先number[u]<=low[v]
            if((parent[u]==-1 && child>=2) || (parent[u]!=-1 && number[u]<=low[v])){
                /*
                child>2保证如果是根节点,将比非根节点少加1
                因为如果割点不是根节点,其连通分量要加上其父辈
                */
                flag = true;
                size_[u]++;
            }
        }else{
            low[u] = min(low[u], number[v]);
        }
    }
}
void init(){
    memset(size_, 0, sizeof(size_));
    memset(number, 0, sizeof(number));
    memset(low, 0, sizeof(low));
    memset(head, -1, sizeof(head));
    memset(parent, -1, sizeof(parent));
    E = V = 0;
    flag = false;
    cnt = 0;
}
int main(){
//    freopen("in.txt","r", stdin);
    int cas;
    for(int tt=1; ;tt++){
        init();
        //input
        int u, v=-1;
        while(scanf("%d", &u) && u){
            scanf("%d", &v);
            add_edge(u, v);
        }
        if(v == -1)    break;
        //input
        dfs(V);
        printf("Network #%d\n",tt);
        if(flag){
            for(int i=1; i <= V; i++){
                if(size_[i] > 0){
                    printf("  SPF node %d leaves %d subnets\n",i,size_[i]+1);
                }
            }
        }else{
            printf("  No SPF nodes\n");
        }
        printf("\n");
    }
    return 0;
}

Thinking2:

  • pre[v]<pre[u]&&v!=f 理解:在无向图中edge(u, f)不是反向边(第一次处理时从后代指向祖先的边),只是树边edge(f, u)的第二次访问。
  • if(lowv > pre[u]) { u->v is bridge} 这是对桥的判断。
  • 在无向连通图G的dfs树中,非根结点u是G的割点当且仅当u存在一个子节点v,使得v及其所有后代都没有反向边连回u的祖先(连回u不算)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
using namespace std;

const int maxn = 1010;
vector<int> G[maxn];

int iscut[maxn];

int low[maxn], pre[maxn];
//pre:时间戳
//low[u]:u及其后代所能连回的最早祖先的pre值
int cnt, V;

int dfs(int u, int f){
    int lowu = pre[u] = ++cnt;
    int child = 0;   //子节点数目
    for(int i=0; i<G[u].size(); i++){
        int v = G[u][i];
        if(!pre[v]){   //未访问v
            child++;
            int lowv = dfs(v, u);
            lowu = min(lowv, lowu);   //用后代low更新low u
            if(lowv >= pre[u]){
                iscut[u]++;
            }
        }else if(pre[v]<pre[u] && v!=f){
        //v==f无向图,此边不属于反向边,属于树边的二次访问
            lowu = min(lowu, pre[v]);  //反向边更新u
        }
    }
    if(f<0 && child==1){ //根节点特判
        iscut[u] = 0;
    }
    return low[u] = lowu;
}
void add_edge(int a, int b){
    G[a].push_back(b); G[b].push_back(a);
    V = max(max(a, b), V);
}
void init(){
    memset(pre, 0, sizeof(pre));
    memset(iscut, 0, sizeof(iscut));
    for(int i=0; i<maxn; i++){  G[i].clear();  }
    cnt = 0;  V = 0;
}
int main(){
    //freopen("in.txt", "r", stdin);
    for(int tt=1; ;tt++){
        init();
        int u, v=-1;
        while(scanf("%d", &u) && u){
            scanf("%d", &v);
            add_edge(u, v);
        }
        if(v == -1) break;
        dfs(V, -1);
        printf("Network #%d\n", tt);
        bool flag = true;
        for(int i=0; i<=V; i++){
            if(iscut[i]){
                printf("  SPF node %d leaves %d subnets\n", i, iscut[i]+1);
                flag = false;
            }
        }
        if(flag){
            printf("  No SPF nodes\n");
        }puts("");
    }
    return 0;
}

有向图的强连通分量

F - Proving Equivalences

题意:给一个有向图,试求添加多少条边可以使该图成为强连通图。

Thinking:

  • 要利用DAG的性质,则需要用Tarjan算法缩点,将有向图转为DAG。
  • 至于添加多少条边,可以这样想,怎样操作能构成最大环,即将DAG头尾相连即可。即求入度和出度的max();
  • 需要特判强连通分量个数为一的情况如(1,2)(2,1),则不需要加边。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>
using namespace std;
const int maxn = 20010;
struct Node{
    int to;
    int next;
}edge[maxn * 3];
int head[maxn],cnt;
stack<int> S;
int low[maxn], number[maxn];
int belong[maxn], scc;
//保存每个点属于的强连通集合的编号;  强连通分量编号最大值(即个数
void add_edge(int u, int v){
    edge[++cnt].to = v;
    edge[cnt].next = head[u];
    head[u] = cnt;
}
void dfs(int u){
    S.push(u);
    number[u] = low[u] = ++cnt;
    for(int i = head[u]; i!=-1; i = edge[i].next){
        int v = edge[i].to;
        if(!number[v]){
            dfs(v);
            low[u] = min(low[u], low[v]);
        }else if(!belong[v]){
            low[u] = min(low[u], number[v]);
        }
    }
    if(number[u] == low[u]){
        scc++;
        int v;
        do{
            v = S.top(), S.pop();
            belong[v] = scc;
        }while(u != v);
    }
}
int in[maxn], out[maxn];
void init(){
    cnt = scc = 0;
    memset(head, -1, sizeof(head));
    memset(number, 0, sizeof(number));
    memset(low, 0, sizeof(low));
    memset(in, 0, sizeof(in));
    memset(out, 0, sizeof(out));
    memset(belong, 0, sizeof(belong));
    while(!S.empty())    S.pop();
}
int main(){
    //freopen("in.txt", "r", stdin);
    int T;
    scanf("%d", &T);
    while(T--){
        init();
        int n, m;
        scanf("%d%d", &n, &m);
        for(int i=0; i<m; i++){
            int u, v;
            scanf("%d%d", &u, &v);
            add_edge(u, v);
        }
        cnt = 0;
        for(int i=1; i<=n; i++){
            if(!number[i]){
                dfs(i);
            }
        }
        if(scc == 1){
            printf("0\n");
            continue;
        }
        for(int i=1; i<=n; i++){
            for(int j=head[i]; j!=-1; j=edge[j].next){
                int v = edge[j].to;
                if(belong[v] != belong[i]){
                    in[belong[v]]++;
                    out[belong[i]]++;
                }
            }
        }
        int ans1 = 0, ans2 = 0;
        for(int i=1; i<=scc; i++){
            if(!out[i])    ans1++;
            if(!in[i])    ans2++;
        }
        printf("%d\n",max(ans1, ans2));
    }
    return 0;
}
#include <bits/stdc++.h>
using namespace std;

const int maxn = 20010;
vector<int> G[maxn];
stack<int> S;
int pre[maxn], lowlink[maxn], sccno[maxn], dfs_clock, scc_cnt;
//sccno[i]: i所在的SCC编号
void dfs(int u){
    pre[u] = lowlink[u] = ++dfs_clock;
    S.push(u);
    for(int i=0; i<G[u].size(); i++){
        int v = G[u][i];
        if(!pre[v]){
            dfs(v);
            lowlink[u] = min(lowlink[u], lowlink[v]);
        }else if(!sccno[v]){
            lowlink[u] = min(lowlink[u], pre[v]);
        }
    }
    if(lowlink[u] == pre[u]){
        scc_cnt++;
        while(true){
            int x = S.top();  S.pop();
            sccno[x] = scc_cnt;
            if(x == u) break;
        }
    }
}
int find_scc(int n){
    dfs_clock = scc_cnt = 0;
    memset(sccno, 0, sizeof(sccno));
    memset(pre, 0, sizeof(pre));
    for(int i=0; i<n; i++){
        if(!pre[i]) dfs(i);
    }
}
int in[maxn], out[maxn];

int main(){
    //freopen("in.txt", "r", stdin);
    int t; scanf("%d", &t);
    while(t--){
        int n, m;  scanf("%d%d", &n, &m);
        for(int i=0; i<n; i++) G[i].clear();
        for(int i=0; i<m; i++){
            int a, b; scanf("%d%d", &a, &b); a--, b--;
            G[a].push_back(b);
        }
        find_scc(n);
        // 对缩点后的DAG入度和出度计算
        //in[i]:i的入度为0
        for(int i=1; i<=scc_cnt; i++) in[i] = out[i] = 1;
        for(int u=0; u<n; u++){
            for(int i=0; i<G[u].size(); i++){
                int v = G[u][i];
                if(sccno[u] != sccno[v]) in[sccno[v]] = out[sccno[u]] = 0;
                //u,v对应的出入度不为0
            }
        }
        //
        int a = 0, b = 0;
        for(int i=1; i<=scc_cnt; i++){
            if(in[i])  a++;
            if(out[i]) b++;
        }
        int ans = max(a, b);
        if(scc_cnt == 1) ans = 0;
        printf("%d\n", ans);
    }
    return 0;
}

G - The Largest Clique

题意:给一个有向图G,求一个结点数最大的结点集(任意两点相互可达)。

Targan缩点 + DAG上的最长路。 给一个含圈的有向图,求最长路。

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

const int maxn = 1010;
vector<int> G[maxn], mp[maxn];
stack<int> S;
int pre[maxn], lowlink[maxn], sccno[maxn], dfs_clock, scc_cnt;
//sccno[i]: i所在的SCC编号
void dfs(int u){
    pre[u] = lowlink[u] = ++dfs_clock;
    S.push(u);
    for(int i=0; i<G[u].size(); i++){
        int v = G[u][i];
        if(!pre[v]){
            dfs(v);
            lowlink[u] = min(lowlink[u], lowlink[v]);
        }else if(!sccno[v]){
            lowlink[u] = min(lowlink[u], pre[v]);
        }
    }
    if(lowlink[u] == pre[u]){
        scc_cnt++;
        int x;
        do{
            x = S.top();  S.pop();
            sccno[x] = scc_cnt;
        }while(x != u);
    }
}
void find_scc(int n){
    dfs_clock = scc_cnt = 0;
    memset(sccno, 0, sizeof(sccno));
    memset(pre, 0, sizeof(pre));
    for(int i=0; i<n; i++){
        if(!pre[i]) dfs(i);
    }
}
int size_[maxn], d[maxn];

//DAG上的最长距离
int dp(int u) {
    if(d[u] >= 0) return d[u];
    d[u] = size_[u];
    for(int i = 0; i < mp[u].size(); i++){
        int v = mp[u][i];
        d[u] = max(d[u], dp(v) + size_[u]);
    }
    return d[u];
}
void build(int n){
    for(int i=0; i<=scc_cnt; i++) mp[i].clear();
    memset(size_, 0, sizeof(size_));
    for(int i=0; i<n; i++) size_[sccno[i]]++;
    for(int i=0; i<n; i++){
        for(int j=0; j<G[i].size(); j++){
            int v = G[i][j];
            if(sccno[i] != sccno[v]){
                mp[sccno[i]].push_back(sccno[v]);
            }
        }
    }
}
int main(){
//    freopen("in.txt", "r", stdin);
    int t; scanf("%d", &t);
    while(t--){
        int n, m;  scanf("%d%d", &n, &m);
        for(int i=0; i<n; i++) G[i].clear();
        for(int i=0; i<m; i++){
            int a, b; scanf("%d%d", &a, &b); a--; b--;
            G[a].push_back(b);
        }

        find_scc(n);

        build(n);

        int ans = 0;
        memset(d, -1, sizeof(d));
        for(int i=1; i<=scc_cnt; i++){
            ans = max(ans, dp(i));
        }
        printf("%d\n", ans);
    }
    return 0;
}

无向图的双连通分量

K - Knights of the Round Table(uva 3523)

题意:n个骑士举行会议,每次圆桌会议至少三人,且骑士数目为奇数,相互憎恨的骑士不能再相邻位置,问多少个骑士不能参加会议。

二分图 + BCC

建图:两个骑士可以相邻,则建一条无向边。

求不在任何一个简奇圈上的结点个数。

简单圈上的所有结点必然属于同一个双连通分量;二分图没有奇圈。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;

struct edge{
    int u, v;
    edge(int a, int b) : u(a), v(b) {}
};
int pre[maxn], iscut[maxn], bccno[maxn], dfs_clock, bcc_cnt;
//bccno[i]=x:第i个顶点属于第x个点-双连通分量
vector<int> G[maxn], bcc[maxn];
//bcc[i]: 编号为i的点-双连通分量的所有结点
stack<edge> S;

int dfs(int u, int f){
    int lowu = pre[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(!pre[v]){
            S.push(e);
            child++;
            int lowv = dfs(v, u);
            if(lowv >= pre[u]){
                iscut[u]++;
                bcc_cnt++; bcc[bcc_cnt].clear();
                while(true){
                    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){
                        break;
                    }
                }
            }
        }else if(pre[v] < pre[u] && v != f){
            S.push(e);
            lowu = min(lowu, pre[v]);
        }
    }
    if(f < 0 && child==1) iscut[u]=0;
    return lowu;
}

void find_bcc(int n){
    memset(pre, 0, sizeof(pre));
    memset(iscut, 0, sizeof(iscut));
    memset(bccno, 0,  sizeof(bccno));
    dfs_clock = bcc_cnt = 0;
    for(int i=0; i<n; i++){
        if(!pre[i]) dfs(i, -1);
    }
}
int color[maxn];
bool odd[maxn];
bool bipartite(int u, int b){
    for(int i=0; i<G[u].size(); i++){
        int v = G[u][i];
        if(bccno[v] != b) continue;
        if(color[v] == color[u]){
            return false;
        }
        if(!color[v]){
            color[v] = 3 - color[u];
            if(!bipartite(v, b)) return false;
        }
    }
    return true;
}
int A[maxn][maxn];

int main(){
    //freopen("in.txt", "r", stdin);
    int kase = 0, n, m;
    while(scanf("%d%d", &n, &m) != EOF && n){
        for(int i=0;i<n;i++)G[i].clear();
        memset(A, 0, sizeof(A));

        for(int i=0;i<m;i++){
            int a,b; scanf("%d%d",&a, &b); a--,b--;
            A[a][b] = A[b][a] = 1;
        }
        for(int i=0; i<n; i++){
            for(int j=i+1; j<n; j++){
                if(!A[i][j]) G[i].push_back(j), G[j].push_back(i);
            }
        }

        find_bcc(n);

        memset(odd, 0, sizeof(odd));
        for(int i=1; i<=bcc_cnt; i++){
            for(int j=0; j<bcc[i].size(); j++){  //同一个BBC内统一编号
                bccno[bcc[i][j]] = i;
            }
            int u = bcc[i][0];

            memset(color, 0, sizeof(color));
            color[u] = 1;
            if(!bipartite(u, i)){   //不是二分图,标记为奇圈
                for(int j=0; j<bcc[i].size(); j++){
                    odd[bcc[i][j]] = true;
                }
            }
        }
        int ans = n;
        for(int i=0; i<n; i++) if(odd[i]) ans--;
        printf("%d\n", ans);
    }
    return 0;
}

作者:seaupnice

出处:https://www.cnblogs.com/seaupnice/p/9440137.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   seaupnice  阅读(116)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示