强联通分量
2020 10 .31
补记一下强连通分量的笔记
内容来自OI-wiki和yu__xuan 的讲课
强联通分量(个人理解):
-
在一个无向图中,u 能到v ,v 也能到u
-
在有向图中u能到v,v也能到u那么很显然 , u---v可以构成一个环
-
强联通分量就是把这个点集缩成一个点,于是乎,这个图变成了一个DAG(有向无环图) 然后你就可在这个新图上根据DAG的性质开始做题,比如 拓扑
这里只记录自己最熟悉的tarjan算法
tarjan 算法是由栈来实现的
变量声明:
dfn[u] ---->dfs序
low[u] ---->以u为根的子树,最小的dfs序
那么会出现 3 中情况:
-
v 未被访问:继续对 v 进行深度搜索。在回溯过程中,用 low[v] 更新 low[u]。因为存在从 u 到 v 的直接路径,所以 v 能够回溯到的已经在栈中的结点,u 也一定能够回溯到。
-
v 被访问过,还在栈中:即已经被访问过,根据low的定义(能够回溯到的最早的已经在栈中的结点),则用dfn[v]更新low[u]。
-
v 被访问过,已不在栈中:说明 v 所在的强连通分量已经找出,不可能和 u 在一个强连通分量中,所以不用操作。
by---yu__xuan学姐
targan:
void tarjan(int u){
st[++sn]=u,vis[u]=1;
dfn[u]=low[u]=++cnt;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(!dfn[v]){
tarjan(v),low[u]=min(low[u],low[v]);
}
else if(vis[v]==1) low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
int top=st[sn--];vis[top]=0;
scc++; siz[scc]++,num[top] = scc;
while(top!=u){
top=st[sn--];
vis[top]=0;
num[top] = scc,siz[scc]++;
}
}
}//变量注释在下一个板子里面
另一种短一点的板子
void tarjan(int u){
dfn[u] = low[u] = ++cnt;
st[++sn] = u,vis[u] = 1;
for(int i = head[u] ; i ; i = e[i].next){
int to = e[i].to;
if(!dfn[to]) low[u] = min(low[u],low[to]);
else if(vis[to]) low[u] = min(low[u], dfn[to]);
//if(vis[to]) low[u] = min(low[u], low[to]);
//这两种写法都对
//但是对于割点(顶)来说就只有第一种写法对了,可以看我割点的博客
}
if(low[u] == dfn[u]){
int top ,scc++;//scc--第几个强联通分量
do{
top = st[sn--]; vis[top] = 0;
num[top] = scc; siz[scc]++;
//top所对应的第几个强联通分量,这个强联通分量里面有几个点
//上一个板子可以跟着这个修改一下
}while(top != u);
}
}
实现code(以前写的有点丑):
模板
#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
const int M=5e4+10;
const int N=1e4+10;
int n,m,nume,head[N];
int st[M],sn,cnt,dfn[N],low[N];
bool vis[N];
struct node{
int to,next;
}e[M<<1];
void add_edge(int from,int to){
e[++nume].next = head[from];
e[nume].to=to;
head[from]=nume;
}
bool falg=0;
int ans;
void tarjan(int u){
st[++sn]=u,vis[u]=1;
dfn[u]=low[u]=++cnt;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(dfn[v]==0){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else
if(vis[v]==1)
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
int top=st[sn--];
vis[top]=0;
while(top!=u){
top=st[sn--];
vis[top]=0;
falg=1;
}
}
if(falg==1)
falg=0,ans++;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<=m;i++){
scanf("%d%d",&u,&v);
add_edge(u,v);
//add_edge(v,u);
}for(int i=1;i<=n;i++){
if(dfn[i]!=0) continue;
tarjan(i);
}
printf("%d",ans);
return 0;
}
update on 2021.1.31
修一下以前写的博客,以前写的真是惨不忍睹现在也是加两道有意思的题
间谍网络
solution:
- 可以很显然的发现如果一个罪犯不能被收买并且没有人能揭发他,那必然是无解的
- 如果有人能揭发他或者能收买他,那就简单的跑缩点,然后统计入度为零的点的收买代价(缩点时记录整个点的信息)
- 考虑无解时如何输出最小点号,可以让所有能收买或者揭发的人跑一遍缩点,从小到大枚举点号,如果这个人既不能被揭发也不能被收买,直接输出,\(return~~0\)即可
code:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#include <queue>
#define ll long long
using namespace std;
const int N = 1e4 + 100;
const int inf = 1e9;
int read() {
int s = 0, f = 0;
char ch = getchar();
while (!isdigit(ch))
f |= (ch == '-'), ch = getchar();
while (isdigit(ch))
s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
struct Edge {
int from, to, net;
} e[N * N] ;
int head[N], nume;
void add_edge(int from, int to) {
e[++nume] = (Edge){from, to, head[from]};
head[from] = nume;
}
int st[N], sn, vis[N];
int dfn[N], low[N], cnt;
int scc,rd[N], cd[N],minn[N];
int cost[N];
int num[N];
void tir(int x) {
st[++sn] = x, vis[x] = 1;
low[x] = dfn[x] = ++cnt;
for (int i = head[x]; i; i = e[i].net) {
int to = e[i].to;
if (!dfn[to])
tir(to), low[x] = min(low[x], low[to]);
else if (vis[to])
low[x] = min(low[x], dfn[to]);
}
if (dfn[x] == low[x]) {
int top = st[sn--];
vis[top] = 0;scc++;
minn[++scc] = inf;
minn[scc] = min(minn[scc],cost[top]);
num[top] = scc;
while (top != x) {
top = st[sn--];
vis[top] = 0;
minn[scc] = min(minn[scc],cost[top]);
num[top] = scc;
}
}
}
int main() {
int n = read() , p = read();
for(int i = 1 ; i<= n; i++) cost[i] = inf;
for (int i = 1; i <= p; i++) {
int x = read();
cost[x] = read();
}
int r = read();
for(int i = 1 ; i <= r ; i++) {
int u = read() , v = read();
add_edge(u ,v);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i] && cost[i] != inf)
tir(i);
}
for(int i = 1 ; i<= n ;i++) {
if(!dfn[i]) {
puts("NO");
printf("%d",i);
system("pause");
return 0;
}
}
for(int i = 1 ; i <= nume ; i++) {
if(num[e[i].from] == num[e[i].to]) continue;
cd[num[e[i].from]]++;
rd[num[e[i].to]]++;
}
int ans = 0;
for(int i = 1 ; i <= scc ;i++) {
if(rd[i] == 0) ans += minn[i];
}
puts("YES");
printf("%d",ans);
system("pause");
return 0;
}
抢夺计划
solution:
- 首先跑一遍裸的缩点,同时将每个点的点权记录到每个点中,然后用spfa跑一遍最长路,如果到达的点有标记(是酒馆),那就取一次\(\text{max}\)
code:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <string>
#define ll long long
using namespace std;
const int N = 5e5 + 100;
const int inf = 1e9;
int read() {
int s = 0, f = 0;
char ch = getchar();
while (!isdigit(ch))
f |= (ch == '-'), ch = getchar();
while (isdigit(ch))
s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
struct Edge {
int from, to, net;
} e[N], G[N];
int head[N], nume;
void add_edge(int from, int to) {
e[++nume] = (Edge){from, to, head[from]};
head[from] = nume;
}
int headg[N], numg;
void another(int from, int to) {
G[++numg] = (Edge){from, to, headg[from]};
headg[from] = numg;
}
int st[N], sn, vis[N];
int dfn[N], low[N], cnt;
int scc, rd[N], cd[N], sum[N];
int val[N], flag[N], FLAG[N];
int num[N];
int n, m, s, p;
int start;
void tir(int x) {
st[++sn] = x, vis[x] = 1;
low[x] = dfn[x] = ++cnt;
for (int i = head[x]; i; i = e[i].net) {
int to = e[i].to;
if (!dfn[to])
tir(to), low[x] = min(low[x], low[to]);
else if (vis[to])
low[x] = min(low[x], dfn[to]);
}
if (dfn[x] == low[x]) {
int top = st[sn--];
vis[top] = 0;
scc++;
if (top == s)
start = scc;
FLAG[scc] += flag[top];
sum[scc] += val[top];
num[top] = scc;
while (top != x) {
top = st[sn--];
if (top == s)
start = scc;
vis[top] = 0;
sum[scc] += val[top];
num[top] = scc;
FLAG[scc] += flag[top];
}
}
}
int ans, dis[N];
bool VIS[N];
void spfa() {
queue<int> q;
q.push(start);
VIS[start] = 1;
dis[start] = sum[start];
while (!q.empty()) {
int fr = q.front();
q.pop();
VIS[fr] = 0;
for (int i = headg[fr]; i; i = G[i].net) {
int to = G[i].to;
if (dis[to] < dis[fr] + sum[to]) {
dis[to] = dis[fr] + sum[to];
if (!VIS[to]) {
q.push(to), VIS[to] = 1;
}
}
}
}
}
int main() {
n = read(), m = read();
for (int i = 1; i <= m; i++) {
int u = read(), v = read();
add_edge(u, v);
}
for (int i = 1; i <= n; i++)
val[i] = read();
s = read(), p = read();
for (int i = 1; i <= n; i++) {
if (!dfn[i])
tir(i);
}
for (int i = 1; i <= p; i++) {
int x = read();
flag[x] = 1;
}
for (int i = 1; i <= m; i++) {
if (num[e[i].from] == num[e[i].to])
continue;
another(num[e[i].from], num[e[i].to]);
rd[num[e[i].to]]++;
}
spfa();
for (int i = 1; i <= n; i++) {
if(flag[i])
ans = max(ans, dis[num[i]]);
// cout << dis[i] << " ";
}
printf("%d", ans);
system("pause");
return 0;
}
Future never has to do with past time,but present time dose.