图论学习:分层图

分层图的应用范围:

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

分层图的构建步骤可以描述为:
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\n");
	else printf("%.3f\n",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;
}
posted @ 2020-07-28 19:06  wsl_lld  阅读(328)  评论(0编辑  收藏  举报