tarjan算法 习题
dfs树与tarjan算法
标签(空格分隔): 517coding problem solution dfs树 tarjan
Task 1
给出一幅无向图\(G\),在其中给出一个dfs树\(T\),选出\(G\)中的一个边集\(E\),使得在所有T-Simple-Circle(即最多有1条边不在\(T\)中的路径)包含至少一个\(E\)中的元素,最小化\(E\)中元素个数。
对于100%的数据 \(1 \leq n \leq 2 \times 10^3, 1 \leq m \leq 2\times 10^4\)
考虑到选取边一定是在\(T\)中,所以对于一个简单环构成的路径,连接着当前节点和它的祖先节点这一条树上的路径,这条路径上必须选择一条边被选择。
将边下放到其对应子节点,
考虑到一个贪心,只需要将这些链按照深度较浅的节点深度递减排序,每一次如果当前链没有被点覆盖,那么就将深度较浅的点覆盖,这样线段本身被覆盖然后对后续的线段有不低于放在深度较浅的点之下的点的效果。
虽然可以树链剖分达到\(O(n{ log_2 }^2 n)\)的复杂度,但没有必要。
最终复杂度是\(O(n^2)\)
# include<bits/stdc++.h>
using namespace std;
const int N=2e3+10,M=2e4+10;
struct rec{ int pre,to; }a[M<<1];
struct node{ int u,v; }rec[N];
int n,m,tot=1,g;
int head[N],dep[N],pre[N];
bool mark[N],inTree[M<<1];
vector<node>V;
void clear()
{
tot=1;
memset(a,0,sizeof(a)); memset(rec,0,sizeof(rec));
memset(dep,0,sizeof(dep)); memset(head,0,sizeof(head));
memset(pre,0,sizeof(pre)); memset(mark,false,sizeof(mark));
memset(inTree,false,sizeof(inTree)); V.clear();
}
void adde(int u,int v,bool flag)
{
a[++tot].pre=head[u];
a[tot].to=v;
head[u]=tot;
if (flag) inTree[tot]=1;
}
void dfs(int u,int fa)
{
dep[u]=dep[fa]+1;
for (int i=head[u];i;i=a[i].pre){
int v=a[i].to; if (v==fa) continue;
pre[v]=u; dfs(v,u);
}
}
bool cmp(node x,node y){return dep[x.v]>dep[y.v];}
bool check(int s,int t)
{
while (pre[s]!=t) {
if (mark[s]) return true;
s=pre[s];
}
if (mark[s]) return true;
g=s;
return false;
}
int main()
{
//freopen("data.in","r",stdin);
while (true) {
scanf("%d%d",&n,&m); if (!n&&!m) break;
for (int i=1;i<n;i++) {
int u,v; scanf("%d%d",&u,&v);
adde(u,v,1); adde(v,u,1);
rec[i]=(node){u,v};
}
dfs(1,0);
for (int i=n;i<=m;i++) {
int u,v; scanf("%d%d",&u,&v);
adde(u,v,0); adde(v,u,0);
}
for (int i=1;i<n;i++) {
int u=rec[i].u;
for (int w=head[u];w;w=a[w].pre){
int v=a[w].to; if (dep[v]>dep[u]||inTree[w]) continue;
V.push_back((node){u,v});
}
u=rec[i].v;
for (int w=head[u];w;w=a[w].pre){
int v=a[w].to; if (dep[v]>dep[u]||inTree[w]) continue;
V.push_back((node){u,v});
}
}
sort(V.begin(),V.end(),cmp);
int ans=0;
for (int i=0;i<V.size();i++) {
int s=V[i].u,t=V[i].v;
if (check(s,t)) continue;
else mark[g]=1,ans++;
}
printf("%d\n",ans);
clear();
}
return 0;
}
Task 2
在无向图\(G\)中取任意一个生成树,求出这些生成树必然会经过哪些边。
对于100%的数据$2 \leq N \leq 10^4, 1\leq M \leq 10^5 $
如果一条边是割边,那么它必然会出现在生成树中。
本题转化为求出所有的割边。
使用tarjan算法,其中dfn表示无向图的一棵dfs树中节点的dfs序编号,low表示节点经过一条不在dfs树上的边能连接到的节点中dfs序最小值。
显然,割边的判断条件是对于边\((u,v)是割边\),当且仅当$low_{v} > dfn_{u} $
如有重边,仍然可以处理,显然只需要记一下当前进入该节点的边编号(可以用成对变换)如果当前回到父亲节点的这条边不是来的那条边,就说明这两个节点直接含有重边了,一旦含有重边,那么子节点的low值就可以用父亲的dfs序值更新了。
这道题就做完了,\(O(n)\)
# include<bits/stdc++.h>
using namespace std;
const int N=1e4+10,M=1e5+10;
struct rec{ int pre,to; }a[M<<1];
int dfn[N],low[N],n,m,head[N],tot=1;
bool bridge[M<<1];
void clear()
{
tot=1;
memset(a,0,sizeof(a)); memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low)); memset(head,0,sizeof(head));
memset(bridge,false,sizeof(bridge));
}
void adde(int u,int v)
{
a[++tot].pre=head[u];
a[tot].to=v;
head[u]=tot;
}
void tarjan(int u,int id)
{
low[u]=dfn[u]=++dfn[0];
for (int i=head[u];i;i=a[i].pre) {
int v=a[i].to;
if (!dfn[v]) {
tarjan(v,i); low[u]=min(low[u],low[v]);
if (low[v]>dfn[u]) bridge[i]=bridge[i^1]=1;
} else if (i!=(id^1)) low[u]=min(low[u],dfn[v]);
}
}
int main()
{
int T; scanf("%d",&T);
while (T--) {
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) {
int u,v; scanf("%d%d",&u,&v);
adde(u,v); adde(v,u);
}
tarjan(1,0); int cnt=0;
for (int i=2;i<=tot;i+=2) if (bridge[i]) cnt++;
printf("%d\n",cnt);
for (int i=2;i<=tot;i+=2)
if (bridge[i]) printf("%d%c",i>>1,(--cnt)==0?'\n':' ');
if (T!=0) puts("");
clear();
}
return 0;
}
Task 3
无向图找割点
我也不知道它的输入为什么这么复杂(是为了掩饰这道题水的本质吗?)
# include <iostream>
# include <cstdio>
# include <cstring>
# define min(a,b) ((a)<(b)?(a):(b))
# define max(a,b) ((a)<(b)?(b):(a))
using namespace std;
const int N=105,M=N*N;
struct rec{ int pre,to; }a[M<<1];
bool cur[N];
int dfn[N],low[N],head[N],root;
int n,m,tot;
void clear()
{
memset(a,0,sizeof(a)); memset(cur,false,sizeof(cur));
memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low));
memset(head,0,sizeof(head)); tot=0;
}
void adde(int u,int v)
{
a[++tot].pre=head[u];
a[tot].to=v;
head[u]=tot;
}
void tarjan(int u)
{
dfn[u]=low[u]=++dfn[0];
int flag=0;
for (int i=head[u];i;i=a[i].pre) {
int v=a[i].to;
if (!dfn[v]){
tarjan(v); low[u]=min(low[u],low[v]);
if (low[v]>=dfn[u]) {
flag++;
if (u!=root || flag>1) cur[u]=1;
}
} else low[u]=min(low[u],dfn[v]);
}
}
int main()
{
while (true) {
scanf("%d",&n); if (!n) break;
while (true) {
int u; scanf("%d",&u); if (!u) break;
while (getchar()!='\n') {
int v; scanf("%d",&v);
adde(u,v); adde(v,u);
}
}
for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(root=i);
int ans=0;
for (int i=1;i<=n;i++) if (cur[i]) ans++;
printf("%d\n",ans);
clear();
}
return 0;
}
Task 4
有向图\(G\)中各节点都有一个权值,从\(s\)点出发到\(P\)个合法终点中的一个被认为是一条合法路径。选取一条路径,可以重复经过一些点(但是其权值只可以获得一次),最大化经过路径权值和。
对于100%的数据$1 \leq P\leq n,m\leq 5 \times 10^5 $
考虑到如果有环的话,那么最优的走法就是绕一圈环获取所有权值。
于是我们采用了\(tarjan\)算法来缩点,然后问题就转化为在\(DAG\)`上从\(s\)点出发到任意点的最长路了。(环中节点答案是一样的)
可以使用dp做法,将\(DAG\)进行拓扑排序,按照拓扑序做动态规划。
如果某个缩点的拓扑序在\(s\)所在的拓扑序之前,那么\(s\)是不可能走到那里的。
设\(f_u\)表示从\(s\)点到\(u\)点的最长路,转移就是\(f_u=max\{f_{from} + \sum w_u\}\)
答案就是包含合法终点的缩点的最大值。
还要注意一些问题,比如说dp初值除了出发点,其他必须全部设为-inf
由于dp的时候是取max的,所以重边应该没什么问题。
# include <iostream>
# include <cstdio>
# include <cstring>
# include <vector>
# include <stack>
# include <queue>
# include <algorithm>
# define int long long
using namespace std;
const int N=5e5+10;
struct rec{ int pre,to; }a[N<<1];
stack<int>s; queue<int>q;
bool ok[N],ins[N];
int dfn[N],low[N],c[N],val[N],w[N],head[N],seq[N],in[N],f[N];
vector<int>E[N],F[N];
vector<int>scc[N];
vector<pair<int,int> >Edge;
int n,m,tot,cnt;
void adde(int u,int v)
{
a[++tot].pre=head[u];
a[tot].to=v;
head[u]=tot;
}
void tarjan(int u)
{
dfn[u]=low[u]=++dfn[0];s.push(u);ins[u]=1;
for (int i=head[u];i;i=a[i].pre){
int v=a[i].to;
if (!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if (ins[v]) low[u]=min(low[u],dfn[v]);
}
if (dfn[u]==low[u]) {
int v; ++cnt;
do {
v=s.top(); s.pop(); ins[v]=0;
c[v]=cnt; scc[cnt].push_back(v);
val[cnt]+=w[v];
} while (v!=u);
}
}
void toposort(int s)
{
if (!in[s]) q.push(s);
for (int i=1;i<=cnt;i++) if (!in[i] && i!=s) q.push(i);
while (!q.empty()) {
int u=q.front();q.pop();
seq[++seq[0]]=u;
for (int i=0;i<E[u].size();i++) {
int v=E[u][i];
in[v]--; if (!in[v]) q.push(v);
}
}
}
signed main()
{
scanf("%lld%lld",&n,&m);
for (int i=1;i<=m;i++) {
int u,v; scanf("%lld%lld",&u,&v);
adde(u,v);
}
for (int i=1;i<=n;i++) scanf("%lld",&w[i]);
int s,p; scanf("%lld%lld",&s,&p);
for (int i=1;i<=p;i++) { int t; scanf("%lld",&t); ok[t]=1;}
for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i);
for (int u=1;u<=n;u++)
for (int i=head[u];i;i=a[i].pre) {
int v=a[i].to; if (c[u]==c[v]) continue;
Edge.push_back(make_pair(c[u],c[v]));
}
sort(Edge.begin(),Edge.end());
Edge.erase((unique(Edge.begin(),Edge.end())),Edge.end());
for (int i=0;i<Edge.size();i++) {
int u=Edge[i].first,v=Edge[i].second;
E[u].push_back(v); in[v]++;
F[v].push_back(u);
}
toposort(s);
int pt; for (int i=1;i<=cnt;i++) if (c[s]==seq[i]) { pt=i; break;}
memset(f,-0x3f,sizeof(f)); f[c[s]]=val[c[s]];
for (int w=pt+1;w<=cnt;w++) {
int u=seq[w];
for (int i=0;i<F[u].size();i++) {
int v=F[u][i];
f[u]=max(f[u],f[v]);
}
f[u]+=val[u];
}
int ans=0;
for (int i=1;i<=n;i++) if (ok[i]) ans=max(ans,f[c[i]]);
printf("%lld\n",ans);
return 0;
}
Task 5
有向图\(G\)中至少需加多少条有向边才能使每个节点都能从节点\(s\)出发被访问到。
对于100%的数据 \(1\leq s \leq n,m \leq 5000\)
缩点以后,图变成了\(DAG\)上的问题。
做一遍$ dfs \(找出所有不能和\)s\(所在的点联通的块,对于每一个连通块至少需要一条边才能和\)s$所在的点连通。
考虑到每一个入度为0的连通块其必然会加一条边使得其连接到\(s\)所在的连通块或者其他连通块。
所以答案是 不在\(s\)缩点里的且入度为0的连通块个数。
复杂度\(O(n)\)
# include <bits/stdc++.h>
using namespace std;
const int N=5005;
struct rec{ int pre,to; }a[N<<1];
stack<int>s;
int low[N],dfn[N],head[N],c[N],in[N];
bool ins[N],vis[N];
int n,m,tot,cnt;
vector<int>E[N];
void adde(int u,int v)
{
a[++tot].pre=head[u];
a[tot].to=v;
head[u]=tot;
}
void tarjan(int u)
{
dfn[u]=low[u]=++dfn[0]; s.push(u); ins[u]=1;
for (int i=head[u];i;i=a[i].pre){
int v=a[i].to;
if (!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if (ins[v]) low[u]=min(low[u],dfn[v]);
}
if (low[u]==dfn[u]) {
int v; ++cnt;
do {
v=s.top();s.pop();ins[v]=0;
c[v]=cnt;
} while (v!=u);
}
}
void dfs(int u)
{
vis[u]=1;
for (int i=0;i<E[u].size();i++) {
int v=E[u][i]; if (vis[v]) continue;
dfs(v);
}
}
int main()
{
int s; scanf("%d%d%d",&n,&m,&s);
for (int i=1;i<=m;i++) {
int u,v; scanf("%d%d",&u,&v);
adde(u,v);
}
for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i);
for (int u=1;u<=n;u++)
for (int i=head[u];i;i=a[i].pre) {
int v=a[i].to; if (c[u]==c[v]) continue;
E[c[u]].push_back(c[v]); in[c[v]]++;
}
dfs(c[s]); int ans=0;
for (int i=1;i<=cnt;i++) if (!vis[i] && !in[i] ) ans++;
printf("%d\n",ans);
return 0;
}
tarjan算法 习题的更多相关文章
- 近期公共祖先(LCA)——离线Tarjan算法+并查集优化
一. 离线Tarjan算法 LCA问题(lowest common ancestors):在一个有根树T中.两个节点和 e&sig=3136f1d5fcf75709d9ac882bd8cfe0 ...
- 有向图强连通分量的Tarjan算法
有向图强连通分量的Tarjan算法 [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G ...
- 点/边 双连通分量---Tarjan算法
运用Tarjan算法,求解图的点/边双连通分量. 1.点双连通分量[块] 割点可以存在多个块中,每个块包含当前节点u,分量以边的形式输出比较有意义. typedef struct{ //栈结点结构 保 ...
- 割点和桥---Tarjan算法
使用Tarjan算法求解图的割点和桥. 1.割点 主要的算法结构就是DFS,一个点是割点,当且仅当以下两种情况: (1)该节点是根节点,且有两棵以上的子树; (2)该节 ...
- Tarjan算法---强联通分量
1.基础知识 在有向图G,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子 ...
- (转载)LCA问题的Tarjan算法
转载自:Click Here LCA问题(Lowest Common Ancestors,最近公共祖先问题),是指给定一棵有根树T,给出若干个查询LCA(u, v)(通常查询数量较大),每次求树T中两 ...
- 强连通分量的Tarjan算法
资料参考 Tarjan算法寻找有向图的强连通分量 基于强联通的tarjan算法详解 有向图强连通分量的Tarjan算法 处理SCC(强连通分量问题)的Tarjan算法 强连通分量的三种算法分析 Tar ...
- [知识点]Tarjan算法
// 此博文为迁移而来,写于2015年4月14日,不代表本人现在的观点与看法.原始地址:http://blog.sina.com.cn/s/blog_6022c4720102vxnx.html UPD ...
- Tarjan 算法&模板
Tarjan 算法 一.算法简介 Tarjan 算法一种由Robert Tarjan提出的求解有向图强连通分量的算法,它能做到线性时间的复杂度. 我们定义: 如果两个顶点可以相互通达,则称两个顶点强连 ...
随机推荐
- sql server第三方产品
sql server第三方产商工具 双活: 1. Moebius for SQL Server :http://www.grqsh.com/Subpage/product_MoebiusDA.html ...
- Dubbo使用Sentinel来对服务进行降级与限流
一.Sentinel 是什么 Sentinel 是阿里中间件团队开源的,面向分布式服务架构的轻量级流量控制产品,主要以流量为切入点,从流量控制.熔断降级.系统负载保护等多个维度来帮助用户保护服务的稳定 ...
- sql server不同排序规则的数据库间字段的比较
不同的排序规则的字段是不能直接比较的.会提示:无法解决 equal to 操作的排序规则冲突.可以把字段强制转换一个排序规则,这样就能比较了.示例: ------------------------- ...
- python-day40(正式学习)
目录 线程队列 1 2 3 线程定时器 进程池和线程池 线程队列 1 import queue q=queue.Queue() q.put('123') q.put('456') q.put('789 ...
- CALL apoc.cypher.doIt创建动态节点的时候怎么指定多个标签?
下面的创建节点实例,请教一下CALL apoc.cypher.doIt如何创建多个标签?现在的方式是只能指定一个标签! UNWIND [{name:"sdasdsad234fdgsasdfa ...
- Tomcat的安全性
Web应用程序的一些内容是受限的,只有授权的用户在提供了正确的用户名和密码后才能查看他们,servlet技术支持通过配置部署 描述器(web.xml)来对这些内容进行访问控制,那么web容器是怎么样支 ...
- 使用iview 的表单组件验证 Upload 组件
使用iview 的表单组件验证 Upload 组件 结果: 点击提交按钮, 没有填的form 项, 提示错误, 当填入数据后提示验证成功 代码: <template> <div id ...
- react如何通过shouldComponentUpdate来减少重复渲染
转自:https://segmentfault.com/a/1190000016494335 在react开发中,经常会遇到组件重复渲染的问题,父组件一个state的变化,就会导致以该组件的所有子组件 ...
- 设置Windows静态IP+动态IP
静态IP 设置以太网属性 进入IPv4属性 设置IPv4 动态IP 同上方法,只不过选成了自动
- h5图片展示和ajax上传
<img src="" id="img"/> <script src="http://static.lamian.tv//pc/pu ...