强联通分量

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;
}
posted @ 2021-01-31 21:24  Imy_bisLy  阅读(197)  评论(0编辑  收藏  举报