浅谈dfs/Tarjan找环【复习】

http://acm.hdu.edu.cn/showproblem.php?pid=6736

题目大意:

求一张沙漠(多个仙人掌),删边使之变成森林的方案数。

分析:

对于一个环,环上所有边都可删,(但必须得保证起码删了一条边)所以有2n-1种方案

对于剩下的链,都可删或不删,2n种方案

找环就好

code:

//#include <bits/stdc++.h>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <stdio.h>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <string.h>
#include <vector>
#define ME(x , y) memset(x , y , sizeof(x))
#define SF(n) scanf("%d" , &n)
#define rep(i , n) for(int i = 0 ; i < n ; i ++)
#define INF  0x3f3f3f3f
#define mod 998244353
#define PI acos(-1)
using namespace std;
typedef long long ll ;
const int maxn = 505005 ;
const int maxm = 2050000;
ll n , m , sum , ans , vis[maxn] , dfn[maxn] , cnt;
ll head[maxn];
struct Edge
{
    ll to , next ;
}e[maxm];

void add(ll u , ll v)
{
    e[cnt].to = v ;
    e[cnt].next = head[u];
    head[u] = cnt++;
}

void init()
{
    memset(vis , 0 , sizeof(vis));
    memset(dfn , 0 , sizeof(dfn));
    memset(head , -1 , sizeof(head));
    cnt = 0 , ans = 1 ;
}
ll qpow(ll base, ll n)
{
    ll ans = 1;
    while(n)
    {
        if(n&1) ans=(ans%mod)*(base%mod)%mod;
        base = (base%mod) * (base%mod)%mod;
        n/=2;
    }
    return ans%mod;
}

void dfs(ll id , ll step , ll fa)
{
    vis[id] = 1 , dfn[id] = step ;
    for(ll i = head[id] ; i != -1 ; i = e[i].next)
    {
        ll v = e[i].to ;
        if(v == fa || vis[v] == 2) continue ;
        if(vis[v] == 1)
        {
            sum += step - dfn[v] + 1;
            ans *= (qpow(2 , step-dfn[v]+1)-1+mod) % mod ;
            ans %= mod ;
        }
        else
            dfs(v , step+1 , id);
    }
    vis[id] = 2 ;
}

int main()
{
    scanf("%lld%lld" , &n , &m);
    init();
    for(ll i = 1 ; i <= m ; i++)
    {
        ll u , v ;
        scanf("%lld%lld" , &u , &v);
        add(u , v);
        add(v , u);
    }
    for(ll i = 1 ; i <= n ; i++)
    {
        if(!vis[i])
            dfs(i , 1 , -1);
    }
    ans *= qpow(2 , m - sum);
    ans %= mod ;
    printf("%lld
" , ans);


    return 0;
}

Codeforces 711D

题意:

有一个n个点的有向图,有n条边,分别从每个点出发指向某个点,现在可以把某些边翻转,问总共可以得到多少种无换图?

分析:

上题类似

先找环

环的贡献是2n-2

2是一个都不翻转,和都翻转

其他链的贡献是2n

code:

#include<bits/stdc++.h>
using namespace std;
#define rep(i,s,t) for(int i=(s);i<(t);i++)
#define per(i,t,s) for(int i=(t);i>=(s);i--)
#define fi first
#define se second
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>pii;
const int mod = 1e9 + 7;
const int N = 2e5 + 9;
vector<pii>g[N];
int r;
int d[N], vis[N];
void dfs (int u, int fa, int deep) {
    if (vis[u]) {
        if (r == 0) r = deep - d[u];
        return;
    }
    vis[u] = 1;
    d[u] = deep;
    rep (i, 0, g[u].size() ) {
        if (g[u][i].se == fa) continue;
        dfs (g[u][i].fi, g[u][i].se, deep + 1);
    }
}
ll f[N];
int main() {
    //freopen("f.txt","r",stdin);
    int n, x;
    scanf ("%d", &n);
    f[0] = 1;
    rep (i, 1, n + 1) f[i] = f[i - 1] * 2 % mod;
    rep (i, 1, n + 1) {
        scanf ("%d", &x);
        g[i].push_back (pii (x, i) );
        g[x].push_back (pii (i, i) );
    }
    ll ans = 1;
    int sum = 0;
    rep (i, 1, n + 1) if (!vis[i]) {
        r = 0;
        dfs (i, 0, 0);
        sum += r;
        //cout<<r<<endl;
        ans = ans * (f[r] - 2) % mod;
    }
    cout << ans* (f[n - sum]) % mod << endl;
    return 0;
}

题目大意:

n个点,m条又向边,源点为0,边有边权,一个环上的点之间没有花费,求源点传输数据到各点的最小花费

分析:

首先tarjan缩点,关键就在怎样求最小花费

尽管缩完点后是一个有向无环图,因为有可能会有重边,所以最小花费还是无法确定的

核心操作:

因为题目保证能够传输到各个点,所以缩完点后每个点起码有一个入度,当然源点所在的除外

所以能传输到一个缩完点的点的最小花费一定是这些入边的最小值

所以缩点的时候直接反向建边即可

总结:

求最小值的时候为什么不正向拓扑呢?

因为每个点的出边无法确定选多少条,而每个点的入边一定只选一条

code:

#include<iostream>
#include<cstdio>
#include<queue>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5e5+10;
int n,m,num,top;
int tot,first[N],nex[N],edge[N],to[N],s[N],in[N],c[N],cnt,low[N],dfn[N];
inline int read(){
    int ss=0;char bb=getchar();
    while(bb<48||bb>57)bb=getchar();
    while(bb>=48&&bb<=57)ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar();
    return ss;
}
void add(int a,int b,int ci){
    to[++tot]=b,edge[tot]=ci,nex[tot]=first[a],first[a]=tot;
}
int totc,firstc[N],toc[N],nexc[N],edgec[N];
void add_c(int a,int b,int ci){
    toc[++totc]=b,edgec[totc]=ci,nexc[totc]=firstc[a],firstc[a]=totc;
}
void init(){
    tot=0;
    memset(first,0,sizeof(first));
    memset(nex,0,sizeof(nex));
    memset(to,0,sizeof(to));
    memset(s,0,sizeof(s));
    memset(in,0,sizeof(in));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(c,0,sizeof(c));
    memset(firstc,0,sizeof(firstc));
    memset(nexc,0,sizeof(nexc));
    memset(toc,0,sizeof(toc));
    memset(edgec,0,sizeof(edgec));
    top=0,totc=0,cnt=0;
}
void tarjan(int x){
    dfn[x]=low[x]=++num;s[++top]=x,in[x]=1;
    for(int i=first[x];i;i=nex[i]){
        int y=to[i];
        if(!dfn[y]){
            tarjan(y);
            low[x]=min(low[y],low[x]);
        }
        else if(in[y]){
            low[x]=min(low[x],dfn[y]);
        }
    }
    if(dfn[x]==low[x]){
        cnt++;int y;
        do{
            y=s[top--],in[y]=0;
            c[y]=cnt;
        }while(x!=y);
    }
}
int main(){
    for(;;){
        n=read();m=read();
        if(n==0&&m==0) break;
        int pf=0;
        init();
        for(int i=1;i<=m;i++){
            int a,b,ci;
            a=read(),b=read(),ci=read();
            a++,b++;pf+=ci;
            add(a,b,ci);
        }
        if(m==n-1){
            printf("%d
",pf);
            continue;
        }
        for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
        for(int x=1;x<=n;x++){
            for(int i=first[x];i;i=nex[i]){
                int y=to[i];
                if(c[x]==c[y]) continue;
                add_c(c[y],c[x],edge[i]);
            }
        }
        long long ans=0;
        for(register int x=1;x<=cnt;x++){
            int res=1e9+10;
            for(register int i=firstc[x];i;i=nexc[i]){
                res=min(res,edgec[i]);
            }
            ans+=res;
        }ans-=(1e9+10);
        printf("%lld
",ans);
    }
    return 0;
}

题目补充:还保证每个点的出度为1

先拓展一个:

分析:

首先只是一颗基环内向树组成的森林,并且不存在什么最短路,两点间的距离是唯一的

先用Tarjan缩点,再算树边的贡献

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#define ll long long
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=500005,p=1e9+7;
int n;
int go[N],len[N];
int tot,first[N],nxt[N],to[N],w[N];
int idx,top,dfn[N],low[N],stk[N];
int num,id[N],size[N];
ll sum[N],val[N],ans;
vector<int>f[N];
bool exist[N];

inline void add(int x,int y,int z)
{
    nxt[++tot]=first[x],first[x]=tot,to[tot]=y,w[tot]=z;
}

inline void tarjan(int u)
{
    dfn[u]=low[u]=++idx;
    stk[++top]=u,exist[u]=true;
    int v=go[u];
    if(!dfn[v])
    {
        tarjan(v);
        low[u]=min(low[u],low[v]);
    }
    else 
        if(exist[v])low[u]=min(low[u],dfn[v]);
    if(low[u]==dfn[u])
    {
        ++num;
        v=stk[top];
        while(v!=u)
        {
            id[v]=num,size[num]++;
            f[num].push_back(v);
            top--,exist[v]=false,v=stk[top];
        }
        id[v]=num,size[num]++;
        f[num].push_back(v);
        top--,exist[v]=false;
    }
}

inline void calc(int i)
{
    reverse(f[i].begin(),f[i].end());//翻转reverse,因为入栈顺序是反的 
    for(int j=0;j<size[i];j++)
        val[f[i][0]]=(val[f[i][0]]+1ll*(size[i]-j)*len[f[i][j]]%p)%p;
    for(int j=1;j<size[i];j++)
        val[f[i][j]]=(val[f[i][j-1]]+sum[i]-1ll*size[i]%p*len[f[i][j-1]]%p)%p;
    for(int j=0;j<size[i];j++)
        val[f[i][j]]=(val[f[i][j]]-sum[i])%p;   
}

inline void dfs(int u,int cnt)
{
    exist[u]=true;
    ans=((ans-1ll*(n-cnt)*size[u])%p+p)%p;
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];
        if(!exist[v])dfs(v,cnt+size[v]);
        ans=(ans+1ll*size[v]*cnt%p*w[e]%p)%p;
        size[u]+=size[v];
    }
}

int main()
{
    //freopen("road.in","r",stdin);
    //freopen("road.out","w",stdout);
    int i,y,z;
    n=getint();
    for(i=1;i<=n;i++)
        go[i]=getint(),len[i]=getint();
    for(i=1;i<=n;i++)
        if(!dfn[i])tarjan(i);
    for(i=1;i<=n;i++)
        if(id[go[i]]==id[i])sum[id[i]]=(sum[id[i]]+len[i])%p;
        else add(id[go[i]],id[i],len[i]);
    for(i=1;i<=num;i++)
    {
        if(size[i]==1)continue;
        ans=(ans+1ll*size[i]*(size[i]-1)/2%p*sum[i]%p)%p;
        calc(i);
    }
    memset(exist,0,sizeof(exist));
    for(i=1;i<=num;i++)
        if(!exist[i])dfs(i,size[i]);
    for(i=1;i<=n;i++)
        if(id[go[i]]!=id[i])ans=(ans+1ll*size[id[i]]*val[go[i]]%p)%p;
    cout<<(ans%p+p)%p;
    return 0;
}

https://www.luogu.org/problem/P3533

分析:

如果两点再同一个连通块上,最后两点肯定会走到同一个环上

这里分情况讨论:

1.两点不在同一个连通块,输出(-1,-1)

2.两点走到环上是同一点,这时只要一个动,另一个不动就好,不然就成追击问题了

3.两点走到环上不是同一个点,分两种情况(x->y||y->x),因为有方向嘛

1很好解决,2倍增处理lca,处理环上每个点的外向树,3直接两种情况取最优

code:

#include<bits/stdc++.h>
#define de puts("#")
#define bug(x) cout<<#x<<" : "<<x<<endl
using namespace std;
const int maxn=5e5+5;

int read() {
    int a=0;
    char c;
    while(c=getchar(),c<48);
    do a=(a<<1)+(a<<3)+(c^48);
    while(c=getchar(),c>=48);
    return a;
}

bool a1;
int n,q,to[maxn],deg[maxn],cnt,tot,qx,qy,fa[maxn][20],ID[maxn],sz[maxn],dep[maxn],head[maxn],from[maxn],tot1;
bool bl[maxn];
queue<int>Q;
bool b1;

struct node {
    int to,nxt;
} edge[maxn<<1];

void add(int x,int y) {
    edge[++tot]=(node)<%y,head[x]%>;
    head[x]=tot;
}

void getloop(int x,int id,int step) {
    if(ID[x])return;
    ID[x]=step,sz[id]++,to[x]=id;
    getloop(fa[x][0],id,step+1);
}

int LCA(int x,int y) {
    if(dep[x]>dep[y])swap(x,y);
    int step=dep[y]-dep[x];
    for(int i=19; i>=0; i--)if(step&(1<<i))y=fa[y][i];
    if(x==y)return x;
    for(int i=19; i>=0; i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}

void dfs(int x,int f,int F,int D) {
    dep[x]=D,from[x]=F;
    for(int i=head[x]; i; i=edge[i].nxt) {
        int to=edge[i].to;
        if(to==f||!bl[to])continue;
        dfs(to,x,F,D+1);
    }
}

bool pd(int x1,int y1,int x2,int y2) {
    if(std::max(x1,y1)!=std::max(x2,y2))return std::max(x1,y1)<std::max(x2,y2);
    if(std::min(x1,y1)!=std::min(x2,y2))return std::min(x1,y1)<std::min(x2,y2);
    return x1>=y1;
}

int main() {
    n=read(),q=read();
    for(int i=1; i<=n; i++)fa[i][0]=read(),add(fa[i][0],i),deg[fa[i][0]]++;
    for(int i=1; i<=n; i++)if(deg[i]==0)Q.push(i);
    while(!Q.empty()) {
        int x=Q.front();
        bl[x]=1;
        Q.pop();
        if(--deg[fa[x][0]]==0)Q.push(fa[x][0]);
    }
    for(int i=1; i<=n; i++) {
        if(!bl[i]) {
            dfs(i,0,i,0);
            if(!ID[i])getloop(i,++tot1,1);
        }
    }
    for(int k=1; k<=19; k++)
        for(int x=1; x<=n; x++)
            fa[x][k]=fa[fa[x][k-1]][k-1];
    while(q--) {
        qx=read(),qy=read();
        int fx=from[qx],fy=from[qy];
        if(to[fx]!=to[fy]) {
            puts("-1 -1");
            continue;
        } else if(fx==fy) {
            int lca=LCA(qx,qy);
            printf("%d %d
",dep[qx]-dep[lca],dep[qy]-dep[lca]);
        } else {
            int more1=dep[qx]+(ID[fy]-ID[fx]+sz[to[fx]])%sz[to[fx]],more2=dep[qy]+(ID[fx]-ID[fy]+sz[to[fx]])%sz[to[fx]];
            if(pd(dep[qx],more2,more1,dep[qy]))printf("%d %d
",dep[qx],more2);
            else printf("%d %d
",more1,dep[qy]);
        }
    }
    return 0;
}

https://cometoj.com/contest/73/problem/E

分析:

code by std:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
typedef vector<int> vi;
#define pb push_back
typedef pair<int,int> pii;
#define mp make_pair
#define fi first
#define se second

int gi() {
	int x=0,o=1;char ch=getchar();
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') o=-1,ch=getchar();
	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
	return x*o;
}

struct edge {
	int v,mx,mn;
};

int n,m,q,dfn[N],low[N],tim,mx[N],mn[N],f[N],bel[N],MX[N],MN[N],st[N],tp,scc,deg[N];
bool in[N];
vector<int> P[N];
vector<pii> E[N];
vector<edge> G[N];
void tarjan(int u) {
	dfn[u]=low[u]=++tim;
	st[++tp]=u,in[u]=1;
	for(auto e:E[u]) {
		int v=e.fi;
		if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
		else if(in[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]) {
		int v;++scc;
		do v=st[tp--],bel[v]=scc,in[v]=0,P[scc].pb(v); while(u!=v);
	}
}

void link(int u,int v,int mx,int mn) {
	G[u].pb((edge){v,mx,mn});
	++deg[v];
}

void dfs(int u) {
	if(in[u]) return;
	in[u]=1;
	for(auto e:G[u]) dfs(e.v);
}

int main() {
	cin>>n>>m>>q;
	for(int i=1;i<=m;i++) {
		int u=gi(),v=gi(),w=gi();
		E[u].pb(mp(v,w));
	}
	for(int i=1;i<=n;i++)
		if(!dfn[i]) tarjan(i);
	for(int i=1;i<=scc;i++) MX[i]=-1,MN[i]=1e9+1;
	for(int u=1;u<=n;u++)
		for(auto e:E[u]) {
			int v=e.fi,w=e.se;
			if(bel[u]==bel[v]) {
				MX[bel[u]]=max(MX[bel[u]],w);
				MN[bel[u]]=min(MN[bel[u]],w);
			}
		}//先处理自身 
	for(int i=1;i<=scc;i++)
		link(i,i+scc,MX[i],MN[i]);
		//将前一部分的边先连着 
	for(int u=1;u<=n;u++)
		for(auto e:E[u]) {
			int v=e.fi,w=e.se;
			if(bel[u]!=bel[v]) link(bel[u]+scc,bel[v],w,w);
		}
		//再将不是同一连通块的边后部分连上 
	memset(f,-1,sizeof(f));
	for(int i=1;i<=scc*2;i++)
		mx[i]=-1,mn[i]=1e9+1;
	dfs(bel[1]);
	queue<int> Q;
	for(int i=1;i<=scc*2;i++)
		if(!deg[i]) Q.push(i);
	while(!Q.empty()) {
		int u=Q.front();Q.pop();
		for(auto e:G[u]) {
			int v=e.v;
			if(in[u]) {
				f[v]=max(f[v],f[u]);
				f[v]=max(f[v],e.mx-mn[u]);
				f[v]=max(f[v],mx[u]-e.mn);
				f[v]=max(f[v],e.mx-e.mn);
				mx[v]=max(mx[v],max(mx[u],e.mx));
				mn[v]=min(mn[v],min(mn[u],e.mn));
			}
			if(!--deg[v]) Q.push(v);
		}
	}
	while(q--) {
		int t=gi();
		cout<<f[bel[t]+scc]<<'
';
	}
	return 0;
}
原文地址:https://www.cnblogs.com/wzxbeliever/p/11818612.html