牛客图论--连通性

有向图强连通分量

有向图的强连通分量;

把所有环缩成一个点,使图变成有向无环图

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
//typedef __int128 LL;//2的128
typedef long long ll; //2的64
// 1e9,, 1e19,,1e38
#define int long long
const int N=50200;
int cnt,head[N],n,m,timestamp;
int dfn[N];
int low[N];
int stk[N],top,in_stk[N];//栈
int scc_cnt;//分量数
int Size[N];//强连通分量点的个数
int id[N];//强连通分量
int dout[N];//缩点后的出度
int din[N];//缩点后的入度
struct edge
{
    int to,nx;
};
edge ed[N];
void add(int a,int b)
{
    ed[++cnt].to=b;
    ed[cnt].nx=head[a];
    head[a]=cnt;
}
void tarjan(int u){
    //u的时间戳
    dfn[u]=low[u]=++timestamp;
    //把当前点加到栈中,当前点在栈中
    stk[++top]=u,in_stk[u]=true;
    //从u开始搜索
    for(int i=head[u];i!=-1;i=ed[i].nx){
        int j=ed[i].to;
        if(!dfn[j]){//如果没有遍历过
            tarjan(j);//继续搜索,遍历j
//如果j存在反向边到达比u还高的层就更新最小的序
            low[u]=min(low[u],low[j]);
        }
 //j点在栈中  说明还没出栈 是dfs序比当前点u小的
        //则其 1要么是横插边(左边分支的点)
        //         o
        //        / \
        //       j ← u    
        //     2要么是u的祖宗节点
        //         j
        //      ↗/ 
        //       u    
        //    两种情况u的dfs序都比j大 所以用dfn[j]更新low[u]
        else if(in_stk[j]){
            low[u]=min(low[u],dfn[j]);//直接用j的时间戳更新u
        }
    //栈代表当前未被搜完的强连通分量的所有点
    }
 // ?
    // 解释一下为什么tarjan完是逆dfs序
    // 假设这里是最高的根节点fa
    // 上面几行中 fa的儿子节点j都已经在它们的递归中走完了下面9行代码
    // 其中就包括 ++scc_cnt 
    // 即递归回溯到高层节点的时候 子节点的scc都求完了
    // 节点越高 scc_id越大
    // 在我们后面想求链路dp的时候又得从更高层往下
    // 所以得for(int i=scc_cnt(根节点所在的scc);i;i--)开始

    // 所以当遍历完u的所有能到的点后 发现u最高能到的点是自己
    // 1 则u为强连通分量中的最高点,则以u为起点往下把该强连通分量所有节点都找出来
    // 2 要么它就没有环,就是一个正常的往下的点
    if(dfn[u]==low[u]){
        int y=0;
        ++scc_cnt;//强连通分量总数+1
        do{
            y=stk[top--];//取栈顶元素y
            in_stk[y]=false;//则y不再在栈中
            id[y]=scc_cnt;//点y在此连通图
            Size[scc_cnt]++;//第scc_cnt个连通块点数+1
        }while(y!=u);
//1 因为栈中越高的元素的dfs序越大,那么我们只需要把dfs序比u大的这些pop到u
        //即因为最终会从下至上回到u 所以当y==u     
        //则说明点u所在的所有强连通分量都标记了id
        //           →  u
        //          /  /
        //         /  ne1
        //         ← ne2
        //      因为ne2会在u能到的dfs序里最大的,也就是此时的栈顶
        //      那么我们就逐一pop出ne2和ne1
        //2 要么它就是一个没有环的点 则该点单点成一个连通分量
    }
}
void solve()
{   cnt=0;top=0;timestamp=0;cin>>n>>m;
    for(int i=1;i<=n;i++) {head[i]=-1;dfn[i]=0;}
    while(m--){
        int a,b;cin>>a>>b;
        add(a,b);//有向图
    }
    //缩点,把所有强连通分量缩成一个点
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            tarjan(i);
        }
    }
//统计新图中点的出度 
    for(int i=1;i<=n;i++){//判断i到k是不是在一个分量中
        for(int j=head[i];j!=-1;j=ed[j].nx){
            int k=ed[j].to;
            int a=id[i],b=id[k];
//a,b不为一个连通分量
            if(a!=b) dout[a]++;
//a出度+1  dout[a] += i→k
        }
    }
    int zeros=0,sum=0;
//sum 存的所有出度为0的强连通分量的点的数量
    for(int i=1;i<=scc_cnt;i++){
        if(!dout[i]){//如果第i个强连通分量出度==0
            zeros++;
            sum+=Size[i];
//则加上第i个强连通分量的点的个数
//如果有k>1个出度为0的 则会存在k-1头牛不被所有牛欢迎
            if(zeros>1){
                sum=0;break;
            }   
        }
    }
    cout<<sum;
    return ;
}

signed main()
{
    ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
    solve();

    return 0;
}

有向图的强连通分量

算法分析:先把有向图中强连通分量缩点,使得图变成了有向无环图。为了使一个点集内所有点能到达任意的点,所以我们要把入度为0的强连通分量都记录到这个点集中。如果点集中有多个强连通分量,则输出每个强连通分量的最小点。

注意点:要记录每个强连通分量的最小点可以用pre数组来记录,在tarjan的出栈时用

pre=min(pre,u);记录这个强连通分量的最小点

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
#define int long long
const int INF=999999;
const int N=50200;
int cnt,head[N],n,m,timestamp;
int dfn[N];
int low[N];
int stk[N],top,in_stk[N];//栈
int scc_cnt;//分量数
int Size[N];//强连通分量点的个数
int id[N];//强连通分量
int dout[N];//缩点后的出度
int din[N];//缩点后的入度
int pre[N];//缩点后,每个点集的最小点
struct edge
{
    int to,nx;
};
edge ed[N];
void add(int a,int b)
{
    ed[++cnt].to=b;
    ed[cnt].nx=head[a];
    head[a]=cnt;
}
void tarjan(int u){
    dfn[u]=low[u]=++timestamp;
    stk[++top]=u,in_stk[u]=true;
    for(int i=head[u];i!=-1;i=ed[i].nx){
        int j=ed[i].to;
        if(!dfn[j]){
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }
        else if(in_stk[j]){
            low[u]=min(low[u],dfn[j]);
        }
    }
    if(dfn[u]==low[u]){
        int y=0;
        ++scc_cnt;//强连通分量总数+1
        do{
            y=stk[top--];//取栈顶元素y
            in_stk[y]=false;//则y不再在栈中
            id[y]=scc_cnt;//点y在此连通图
            Size[scc_cnt]++;//第scc_cnt个连通块点数+1
			pre[scc_cnt]=min(pre[scc_cnt],y);
        }while(y!=u);

    }
}
void solve()
{   cnt=0;top=0;timestamp=0;cin>>n>>m;
    for(int i=1;i<=n;i++) {head[i]=-1;dfn[i]=0;pre[i]=INF;}
    while(m--){
        int a,b;cin>>a>>b;
        add(a,b);//有向图
    }
    //缩点,把所有强连通分量缩成一个点
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            tarjan(i);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=head[i];j!=-1;j=ed[j].nx){
            int k=ed[j].to;
            int a=id[i],b=id[k];
            if(a!=b) din[b]++;//该点入度
		
        }
    }
	vector<int>w;
    for(int i=1;i<=scc_cnt;i++){
        if(!din[i]){//入度为0则为起点
			w.push_back(pre[i]);
        }
    }
    cout<<w.size()<<endl;
	sort(w.begin(),w.end());
	for(auto k:w){
		cout<<k<<" ";
	}
    return ;
}

signed main()
{
    ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
    solve();

    return 0;
}

[HAOI2010]软件安装

强连通分量+树形dp

a->b->c->d 如果使用D的话,那么C必须选择,那么b又必须选择,那么a又必须选择

具有传递性, 如果使用x那么x的父亲必须选择,父亲的父亲。

之后建新图

将图进行缩点后,是一个有向图,又因每一个节点最多有一个父亲,可以有多个儿子,

那么该图就是一个树,

确定源点, 将缩点后全部入度为0的源点,作为超级源点0的儿子。

最后从超级源点0开始树形dp

dp[ i ][ j ],,就是第i个节点,容量为j的时候,最大价值,

从尾节点更新


#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N=50200;
int cnt,head[N],n,m,timestamp;
int dfn[N];//时间戳
int low[N];
int stk[N],top,in_stk[N];//栈
int scc_cnt;//分量数
int Size[N];//强连通分量点的个数
int id[N];//强连通分量
int dout[N];//缩点后的出度
int din[N];//缩点后的入度
int v[N],w[N];
int g[1010][1010];//缩点后新图关系
int dp[N][600];
int vv[N],ww[N];
struct edge
{
    int to,nx;
};
edge ed[N];
void add(int a,int b)
{
    ed[++cnt].to=b;
    ed[cnt].nx=head[a];
    head[a]=cnt;
}
void tarjan(int u){
    dfn[u]=low[u]=++timestamp;
    stk[++top]=u,in_stk[u]=true;
    for(int i=head[u];i!=-1;i=ed[i].nx){
        int j=ed[i].to;
        if(!dfn[j]){
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }
        else if(in_stk[j]){
            low[u]=min(low[u],dfn[j]);
        }
    }
    if(dfn[u]==low[u]){
        int y=0;
        ++scc_cnt;//强连通分量总数+1
        do{
            y=stk[top--];//取栈顶元素y
			vv[scc_cnt]+=v[y];
			ww[scc_cnt]+=w[y];
            in_stk[y]=false;//则y不再在栈中
            id[y]=scc_cnt;//点y在此连通图
            Size[scc_cnt]++;//第scc_cnt个连通块点数+1
        }while(y!=u);
    }
}
void dfs(int u){
	for(int i=w[u];i<=m;i++)
	dp[u][i]=v[u];
   for(int i=head[u];i!=-1;i=ed[i].nx){
   	int j=ed[i].to;
   	dfs(j);
	for(int k=m;k>=w[u];k--){
		for(int l=0;l+w[u]<=k;l++){
		dp[u][k]=max(dp[u][k],dp[j][l]+dp[u][k-l]);	
	 }
	}
   }
}
void solve()
{   cnt=0;top=0;timestamp=0;
	cin>>n>>m;
    for(int i=0;i<=2*n;i++) {
	head[i]=-1;dfn[i]=0;}
   for(int i=1;i<=n;i++)
		cin>>w[i];
   for(int i=1;i<=n;i++)
		cin>>v[i];
   for(int i=1;i<=n;i++){
   	 int b;cin>>b;
	if(b==0) continue;
   	 add(b,i);	
   }
    //缩点,把所有强连通分量缩成一个点
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            tarjan(i);
        }
    }
//统计新图中点的出度 
    for(int i=1;i<=n;i++){//判断i到k是不是在一个分量中
        for(int j=head[i];j!=-1;j=ed[j].nx){
            int k=ed[j].to;
            int a=id[i],b=id[k];
//a,b不为一个连通分量
            if(a!=b) {dout[a]++;
		g[a][b]=1;
				din[b]++;}
//a出度+1  dout[a] += i→k
        }
    }
queue<int>q;
	for(int i=1;i<=scc_cnt;i++) {
	w[i+n]=ww[i];
	v[i+n]=vv[i];	
	 if(din[i]==0)//源点
	q.push(i+n);}
	for(int i=1;i<=scc_cnt;i++)
	for(int j=1;j<=scc_cnt;j++){
		if(g[i][j]==0) continue;
		add(i+n,j+n);
	}
//新图是一个树
 	while(q.size()){
 		int a=q.front();
		q.pop();
		add(0,a);//0是超级源点
 	}
	dfs(0);//进行树形dp
	cout<<dp[0][m];
    return ;
}
signed main()
{
    ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
    solve();
    return 0;
}

King's Quest

题目:

题意翻译

很久以前有一个老国王,他有N个儿子。在他的王国里面,也有N个漂亮的女孩。国王知道他的每个孩子喜欢哪一个女孩。国王的孩子们很年轻,比较花心,所以一个孩子可能喜欢好几个女孩。

国王让他的巫师为他的每个儿子找到他最喜欢的女孩,这样这个儿子就可以娶这个女孩了。国王的巫师也做到了这点要求——他为每个儿子选择了他可以结婚的女孩,即这个儿子必须喜欢选中的女孩。当然了,每个儿子只能选择一个女孩。

然而,国王说:“我喜欢你做的清单,但是我还是有点不满意。对于我的每个儿子,我想知道可以和他结婚的所有女孩。当然了,这前提必须是所有的儿子都要找到一个女孩。”

国王希望巫师为他解决的问题对于巫师来说太难了。为了保全巫师的脑袋,请你写一个程序帮助巫师。

Input

输入的第一行包含N - 国王的儿子数(11 ≤≤ N ≤≤ 20002000)。 接下来每个国王的儿子的N行包含他喜欢的女孩的名单:第一个Ki​ - 他喜欢的女孩的数量,然后有Ki​个不同的整数,从1到N表示女孩的编号。 所有Ki​的总和不超过200000200000。

输入的最后一行是巫师所做的清单 - N个不同的整数:对于每个儿子,给出他将按照此列表结婚的女孩的编号。 保证这个清单是正确的,也就是说,每个儿子都喜欢这个清单里面他所要结婚的女孩。

Output

输出N行。对于每个国王的儿子,首先打印Li​ - 他喜欢和可以结婚的不同女孩的数量。 在那之后,输出Li​个不同的整数表示那些女孩的编号,按升序排列。

强连通分量

王子喜欢的女孩,王子->女孩 ,连接一条边。

最后巫师所说的一条完备匹配图,则女孩->王子,连接一条边。

此时并非二分图,直接进行缩点,

每一个强连通分量,王子与女孩数一定相同,并且他们可以互相到达。

因此,此时强连通分量里面每一个女生都可以与任何一个男孩任意匹配。

最后遍历每一个王子后面的女孩,如果在同一个强连通分量,那么就可以任意匹配。

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N=50200;
int cnt,head[N],n,m,timestamp;
int dfn[N];//时间戳
int low[N];
int stk[N],top,in_stk[N];//栈
int scc_cnt;//分量数
int Size[N];//强连通分量点的个数
int id[N];//强连通分量
int dout[N];//缩点后的出度
int din[N];//缩点后的入度
struct edge
{
    int to,nx;
};
edge ed[N*1000];
void add(int a,int b)
{
    ed[++cnt].to=b;
    ed[cnt].nx=head[a];
    head[a]=cnt;
}
void tarjan(int u){
    dfn[u]=low[u]=++timestamp;
    stk[++top]=u,in_stk[u]=true;
    for(int i=head[u];i!=-1;i=ed[i].nx){
        int j=ed[i].to;
        if(!dfn[j]){
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }
        else if(in_stk[j]){
            low[u]=min(low[u],dfn[j]);
        }
    }
    if(dfn[u]==low[u]){
        int y=0;
        ++scc_cnt;//强连通分量总数+1
        do{
            y=stk[top--];//取栈顶元素y
            in_stk[y]=false;//则y不再在栈中
            id[y]=scc_cnt;//点y在此连通图
            Size[scc_cnt]++;//第scc_cnt个连通块点数+1
        }while(y!=u);
    }
}
void solve()
{   cnt=0;top=0;timestamp=0;
	cin>>n;
    for(int i=1;i<=2*n;i++) {head[i]=-1;dfn[i]=0;}
 	for(int i=1;i<=n;i++){
 		int k,a;
 		cin>>k;
 		while(k--){
 			cin>>a;
 			add(i,a+n);
 		}
 	}
	for(int i=1;i<=n;i++){
		int a;cin>>a;
		add(a+n,i);
	}
    //缩点,把所有强连通分量缩成一个点
    for(int i=1;i<=2*n;i++){
        if(!dfn[i]){
            tarjan(i);
        }
    }
	for(int i=1;i<=n;i++){
				vector<int>w;
		for(int j=head[i];j!=-1;j=ed[j].nx){
			int k=ed[j].to;
			int a=id[i],b=id[k];
			if(a==b){
				w.push_back(k-n);
			}
		}
		sort(w.begin(),w.end());
		cout<<w.size()<<" ";
		for(auto l : w)
		cout<<l<<" ";
		cout<<endl;
	}
    return ;
}
signed main()
{
    ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
    solve();
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值