zoukankan      html  css  js  c++  java
  • 浅谈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;
    }
    
  • 相关阅读:
    Ueditor富文本编辑器
    ashx误删后,未能创建类型
    一个菜鸟的ASP.NET观光路线图
    地位尴尬的WebForm、ASP.NET核心知识(10)
    经典排序算法--归并排序
    经典排序算法--快速排序
    经典排序算法--冒泡排序
    经典排序算法--堆排序
    经典排序算法--简单选择排序
    经典排序算法--希尔排序
  • 原文地址:https://www.cnblogs.com/wzxbeliever/p/11818612.html
Copyright © 2011-2022 走看看