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;
    }
    
    你将不再是道具,而是成为人如其名的人
  • 相关阅读:
    可爱的中国电信 请问我们的电脑还属于我们自己吗?
    了解客户的需求,写出的代码或许才是最优秀的............
    DELPHI DATASNAP 入门操作(3)简单的主从表的简单更新【含简单事务处理】
    用数组公式获取字符在字符串中最后出现的位置
    在ehlib的DBGridEh控件中使用过滤功能(可以不用 MemTableEh 控件 适用ehlib 5.2 ehlib 5.3)
    格式化json返回的时间
    ExtJs中使用Ajax赋值给全局变量异常解决方案
    java compiler level does not match the version of the installed java project facet (转)
    收集的资料(六)ASP.NET编程中的十大技巧
    收集的资料共享出来(五)Asp.Net 权限解决办法
  • 原文地址:https://www.cnblogs.com/wsl-lld/p/13393042.html
Copyright © 2011-2022 走看看