图论学习:分层图
分层图的应用范围:
比如最短路、网络流等,题目对边的权值提供可选的操作,比如可以将一定数量的边权减半,在此基础上求解最优解。
分层图的构建步骤可以描述为:
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;
}