有向图连通性
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)
- 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
- 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
- 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 [ 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
- 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;
}