连通图(tarjan等)

缩点

P2341 [HAOI2006]受欢迎的牛

题意:

给牛的个数n,和m条可传递关系,例如a->b表示a喜欢b
问被所有牛喜欢的牛有多少

分析:

缩点之后如果只有一个出度为0的超级点,那么这个点里的原始点数量就是答案,否则答案为0

code:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
#include<stack>
#include<algorithm>
typedef long long ll;
const int inf=0x3f3f3f3f;
const int inn=0x80808080;
using namespace std;
const int maxm=1e5+5;
int low[maxm],dfn[maxm],idx;
int head[maxm],to[maxm],nt[maxm],cnt;
int out[maxm];
int belong[maxm],sum,num[maxm];
bool ins[maxm];
stack<int>s;
void init(){
    memset(low,0,sizeof low);
    memset(dfn,0,sizeof dfn);
    memset(head,-1,sizeof head);
    memset(belong,0,sizeof belong);
    memset(out,0,sizeof out);
    memset(ins,0,sizeof ins);
    while(!s.empty())s.pop();
    idx=0;
    cnt=1;
    sum=0;
}
void add(int x,int y){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void dfs(int u){
    low[u]=dfn[u]=++idx;
    s.push(u);
    ins[u]=1;
    for(int i=head[u];i!=-1;i=nt[i]){
        int v=to[i];
        if(!dfn[v]){
            dfs(v);
            low[u]=min(low[u],low[v]);
        }else if(ins[v]){
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(low[u]==dfn[u]){
        sum++;
        while(1){
            int x=s.top();
            s.pop();
            ins[x]=0;
            belong[x]=sum;
            num[sum]++;
            if(x==u)break;
        }
    }
}
int main(){
    init();
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            dfs(i);
        }
    }
    for(int k=1;k<=n;k++){
        for(int i=head[k];i!=-1;i=nt[i]){
            int v=to[i];
            if(belong[k]!=belong[v]){
                out[belong[k]]++;//out++
            }
        }
    }
    vector<int>ans;
    for(int i=1;i<=sum;i++){
        if(!out[i]){
            ans.push_back(i);
        }
    }
    if(ans.size()==1){
        cout<<num[ans[0]]<<endl;
    }else{
        cout<<0<<endl;
    }
    return 0;
}

文件传递问题&&最少加多少边成为强连通图

poj1236 Network of Schools

题意:

n个学校,每个学校可以通讯若干个学校(即有向边)
问题1:现在要传递文件,问开始最少给多少个学校,能由这些学校传递给其他所有学校
问题2:要添加多少条边使得所有学校强连通

分析:

先缩点,计算超级点的入度和出度。
问题1的答案是:入度为0的超级点的个数
问题2的答案是:入度为0的点的个数,出度为0的点个数,两者的最大值
这里就不证明了

另外要特判一下开始就直接缩成一个点的情况,这种情况下问题2的答案为0

code:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<map>
#include<stack>
#include<vector>
#include<cmath>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxm=1e4+5;
int head[maxm],nt[maxm],to[maxm],cnt;
int low[maxm],dfn[maxm],idx;
int belong[maxm],sum;
int in[maxm],out[maxm];
bool ins[maxm];
stack<int>s;
void init(){
    memset(head,-1,sizeof head);
    memset(low,0,sizeof low);
    memset(dfn,0,sizeof dfn);
    memset(in,0,sizeof in);
    memset(out,0,sizeof out);
    memset(belong,0,sizeof belong);
    memset(ins,0,sizeof ins);
    sum=0;
    idx=0;
    cnt=1;
}
void add(int x,int y){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void dfs(int u){
    low[u]=dfn[u]=++idx;
    s.push(u);
    ins[u]=1;
    for(int i=head[u];i!=-1;i=nt[i]){
        int v=to[i];
        if(!dfn[v]){
            dfs(v);
            low[u]=min(low[u],low[v]);
        }else if(ins[v]){
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(low[u]==dfn[u]){
        sum++;
        while(1){
            int x=s.top();
            s.pop();
            ins[x]=0;
            belong[x]=sum;
            if(x==u)break;
        }
    }
}
int main() {
    init();
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        while(1){
            int t;
            cin>>t;
            if(!t)break;
            add(i,t);
        }
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            dfs(i);
        }
    }
    if(sum==1){//特判1的时候
        cout<<1<<endl;
        cout<<0<<endl;
        return 0;
    }
    for(int k=1;k<=cnt;k++){
        for(int i=head[k];i!=-1;i=nt[i]){
            int v=to[i];
            if(belong[k]!=belong[v]){
                in[belong[v]]++;
                out[belong[k]]++;
            }
        }
    }
    int inn=0,outt=0;
    for(int i=1;i<=sum;i++){
        if(!in[i])inn++;
        if(!out[i])outt++;
    }
    cout<<inn<<endl;
    cout<<max(inn,outt)<<endl;
    return 0;
}

无向图割点

例题:uva315(以前写的)
题意:

求割点数量

割点求法:

1.如果是根节点,有两棵以上子树,则该根节点是割点
2.如果不是根节点,假设当前点为x,去的点为y,当low[y]>=dfn[x]的时候点x是割点

code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<stack>
#include<set>
#include<sstream>
#include<algorithm>
#define lowbit(x) (x&(-(x)))
#define coff ios::sync_with_stdio(0)
const int inf=0x3f3f3f3f;
const int inn=0x80808080;
using namespace std;
const int maxm=1e3+5;
int low[maxm],dfn[maxm],idx;
int head[maxm],nt[maxm<<1],to[maxm<<1],cnt;
set<int>ans;
int n;
int root;
void init(){
    memset(low,0,sizeof low);
    memset(dfn,0,sizeof dfn);
    memset(head,-1,sizeof head);
    cnt=1;
    idx=0;
    ans.clear();
}
void add(int x,int y){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void input(){
    string s;
    while(getline(cin,s)){
        if(s=="0")break;
        stringstream str(s);
        int st;
        str>>st;
        int ed;
        while(str>>ed){
            add(st,ed);
            add(ed,st);
        }
    }
}
void dfs(int x){
    low[x]=dfn[x]=++idx;
    int ch=0;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(!dfn[v]){
            dfs(v);
            low[x]=min(low[x],low[v]);
            if(low[v]>=dfn[x]){
                ch++;//这一步也可以放在外面,因为根节点的low肯定最小
                if(x!=root){//如果是根且low[v]>=dfn[x]
                    ans.insert(x);
                }
            }
        }else{
            low[x]=min(low[x],dfn[v]);
        }
    }
    if(x==root&&ch>=2){//如果是根节点且子树数量大于等于2
        ans.insert(x);
    }
}
void solve(){
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            root=i;
            dfs(i);
        }
    }
    printf("%d\n",ans.size());
}
int main(){
    while(scanf("%d",&n)!=EOF&&n){
        getchar();
        init();
        input();
        solve();
    }
    return 0;
}

无向图割边(桥)

例题:uva796 求无重边图的割边

求有重边图的割边:

递归过程改为记录入边的编号,编号就是边在邻接表的下标(链式前向星)。
利用链式前向星成对变换的技巧,无向图的每一条边都可以看作是双向边,
因为下标是成对存储的,所以利用异或就可以判断是否是回边,
如果不是回边则可以更新low[x]

code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<stack>
#include<sstream>
#include<algorithm>
#define lowbit(x) (x&(-(x)))
#define coff ios::sync_with_stdio(0)
const int inf=0x3f3f3f3f;
const int inn=0x80808080;
using namespace std;
const int maxm=1e3+5;
int head[maxm],nt[maxm<<1],to[maxm<<2],cnt;
int low[maxm],dfn[maxm],idx;
int n,m;//n是点的数量,m是边的数量
vector<pair<int,int> >ans;//存放答案
void init(){
    memset(head,-1,sizeof head);
    memset(low,0,sizeof low);
    memset(dfn,0,sizeof dfn);
    cnt=1;
    idx=0;
    ans.clear();
}
void add(int x,int y){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void input(){
    for(int i=1;i<=m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
}
void dfs(int x,int pre){
    low[x]=dfn[x]=++idx;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(!dfn[v]){
            dfs(v,i);
            low[x]=min(low[x],low[v]);
            if(dfn[x]<low[v]){
                int a=to[i];
                int b=to[i^1];
//                if(a>b)swap(a,b);//可选,编号小的在左边
                ans.push_back({a,b});
            }
        }else if(i!=(pre^1)){//如果不是回边才能更新
            low[x]=min(low[x],dfn[v]);
        }
    }
}
void solve(){
    for(int i=1;i<=n;i++){//dfs
        if(!dfn[i]){
            dfs(i,-1);
        }
    }
//    sort(ans.begin(),ans.end());//编号排序,可选
    int len=ans.size();
    printf("%d critical links\n",len);//输出割边数量
    for(int i=0;i<len;i++){//输出割边的端点
        printf("%d - %d\n",ans[i].first,ans[i].second);
    }
}
int main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        init();
        input();
        solve();
    }
    return 0;
}

无向图双连通分量

待续


题集

2019南昌邀请赛Winner (tarjan)

题意:

n个人q次询问,每个人有三种能力值
n个人共进行n-1场比赛,每场都由你决定谁和谁比以及用哪种类型的能力值比,能力值大的获胜
然后每次询问给一个编号x,问由你决定比赛顺序,最后x能否获得冠军

思路:

对于每种能力值,排序一下,然后能力值大的向能力值小的连有向边
边是具有传递性,只需要建成一条链即可

三种能力值的图建在一起,跑tarjan缩点,入度为0的超级点内都是可以得到冠军的点
对于每次询问判断一下x所属超级点是否入度为0即可

code:
//https://nanti.jisuanke.com/t/40259
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e5+5;
struct Node{
    int v,id;
}e[maxm];
vector<int>g[maxm];
int n,q;
stack<int>stk;
int mark[maxm];
int dfn[maxm];
int low[maxm];
int belong[maxm];
int in[maxm];
int idx,sum;
bool cmp(Node a,Node b){
    return a.v>b.v;
}
void dfs(int x){
    dfn[x]=low[x]=++idx;
    mark[x]=1;
    stk.push(x);
    for(int v:g[x]){
        if(!dfn[v]){
            dfs(v);
            low[x]=min(low[x],low[v]);
        }else if(mark[v]){
            low[x]=min(low[x],dfn[v]);
        }
    }
    if(low[x]==dfn[x]){
        sum++;
        while(1){
            int t=stk.top();
            stk.pop();
            belong[t]=sum;
            mark[t]=0;
            if(t==x)break;
        }
    }
}
signed main(){
    scanf("%d%d",&n,&q);
    //第一种
    for(int i=1;i<=n;i++){
        scanf("%d",&e[i].v);
        e[i].id=i;
    }
    sort(e+1,e+1+n,cmp);
    for(int i=1;i<n;i++){
        g[e[i].id].push_back(e[i+1].id);
    }
    //第二种
    for(int i=1;i<=n;i++){
        scanf("%d",&e[i].v);
        e[i].id=i;
    }
    sort(e+1,e+1+n,cmp);
    for(int i=1;i<n;i++){
        g[e[i].id].push_back(e[i+1].id);
    }
    //第三种
    for(int i=1;i<=n;i++){
        scanf("%d",&e[i].v);
        e[i].id=i;
    }
    sort(e+1,e+1+n,cmp);
    for(int i=1;i<n;i++){
        g[e[i].id].push_back(e[i+1].id);
    }
    //缩点
    for(int i=1;i<=n;i++){
        if(!dfn[i])dfs(i);
    }
    //标记入度不为0的超级点
    for(int x=1;x<=n;x++){
        for(int v:g[x]){
            if(belong[x]!=belong[v]){
                in[belong[v]]=1;
            }
        }
    }
    //处理询问
    while(q--){
        int x;
        scanf("%d",&x);
        if(in[belong[x]]==0){//如果入度为0则说明可以第一
            puts("YES");
        }else{
            puts("NO");
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值