zoukankan      html  css  js  c++  java
  • 图论学习:分层图

    分层图的应用范围:

    比如最短路、网络流等,题目对边的权值提供可选的操作,比如可以将一定数量的边权减半,在此基础上求解最优解。

    分层图的构建步骤可以描述为:
    1、先将图复制成 k+1 份 (0 ~ k)
    2、对于图中的每一条边 <u,v> 从 ui 到 vi+1 建立与题目所给操作相对应的边(i=0,1,…,k)

    k代表了进行操作的次数,而每层之间点的关系代表了何时进行操作。

    例题1:
    洛谷P4822 [BJWC2012]冻结
    题意:给你一个n个点m条边的无向带权图,每条边有一个通过时间w,现在你最多有k次操作可以在通过某条路径的时候将时间变为原来的一半,问你从1到n需要的最短时间是多少?

    题解:建立分层图,层与层之间点的边权为原来的1/2,同层之间边的权值不变

    点击查看折叠代码块
    #include <bits/stdc++.h>
    using namespace std;
    const int maxm = 1e4+10;
    const int maxk = 55;
    const int maxn = 55;
    const int inf=0x3f3f3f3f;
    typedef long long ll;
    typedef pair<int,int> pii;
    
    int head[maxn*maxk],cnt=0;
    struct edge{
        int v,next;
        int w;
    }e[maxm*maxk*2];
    
    void add(int u,int v,int w){
        e[cnt].v=v;
        e[cnt].w=w;
        e[cnt].next=head[u];
        head[u]=cnt++;
    }
    
    int n,m,k;
    int dis[maxn*maxk];
    bool vis[maxn*maxk];
    
    void dijkstra(int s){
        memset(vis,0,sizeof(vis));
        memset(dis,inf,sizeof(dis));
        dis[s] = 0;
        priority_queue<pii,vector<pii>,greater<pii> > q;
        q.push(make_pair(0,s));
    
        while(!q.empty()){
            int u = q.top().second;
            q.pop();
            // cout<<" u = "<<u<<endl;
            if(vis[u]) continue;
            vis[u] = 1;
            for (int i=head[u];~i;i=e[i].next){
                int v=e[i].v;
                if(dis[v]>dis[u]+e[i].w){
                    dis[v]=dis[u]+e[i].w;
                    q.push(make_pair(dis[v],v));
                }
            }
        }
    }
    
    int main(){
        memset(head,-1,sizeof(head));
        cin>>n>>m>>k;
        for (int i=1;i<=m;i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);add(v,u,w);//第0层
            for (int j=1;j<=k;j++){//建立k层
                add(u+j*n-n,v+j*n,w/2);
                add(v+j*n-n,u+j*n,w/2);
                add(u+j*n,v+j*n,w);
                add(v+j*n,u+j*n,w);
            }
        }
        dijkstra(1);
        int ans = inf;
        for (int i=0;i<=k;i++){
            ans=min(ans,dis[n+i*n]);
        }
        cout<<ans<<endl;
        return 0;
    }
    

    例题2:
    洛谷P2939 [USACO09FEB]Revamping Trails G
    题意:同上,只是把边权从w/2变为了0

    点击查看折叠代码块
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef pair<ll,int> pii;
    
    const int maxn=1e4+10;
    const int maxk=21;
    const int maxm=5e5+10;
    const ll inf=0x3f3f3f3f3f3f;
    
    struct edge{
        int v,next;
        int w;
    }e[maxm*maxk*2];
    int head[maxn*maxk],cnt = 0;
    bool vis[maxn*maxk];
    ll dis[maxn*maxk];
    
    void add(int u,int v,int w){
        e[cnt].v=v;
        e[cnt].w=w;
        e[cnt].next=head[u];
        head[u]=cnt++;
    }
    int n,m,k;
    
    void bfs(int s){
        for (int i=0;i<maxn*maxk;i++){
            dis[i] = inf;
            // cout<<dis[i]<<endl;
        } 
        memset(vis,0,sizeof(vis));
        dis[s] = 0;
        priority_queue<pii,vector<pii>,greater<pii> > q;
        q.push(make_pair(dis[s],s));
        while(!q.empty()){
            int u=q.top().second;
            q.pop();
            if(vis[u]) continue;
            vis[u] = 1;
            for (int i=head[u];~i;i=e[i].next){
                int v=e[i].v;
                if(dis[v]>dis[u]+e[i].w){
                    dis[v]=dis[u]+e[i].w;
                    q.push(make_pair(dis[v],v));
                }
            }
        }
    }
    int main(){
        memset(head,-1,sizeof(head));
    
        cin>>n>>m>>k;
        for (int i=1;i<=m;i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
            add(v,u,w);
            for (int j=1;j<=k;j++){
                add(u+j*n-n,v+j*n,0);
                add(v+j*n-n,u+j*n,0);
                add(u+j*n,v+j*n,w);
                add(v+j*n,u+j*n,w);
            }
        }
        bfs(1);
        ll ans = inf;
        for (int i=0;i<=k;i++){
            ans=min(ans,dis[n+i*n]);
        }
        cout<<ans<<endl;
        return 0;
    }
    

    例题3:
    牛客Rinne Loves Dynamic Graph
    在这里插入图片描述
    题解:
    经过几次推导,可以容易发现f函数是一个迭代函数,迭代3次之后会回到F(x)=x,因此我们建立分层图,分为三层,第0层到第1层的边权值是x,第1层到第2层的权值是fabs(1/(1-x))...,最后从第二层连回第一层
    然后跑最短路取个最小值就可以了

    点击查看折叠代码块
    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+10;
    const int maxm=3e5+10;
    typedef long long ll;
    const int inf=0x3f3f3f3f;
    int n,m;
    int q;
    struct edge{
    	int v,next;
    	double w;
    }e[maxm<<4];
    int head[maxn<<3],cnt=0;
    void add(int u,int v,double w){
    	e[++cnt].v=v;
    	e[cnt].w=w;
    	e[cnt].next=head[u];
    	head[u]=cnt;
    }
    bool vis[maxn<<4];
    double dis[maxn<<4];
    
    struct node{
    	int u;
    	double w;
    	bool operator < (const node&rhs) const{
    		return w>rhs.w;
    	}
    	node (int id = 0,double ww = 0.):u(id),w(ww){}
    };
    
    void bfs(){
    	memset(vis,0,sizeof(vis));
    	for (int i=1;i<=3*n;i++){
    		dis[i] = 1.0*inf;
    	}
    	dis[1]=0;
    	priority_queue<node> q;
    	q.push(node(1,0));
    	while(!q.empty()){
    		int u=q.top().u;
    		q.pop();
    		if(vis[u]) continue;
    		vis[u] = 1;
    		for (int i=head[u];i;i=e[i].next){
    			int v=e[i].v;
    			double w=e[i].w;
    			if(dis[v]>dis[u]+w){
    				dis[v]=dis[u]+w;
    				q.push(node(v,dis[v]));
    			}
    		}
    	} 
    }
    int main(){
    	cin>>n>>m;
    	for (int i=1;i<=m;i++){
    		int u,v;
    		double w;
    		scanf("%d%d%lf",&u,&v,&w);
    		//第一层 
    		add(u,v+n,fabs(w));
    		add(v,u+n,fabs(w));
    		//第二层
    		w=1.0/(1-w);
    		add(u+n,v+2*n,fabs(w));
    		add(v+n,u+2*n,fabs(w));
    		//第三层
    		w=1.0/(1-w);
    		add(u+2*n,v,fabs(w));
    		add(v+2*n,u,fabs(w));
    	}
    	bfs();
    	double ans = 1.0*inf;
    	for (int i=1;i<=3;i++){
    		ans=min(ans,dis[i*n]);
    	}
    	if(ans > (inf-1)) puts("-1
    ");
    	else printf("%.3f
    ",ans);
    	return 0;
    }
    

    例题4:
    P3119 [USACO15JAN]Grass Cownoisseur G
    题意:有一个n个点m条边的有向图,现在你要从1点出发,问你在最多可以逆行一次的情况下,最多能遍历图中的多少个点后回到1点,重复遍历的点只算一次
    例如:1-2-4-7-2-5-3-1,答案为6

    做法:
    先将图中存在的环进行缩点,缩点之后图就变成了DAG,然后再在新图上建立分层图,
    假设有一条边为u-v
    共分为两层:
    第0层:u-v
    第1层:u+n-v+n (n为缩点之后新图点的个数)
    第1层到第0层:v-u+n (表示一条可逆行的边)

    注:第1层的每个点的权值应该和第0层相同

    之后在分层图上跑spfa求最长路,然后我们每次求dis[v] = dis[u] + w时,w等于点u的权值,这样就可以保证点book[1] (新图点1的编号) 的权值只计算了一遍,同时,层与层之间只有一条单向边,且每层都是一个无环图,这样也可以保证其他点的权值也只算了一遍,最后答案就是dis[book[1]+n]

    点击查看折叠代码块
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef pair<int ,int> pii;
    
    const int inf=0x3f3f3f3f;
    const int maxn=1e6+10;
    const int maxm=1e6+10;
    struct edge{
        int u,v,next;
    }e[maxm<<2];
    int head[maxn],cnt=0;
    
    void add(int u,int v){
        e[cnt].u=u;
        e[cnt].v=v;
        e[cnt].next=head[u];
        head[u]=cnt++;
    }
    
    int dfn[maxn],low[maxn],tot=0;
    int book[maxn],num[maxn],ct=0;
    bool instack[maxn];
    stack<int> st;
    
    int n,m;
    void tarjan(int u){
        dfn[u]=low[u]=++tot;
        st.push(u);
        instack[u]=1;
        for (int i=head[u];~i;i=e[i].next){
            int v=e[i].v;
            if(!dfn[v]){
                tarjan(v);
                low[u]=min(low[u],low[v]);
            }
            else if(instack[v]){
                low[u]=min(low[u],dfn[v]);
            }
        }
        if(dfn[u] == low[u]){
            ct++;
            while(1){
                int x=st.top();
                st.pop();
                instack[x]=0;
                book[x]=ct;
                num[ct]++;
                if(x == u) break;
            }
        }
    }
    
    int dis[maxn];
    bool vis[maxn];
    
    void spfa(int s){//缩点之后变为DAG
        for (int i=1;i<maxn;i++) dis[i] = -inf;
        memset(vis,0,sizeof(vis));
        dis[s] = 0;
        vis[s] = 1;
    
        queue<int> q;
        q.push(s);
        while(!q.empty()){
            int u = q.front();
            q.pop();
            vis[u] = 0;
            for (int i=head[u];i!=-1;i=e[i].next){
                int v=e[i].v;
                int w=num[u];
                if(dis[v] < dis[u] + w){
                    dis[v] = dis[u] + w;
                    if(!vis[v]){
                        vis[v]=1;
                        q.push(v);
                    }
                }
            }
        }
    }
    
    void Clear(){//清空图
        memset(e,0,sizeof(e));
        memset(head,-1,sizeof(head));
        cnt=0;
    }
    
    int x[maxn],y[maxn];
    
    int main(){
        memset(head,-1,sizeof(head));
        cnt=0;
        cin>>n>>m;
        for (int i=1;i<=m;i++){
            scanf("%d%d",&x[i],&y[i]);//建分层图先存图,不然导致内存出问题,卡死我了qwq
            add(x[i],y[i]);
        }
        for (int i=1;i<=n;i++){
            if(!dfn[i]){
                tarjan(i);
            }
        }
        Clear();
        for (int i=1;i<=m;i++){//缩点后建分层图
            int u=book[x[i]],v=book[y[i]];
            if(u!=v){
                add(u,v);//第0层
                add(u+ct,v+ct);//第1层
                add(v,u+ct);//第0层到第1层建一条逆向边
            }
        }
        for (int i=1;i<=ct;i++){//第二层复制一遍第一层的点权
            num[i+ct] = num[i];
        }
        //跑最长路
        spfa(book[1]);
        int ans = dis[book[1]+ct];
        cout<<ans<<endl;
        return 0;
    }
    
    你将不再是道具,而是成为人如其名的人
  • 相关阅读:
    luogu P1340 兽径管理
    luogu P2828 Switching on the Lights(开关灯)
    luogu P1462 通往奥格瑞玛的道路
    codevs 2596 售货员的难题
    luogu P1145 约瑟夫
    luogu P1395 会议
    luogu P1041 传染病控制
    luogu P1198 [JSOI2008]最大数
    codevs 1191 数轴染色
    [POJ1082]Calendar Game
  • 原文地址:https://www.cnblogs.com/wsl-lld/p/13393042.html
Copyright © 2011-2022 走看看