tarjan缩点:口胡过好多题,不过从来没写过……

什么是缩点

tarjan和Kosaraju、Gabow算法一样,是为了求有向图中的强连通分量。因为有向图中大多数情况下会有环存在,而有环是一个不甚好的性质。如果把有向图里的所有强连通分量都看作是一个点(缩点),则原图就会变成一个DAG——DAG是一个好东西。

什么是tarjan缩点

tarjan算法网上大多有介绍,我也在之前看过多次,不过从未写过,这里不再介绍。

今天把核心代码重新看了一遍,终于深入理解了其算法。那么就不妨在这里直接放上代码。

tarjan代码

     void tarjan(int now)
{
dfn[now] = low[now] = ++tim;    //常规的dfn[]和low[]
stk[++cnt] = now;
for (int i=head[now]; i!=-; i=nxt[i])
{
int v = edges[i];
if (!dfn[v]){
tarjan(v);
low[now] = std::min(low[now], low[v]);
}else if (!col[v])
low[now] = std::min(low[now], dfn[v]);  //注意这里是dfn[v]
}
if (low[now]==dfn[now])  //最后的统计部分
{
col[now] = ++cols;
for (; stk[cnt]!=now; cnt--)
col[stk[cnt]] = cols;
cnt--;
}

用途

1.有向图的缩点

2.解决2-SAT

几道例题

【tarjan+DAGdp】P3387 【模板】缩点

题目背景

缩点+DP

题目描述

给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

输入输出格式

输入格式:

第一行,n,m

第二行,n个整数,依次代表点权

第三至m+2行,每行两个整数u,v,表示u->v有一条有向边

输出格式:

共一行,最大的点权之和。

说明

n<=10^4,m<=10^5,点权<=1000

算法:Tarjan缩点+DAGdp


题目分析

嘛……具体算法在题面里都给出来了。缩点+dp。

我做法是缩完点后重新建边,长是长了点,不过要是到了其他题的话可移植性会大一些。

至于dp,只需要建一个超级源,然后从超级源开始记忆化搜索就好了。

 #include<bits/stdc++.h>
const int maxn = ;
const int maxm = ; int n,m;
int f[maxn],val[maxn],col[maxn],cols;
int edgeTot,edges[maxm],nxt[maxm],head[maxn];
bool vis[maxn]; int read()
{
char ch = getchar();
int num = ;
bool fl = ;
for (; !isdigit(ch); ch = getchar())
if (ch=='-') fl = ;
for (; isdigit(ch); ch = getchar())
num = (num<<)+(num<<)+ch-;
if (fl) num = -num;
return num;
}
namespace tarjanSpace
{
int stk[maxn],cnt;
int a[maxn],dfn[maxn],low[maxn],tim;
int edgeTot,edges[maxm],nxt[maxm],head[maxn];
void tarjan(int now)
{
dfn[now] = low[now] = ++tim;
stk[++cnt] = now;
for (int i=head[now]; i!=-; i=nxt[i])
{
int v = edges[i];
if (!dfn[v]){
tarjan(v);
low[now] = std::min(low[now], low[v]);
}else if (!col[v])
low[now] = std::min(low[now], dfn[v]);
}
if (low[now]==dfn[now])
{
::col[now] = ++::cols;
for (; stk[cnt]!=now; cnt--)
::col[stk[cnt]] = ::cols;
cnt--;
}
}
inline void addedgeInner(int u, int v)
{
edges[++edgeTot] = v, nxt[edgeTot] = head[u], head[u] = edgeTot;
}
inline void addedgeOuter(int u, int v)
{
::edges[++::edgeTot] = v, ::nxt[::edgeTot] = ::head[u], ::head[u] = ::edgeTot;
}
void dealOuter()
{
for (int i=; i<=n; i++) ::val[::col[i]] += a[i];
for (int i=; i<=n; i++)
{
int u = col[i];
for (int j=head[i]; j!=-; j=nxt[j])
{
int v = col[edges[j]];
addedgeOuter(u, v);
}
}
for (int i=; i<cols; i++) addedgeOuter(, i);
}
void solve()
{
memset(head, -, sizeof head);
n = read(), m = read(), cnt = tim = edgeTot = ;
for (int i=; i<=n; i++) a[i] = read(), addedgeInner(, i);
for (int i=; i<=m; i++)
{
int u = read(), v = read();
addedgeInner(u, v);
}
tarjan();
dealOuter();
}
}
void dp(int now)
{
if (vis[now]) return;
vis[now] = ;
for (int i=head[now]; i!=-; i=nxt[i])
{
int v = edges[i];
dp(v);
f[now] = std::max(f[now], f[v]);
}
f[now] += val[now];
}
int main()
{
memset(head, -, sizeof head);
tarjanSpace::solve();
dp();
printf("%d\n",f[]);
return ;
}

【tarjan】bzoj1051: [HAOI2006]受欢迎的牛

Description

  每一头牛的愿望就是变成一头最受欢迎的牛。现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎。 这
种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也认为牛C受欢迎。你的任务是求出有多少头
牛被所有的牛认为是受欢迎的。

Input

  第一行两个数N,M。 接下来M行,每行两个数A,B,意思是A认为B是受欢迎的(给出的信息有可能重复,即有可
能出现多个A,B)

Output

  一个数,即有多少头牛被所有的牛认为是受欢迎的。

Sample Input

3 3
1 2
2 1
2 3

Sample Output

1

HINT

100%的数据N<=10000,M<=50000

题目分析

一个最基础的方法就是把每只奶牛都开一个认可数组vis[]并且赋一个标号,对于出边传递自己所有的认可,这样子到最后统计有多少牛拥有所有认可就好了。

那么为了避免死循环是要先拓扑排序一遍的,但是这张图如果存在环呢?存在环就不能只这样操作了。

当做法因为环的存在而不适用的时候,自然就考虑到了缩点。

对于缩完点的图,我们再来观察一下:

可以发现,这个DAG新图若存在强连通分量满足条件,则这个强连通分量一定是出度为零的。

这里不能够用入度是否为n-1判断,因为认可能够被传递,与入度没有直接关系。

还有非常重要的一点!如果建了超级源来联通森林,那么$maxm'=maxn+maxm$,不然要RE的!

 #include<bits/stdc++.h>
const int maxn = ;
const int maxm = ; int n,m;
int deg[maxn],stk[maxn],cnt;
int col[maxn],cols;
int dfn[maxn],low[maxn],size[maxn],tim;
int edgeTot,edges[maxm],nxt[maxm],head[maxn]; int read()
{
char ch = getchar();
int num = ;
bool fl = ;
for (; !isdigit(ch); ch = getchar())
if (ch=='-') fl = ;
for (; isdigit(ch); ch = getchar())
num = (num<<)+(num<<)+ch-;
if (fl) num = -num;
return num;
}
void addedge(int u, int v)
{
edges[++edgeTot] = v, nxt[edgeTot] = head[u], head[u] = edgeTot;
}
void tarjan(int x)
{
dfn[x] = low[x] = ++tim;
stk[++cnt] = x;
for (int i=head[x]; i!=-; i=nxt[i])
{
int v = edges[i];
if (!dfn[v])
tarjan(v), low[x] = std::min(low[x], low[v]);
else if (!col[v])
low[x] = std::min(low[x], dfn[v]);
}
if (dfn[x]==low[x])
{
col[x] = ++cols, size[cols] = ;
for (; stk[cnt]!=x; cnt--, size[cols]++)
col[stk[cnt]] = cols;
cnt--;
}
}
int main()
{
memset(head, -, sizeof head);
n = read(), m = read();
for (int i=; i<=m; i++)
{
int u = read(), v = read();
addedge(u, v);
}
for (int i=; i<=n; i++) addedge(, i);
tarjan();
for (int i=; i<=n; i++)
for (int j=head[i]; j!=-; j=nxt[j])
if (col[i]!=col[edges[j]])
deg[col[i]]++;
int tot = , gr = ;
for (int i=; i<cols; i++)
if (!deg[i])
tot = size[i], gr++;
printf("%d\n",gr>?:tot);
return ;
}

【tarjan+拓扑排序】P1262 间谍网络

题目描述

由于外国间谍的大量渗入,国家安全正处于高度的危机之中。如果A间谍手中掌握着关于B间谍的犯罪证据,则称A可以揭发B。有些间谍收受贿赂,只要给他们一定数量的美元,他们就愿意交出手中掌握的全部情报。所以,如果我们能够收买一些间谍的话,我们就可能控制间谍网中的每一分子。因为一旦我们逮捕了一个间谍,他手中掌握的情报都将归我们所有,这样就有可能逮捕新的间谍,掌握新的情报。

我们的反间谍机关提供了一份资料,色括所有已知的受贿的间谍,以及他们愿意收受的具体数额。同时我们还知道哪些间谍手中具体掌握了哪些间谍的资料。假设总共有n个间谍(n不超过3000),每个间谍分别用1到3000的整数来标识。

请根据这份资料,判断我们是否有可能控制全部的间谍,如果可以,求出我们所需要支付的最少资金。否则,输出不能被控制的一个间谍。

输入输出格式

输入格式:

第一行只有一个整数n。

第二行是整数p。表示愿意被收买的人数,1≤p≤n。

接下来的p行,每行有两个整数,第一个数是一个愿意被收买的间谍的编号,第二个数表示他将会被收买的数额。这个数额不超过20000。

紧跟着一行只有一个整数r,1≤r≤8000。然后r行,每行两个正整数,表示数对(A, B),A间谍掌握B间谍的证据。

输出格式:

如果可以控制所有间谍,第一行输出YES,并在第二行输出所需要支付的贿金最小值。否则输出NO,并在第二行输出不能控制的间谍中,编号最小的间谍编号。


题目分析

这题显然我们首先要环缩点,然后对于DAG拓扑排序,所有入度为0的点都必须要选。

要注意一点的就是:若一个连通块是可控制的,它的代价是块内所有可贿赂间谍的最小值

 #include<bits/stdc++.h>
const int maxn = ;
const int maxm = ; struct node
{
int id,val;
}a[maxn];
int n,p,r,ans;
int low[maxn],dfn[maxn],tim;
int col[maxn],cols;
int stk[maxn],deg[maxn],val[maxn],cnt;
int edgeTot,edges[maxm],nxt[maxm],head[maxn];
bool mp[maxn]; int read()
{
char ch = getchar();
int num = ;
bool fl = ;
for (; !isdigit(ch); ch = getchar())
if (ch=='-') fl = ;
for (; isdigit(ch); ch = getchar())
num = (num<<)+(num<<)+ch-;
if (fl) num = -num;
return num;
}
void addedge(int u, int v)
{
edges[++edgeTot] = v, nxt[edgeTot] = head[u], head[u] = edgeTot;
}
void tarjan(int now)
{
dfn[now] = low[now] = ++tim;
stk[++cnt] = now;
for (int i=head[now]; i!=-; i=nxt[i])
{
int v = edges[i];
if (!dfn[v])
tarjan(v),
low[now] = std::min(low[now], low[v]);
else if (!col[v])
low[now] = std::min(low[now], dfn[v]);
}
if (dfn[now]==low[now])
{
col[now] = ++cols;
for (; stk[cnt]!=now; cnt--)
col[stk[cnt]] = cols;
cnt--;
}
}
int main()
{
memset(head, -, sizeof head);
memset(val, 0x3f3f3f3f, sizeof val);
n = read(), p = read();
for (int i=; i<=p; i++)
a[i].id = read(), a[i].val = read();
r = read();
for (int i=; i<=r; i++)
{
int u = read(), v = read();
addedge(u, v);
}
for (int i=; i<=n; i++) addedge(, i);
tarjan();
for (int i=; i<=p; i++)
{
int u = col[a[i].id];
mp[u] = , val[u] = std::min(val[u], a[i].val);
}
for (int i=; i<=n; i++)
for (int j=head[i]; j!=-; j=nxt[j])
if (col[i]!=col[edges[j]])
deg[col[edges[j]]]++;
bool fl = ;
// for (int i=1; i<cols&&fl; i++)
// if (!deg[i]&&!mp[col[a[i].id]])
// fl = 0, ans = i;
// else ans += a[i].val;
// for (int i=1; i<=p; i++)
// if (!deg[col[a[i].id]])
// ans += a[i].val;
for (int i=; i<cols; i++)
if (mp[i]&&!deg[i]) ans += val[i];
for (int i=; i<=n&&fl; i++)
if (!deg[col[i]]&&!mp[col[i]])
fl = , ans = i;
printf("%s\n%d\n",fl?"YES":"NO",ans);
return ;
}

END

初涉tarjan缩点的更多相关文章

  1. hihoCoder 1185 连通性·三(Tarjan缩点+暴力DFS)

    #1185 : 连通性·三 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 暑假到了!!小Hi和小Ho为了体验生活,来到了住在大草原的约翰家.今天一大早,约翰因为有事要出 ...

  2. POJ 1236 Network of Schools(Tarjan缩点)

    Network of Schools Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 16806   Accepted: 66 ...

  3. King's Quest —— POJ1904(ZOJ2470)Tarjan缩点

    King's Quest Time Limit: 15000MS Memory Limit: 65536K Case Time Limit: 2000MS Description Once upon ...

  4. 【BZOJ-2438】杀人游戏 Tarjan + 缩点 + 概率

    2438: [中山市选2011]杀人游戏 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1638  Solved: 433[Submit][Statu ...

  5. 【BZOJ-1924】所驼门王的宝藏 Tarjan缩点(+拓扑排序) + 拓扑图DP

    1924: [Sdoi2010]所驼门王的宝藏 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 787  Solved: 318[Submit][Stat ...

  6. 【BZOJ-1797】Mincut 最小割 最大流 + Tarjan + 缩点

    1797: [Ahoi2009]Mincut 最小割 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1685  Solved: 724[Submit] ...

  7. BZOJ 1051 受欢迎的牛(Tarjan缩点)

    1051: [HAOI2006]受欢迎的牛 Time Limit: 10 Sec  Memory Limit: 162 MB Submit: 4573  Solved: 2428 [Submit][S ...

  8. HDU4612+Tarjan缩点+BFS求树的直径

    tarjan+缩点+树的直径题意:给出n个点和m条边的图,存在重边,问加一条边以后,剩下的桥的数量最少为多少.先tarjan缩点,再在这棵树上求直径.加的边即是连接这条直径的两端. /* tarjan ...

  9. POJ 1236 Network of Schools(强连通 Tarjan+缩点)

    POJ 1236 Network of Schools(强连通 Tarjan+缩点) ACM 题目地址:POJ 1236 题意:  给定一张有向图,问最少选择几个点能遍历全图,以及最少加入�几条边使得 ...

随机推荐

  1. VRTK3.3.0-004传送

    直线传送: 一.无高度变换传送(VRTK_BasicTeleport) 1丶继续在VRScripts下创建空物体PlayArea,用来挂在传送相关脚本:创建Plane作为传送地面 2丶在PlayAre ...

  2. ADO winform注册

    前面我们了解了如何实现登录,现在.我们来讨论如何实现注册功能,注册其实就是 通过程序,往数据库中新增数据.首先我们同样连接到数据库,同上页一样创建连接,然后 打开连接.之后我们就要去获取我们输入到文本 ...

  3. 网络请求方法(SDK封装可以替换afn)

    //个人觉得 sdk开发时候 最好不要用第三方 最好可以用最原始的方法 替换 此处仅做sdk封装使用 留存+(void)GET:(NSString *)urlStr params:(NSDiction ...

  4. tensorflow:实战Google深度学习框架第四章02神经网络优化(学习率,避免过拟合,滑动平均模型)

    1.学习率的设置既不能太小,又不能太大,解决方法:使用指数衰减法 例如: 假设我们要最小化函数 y=x2y=x2, 选择初始点 x0=5x0=5  1. 学习率为1的时候,x在5和-5之间震荡. im ...

  5. 牛客练习赛42D(性质、数学)

    题目传送 就像题解所说的,写几个可以发现有分成四段的性质:第一段是从n开始往下贪,第二段是个数字,第三段……卧槽好吧真难描述. 然后发现这个数据量可达1e9,所以考虑“二分确定序列+数学计算”的方式解 ...

  6. 栈 && 教授的测试

    卡特兰数:https://blog.csdn.net/wu_tongtong/article/details/78161211 https://www.luogu.org/problemnew/sho ...

  7. oratop

    1.下载: 目前,Oratop是在MOS上免费下载.每个db 版本和 os 版本都有对应的程序:The tool is a compiled c program. 不需要编译,直接运行.   (下载文 ...

  8. C++ 自定义结构体的Priority Queue

    比较函数return true 意味着排序需要交换. #include <iostream> #include <queue> #include <vector> ...

  9. vue echarts 大小自适应

    窗口大小时候 ,echarts图自适应 在创建图表的方法中直接,用resize方法 let myChart=this.$refs.myChart; let mainChart = echarts.in ...

  10. var声明提前 undefined

    1.同一代码块内,所有var声明都提前: 2.var 变量的初始化不提前,按顺序执行: 3."undefined"和undefined都存在于window中: 4.if(" ...