连通图专题

有向图连通性

P2835 刻录光盘
思路:
读题很容易就可以发现答案就是缩点后入度为 0 0 0 的强连通分量个数

P2002 消息扩散
思路:
和上题一样,答案即为入度为 0 0 0 的强连通分量个数

Network of Schools
题意:
给你一张有向图,问最少需要几个起点,才能遍历完所有点以及要至少加几条边,才能从使得整个图强连通
思路:
先缩点求出所有的强连通分量,如果图本身强连通,那么选一个起点就好,并且不用加边
否则就需要选定所有入度为 0 0 0 的强连通分量作为起点
可以发现入度为 0 0 0 和出度为 0 0 0 的强连通分量两两匹配,取 m a x max max 即为至少加几条边
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to;
}e[maxn];
int head[maxn], cnt;
int dfn[maxn], low[maxn], dcnt;
int stk[maxn], top;
bool vis[maxn];
int scc_id[maxn];
int id;

void add(int x, int y){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void tarjan(int now){
	dfn[now] = low[now] = ++dcnt;
	stk[++top] = now;
	vis[now] = 1;
	for(int i = head[now]; i; i = e[i].next){
		int to = e[i].to;
		if(!dfn[to]){
			tarjan(to);
			low[now] = min(low[now], low[to]);
		}
		else if(vis[to])
			low[now] = min(low[now], dfn[to]);
	}
	if(dfn[now] == low[now]){
		++id;
		int t;
		do{
			t = stk[top--];vis[t] = 0;scc_id[t] = id;
		}while(t != now);
	}
}
int in[maxn], out[maxn];

void work()
{
	cin >> n;
	for(int i = 1; i <= n; ++i){
		int x;while(cin >> x && x){
			add(i, x);
		}
	}
	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; j = e[j].next){
			int to = e[j].to;
			if(scc_id[i] != scc_id[to]){
				in[scc_id[to]]++;
				out[scc_id[i]]++;
			}
		}
	}
	int x = 0, y = 0;
	for(int i = 1; i <= id; ++i){
		if(!in[i]) ++x;
		if(!out[i]) ++y;
	}
	if(id == 1) cout << 1 << endl << 0 << endl;
	else cout << x << endl << max(x, y) << endl;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

银河
题意:

思路:
算法竞赛进阶指南里的讲解:
d [ x ] d[x] d[x] 表示 x x x 的亮度 ( 1 < = x < = n ) (1<=x<=n) (1<=x<=n)

  1. d [ x ] = d [ y ] ⟹ d [ x ] − d [ y ] > = 0 d[x]=d[y] \Longrightarrow d[x]-d[y]>=0 d[x]=d[y]d[x]d[y]>=0 并且 d [ y ] − d [ x ] > = 0 d[y]-d[x]>=0 d[y]d[x]>=0
  2. d [ x ] < d [ y ] ⟹ d [ y ] − d [ x ] > = 1 d[x]<d[y] \Longrightarrow d[y]-d[x]>=1 d[x]<d[y]d[y]d[x]>=1
  3. d [ x ] > = d [ y ] ⟹ d [ x ] − d [ y ] > = 0 d[x]>=d[y] \Longrightarrow d[x]-d[y]>=0 d[x]>=d[y]d[x]d[y]>=0
  4. d [ x ] > d [ y ] ⟹ d [ x ] − d [ y ] > = 1 d[x]>d[y] \Longrightarrow d[x]-d[y]>=1 d[x]>d[y]d[x]d[y]>=1
  5. d [ x ] < = d [ y ] ⟹ d [ y ] − d [ x ] > = 0 d[x]<=d[y] \Longrightarrow d[y]-d[x]>=0 d[x]<=d[y]d[y]d[x]>=0

另外,题目指出恒星的亮度最暗是 1 1 1,可以设 d [ 0 ] = 0 d[0]=0 d[0]=0,然后转化为 ∀ x , d [ x ] − d [ 0 ] > = 1 \forall x,d[x]-d[0]>=1 x,d[x]d[0]>=1
对于形如 d [ y ] − d [ x ] > = 1 d[y]-d[x]>=1 d[y]d[x]>=1 的不等式,根据差分约束的知识,从 x x x 节点向 y y y 节点建权值为 1 1 1 的边,然后从 0 0 0 号节点开始跑最长路,如果有正环则无解,否则 0 0 0 号节点到每个节点的最长路 d i s [ i ] dis[i] dis[i] 即为不等式组最小正整数解,对应题目问题最小总亮度和
但是本题直接用 s p f a spfa spfa 会超时
因为本题建图的边权只有 0 0 0 1 1 1,如果图中存在一个环,那么环上的长度必须都是 0 0 0(即不存在正环),因此我们可以求出图中的所有强连通分量,只要强连通分量内部存在长度为 1 1 1 的边则无解。
如果有解,每个强连通分量内部的各个恒星亮度都是相同的,就可以缩点,得到边权只有 0 0 0 1 1 1 的有向无环图。从节点 0 0 0 所在的 S C C SCC SCC 出发,拓扑排序求到每个 S C C SCC SCC 的最长路,即为该 S C C SCC SCC 内所有恒星的最小亮度
注意边的范围开大点, 4 e 5 4e5 4e5 差不多,不然会 w a wa wa
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 4e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn];
int head[2][maxn], cnt;// 0表示原图,1表示缩点后的图 
int dis[maxn];// 最长路 
int dfn[maxn], low[maxn], dcnt;
int scc_id[maxn], scc_c[maxn], id, stk[maxn], top, vis[maxn];
int in[maxn];

void add(int op, int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[op][x];
	head[op][x] = cnt;
}
void tarjan(int now){
	dfn[now] = low[now] = ++dcnt;
	stk[++top] = now;
	vis[now] = 1;
	for(int i = head[0][now]; i; i = e[i].next){
		int to = e[i].to;
		if(!dfn[to]){
			tarjan(to); low[now] = min(low[now], low[to]);
		}
		else if(vis[to]) low[now] = min(low[now], dfn[to]);
	}
	if(dfn[now] == low[now]){
		++id;int t;
		do{
			t = stk[top--];vis[t] = 0;scc_id[t] = id;scc_c[id]++;
		}while(t != now);
	}
}
void topsort(){
	queue <int> q;
	for(int i = 1; i <= id; ++i) if(!in[i]) q.push(i);
	while(!q.empty()){
		int x = q.front();q.pop();
		for(int i = head[1][x]; i; i = e[i].next){
			int to = e[i].to;
			--in[to];
			dis[to] = max(dis[to], dis[x] + e[i].w);
			if(!in[to]) q.push(to);
		}
	}
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) add(0, 0, i, 1);//建超级源点
	for(int i = 1; i <= m; ++i){
		int x, y, op;cin >> op >> x >> y;
		if(op == 1) add(0, x, y, 0), add(0, y, x, 0);
		else if(op == 2) add(0, x, y, 1);
		else if(op == 3) add(0, y, x, 0);
		else if(op == 4) add(0, y, x, 1);
		else add(0, x, y, 0);
	}
	for(int i = 0; i <= n; ++i) if(!dfn[i]) tarjan(i);
	bool f = 0;
	for(int i = 0; i <= n; ++i)
	{
		for(int j = head[0][i]; j; j = e[j].next){
			int to = e[j].to;
			if(scc_id[i] == scc_id[to]) {
				if(e[j].w > 0){
					f = 1;break;
				}
			}
			else add(1, scc_id[i], scc_id[to], e[j].w), in[scc_id[to]]++;
		}
		if(f) break;
	}
	if(f){
		cout << -1 << endl;
	}
	else{
		topsort();
		ll ans = 0;
		for(int i = 1; i <= id; ++i) ans += (1ll * dis[i] * scc_c[i]);
		cout << ans;
	}	
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

P3916 图的遍历
思路:
非常妙的一道题
思路一:
题目要求我们求从 v v v 出发,能走到编号最大的点是多少
与其考虑能走到的编号最大点的多少,不如反过来想,考虑较大的点可以反向到达哪些点
反向建图,从编号大的开始 d f s dfs dfs ,显然这么贪心的遍历,所有已经遍历过的点一定是更大的编号能到达的,因此没必要去继续遍历
思路二:
所有强连通分量之间是能够相互达到的,所以他们的答案都是一样的
缩点后再建反图拓扑排序就好了,建反图才是核心啊 ! ! ! !!!
思路二code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to;
}e[maxn];
int head[2][maxn], cnt;
int dfn[maxn], low[maxn], dcnt, stk[maxn], top, vis[maxn];
int scc_id[maxn], id, scc_max[maxn];
int ans[maxn], in[maxn];

void add(int op, int x, int y){
	e[++cnt].to = y;
	e[cnt].next = head[op][x];
	head[op][x] = cnt;
}
void tarjan(int x){
	dfn[x] = low[x] = ++dcnt;
	stk[++top] = x;
	vis[x] = 1;
	for(int i = head[0][x]; i; i = e[i].next){
		int to = e[i].to;
		if(!dfn[to]){
			tarjan(to);low[x] = min(low[to], low[x]);
		}
		else if(vis[to]) low[x] = min(low[x], dfn[to]);
	}
	if(dfn[x] == low[x]){
		++id;int t;
		do{
			t = stk[top--];vis[t] = 0;scc_id[t] = id;scc_max[id] = max(scc_max[id], t);
		}while(t != x);
	}
}
void topsort(){
	queue <int> q;
	for(int i = 1; i <= n; ++i) if(!in[i]) {
		q.push(i);ans[i] = scc_max[i];
	}
	while(!q.empty()){
		int x = q.front();q.pop();
		for(int i = head[1][x]; i; i = e[i].next){
			int to = e[i].to;
			in[to]--;
			if(!in[to]) q.push(to);
			ans[to] = max({ans[to], scc_max[to], ans[x]});
		}
	}
}
void work()
{
	cin >> n >> m;
	for(int i = 1, x, y; i <= m; ++i){
		cin >> x >> y;add(0, x, y);
	}
	for(int i = 1; i <= n; ++i) if(!dfn[i]) tarjan(i);
	for(int i = 1; i <= n; ++i)
		for(int j = head[0][i]; j; j = e[j].next){
			int to = e[j].to;
			if(scc_id[i] != scc_id[to]){
				add(1, scc_id[to], scc_id[i]);
				in[scc_id[i]]++;
			}
		}
	topsort();
	for(int i = 1; i <= n; ++i) cout << ans[scc_id[i]] << " ";
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

P2194 HXY烧情侣
题意:
给定一个有向图, n n n 个点 m m m 条边,每个点有一个权值 v a l i val_i vali
点亮魔法是指从该点出发最后回到该点,通过路径上的所有节点都会被点亮,花费为该节点的权值
每个节点只需要点亮一次
求点亮所有节点的最小花费以及方案数,方案数对 1 0 9 + 7 10^9+7 109+7 取模
思路:
显然一个强连通分量中选出一个点,即可把整个强连通分量中的点点亮
因此花费即为所有强连通分量中权值最小的节点之和 ∑ i = 1 i d m i n ( v a l 1 , v a l 2 , . . . v a l k ) \sum_{i=1}^{id} min(val_1,val_2,...val_k) i=1idmin(val1,val2,...valk)
方案数即为所有强连通分量权值最小节点个数之积
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to;
}e[maxn];
int head[2][maxn], cnt;
int dfn[maxn], low[maxn], dcnt, stk[maxn], top, vis[maxn];
int scc_id[maxn], id, scc_min[maxn], scc_minnum[maxn];
int val[maxn];

void add(int op, int x, int y){
	e[++cnt].to = y;
	e[cnt].next = head[op][x];
	head[op][x] = cnt;
}
void tarjan(int x){
	dfn[x] = low[x] = ++dcnt;
	stk[++top] = x;
	vis[x] = 1;
	for(int i = head[0][x]; i; i = e[i].next){
		int to = e[i].to;
		if(!dfn[to]){
			tarjan(to);low[x] = min(low[to], low[x]);
		}
		else if(vis[to]) low[x] = min(low[x], dfn[to]);
	}
	if(dfn[x] == low[x]){
		++id;int t;
		scc_min[id] = inf;
		do{
			t = stk[top--];vis[t] = 0;scc_id[t] = id;
			if(val[t] < scc_min[id]){
				scc_min[id] = val[t];
				scc_minnum[id] = 1;
			}
			else if(val[t] == scc_min[id]){
				scc_min[id] = val[t];
				scc_minnum[id]++;
			}
		}while(t != x);
	}
}

void work()
{
	cin >> n;
	for(int i = 1; i <= n; ++i) cin >> val[i];
	cin >> m;
	for(int i = 1, x, y; i <= m; ++i){
		cin >> x >> y;add(0, x, y);
	}
	for(int i = 1; i <= n; ++i) if(!dfn[i]) tarjan(i);
	ll sum = 0, ans = 1;
	for(int i = 1; i <= id; ++i) sum += scc_min[i], ans = ans * scc_minnum[i] % mod;
	cout << sum << " " << ans;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值