分层图

分层图学习笔记

分层图的笔记咕了好久,今天(2024/10/15)终于有时间写了。

首先,分层图只是一种思想,而不是一种算法。

从名字上来理解,就是将图论中的点分成不同的批次,不同的类型,也就是分层。

一般来说,分层图就是原来存在的节点乘上某个数再加上原来的节点,直接一点,就是变成一个不会重复的,空间合理的,有顺序的其他节点进行建图。

P4568 [JLOI2011] 飞行路线

题目描述

Alice 和 Bob 现在要乘飞机旅行,他们选择了一家相对便宜的航空公司。该航空公司一共在 \(n\) 个城市设有业务,设这些城市分别标记为 \(0\)\(n-1\),一共有 \(m\) 种航线,每种航线连接两个城市,并且航线有一定的价格。

Alice 和 Bob 现在要从一个城市沿着航线到达另一个城市,途中可以进行转机。航空公司对他们这次旅行也推出优惠,他们可以免费在最多 \(k\) 种航线上搭乘飞机。那么 Alice 和 Bob 这次出行最少花费多少?

数据规模与约定

对于 \(30\%\) 的数据,\(2 \le n \le 50\)\(1 \le m \le 300\)\(k=0\)

对于 \(50\%\) 的数据,\(2 \le n \le 600\)\(1 \le m \le 6\times10^3\)\(0 \le k \le 1\)

对于 \(100\%\) 的数据,\(2 \le n \le 10^4\)\(1 \le m \le 5\times 10^4\)\(0 \le k \le 10\)\(0\le s,t,a,b < n\)\(a\ne b\)\(0\le c\le 10^3\)

因为免费的次数是有限的,所以我们将一次免费的乘坐看作是上了一层楼,如图。

建图核心代码

rep(i, 1, m){
	int a, b, c;
	cin >> a >> b >> c;
	add(a, b, c);//本层 
	add(b, a, c);
	
	rep(j, 1, k){
		add(a + (j - 1) * n, b + j * n, 0);//j只能从1开始,不然越界,a到b免费 
		add(b + (j - 1) * n, a + j * n, 0);//b也可以建向a 
		add(a + j * n, b + j * n, c);//这一层可以不使用免费次数 
		add(b + j * n, a + j * n, c);//同理 
	}
}

Dijkstra 还是 SPFA 自己选,反正最短路很简单,但是输出答案又有细节。

\(k\) 的值可能很大,达到用不完,所以需要取最小值。

取答案核心代码

ans = 0x3f3f3f3f;
rep(i, 0, k) ans = min(ans, dis[t + i * n]);//可能用不完 

在定义数组是必须注意计算清楚,开几倍空间,是否开 LL 等等。

例如这道题,一次机会会产生 \(4\) 条边,所以这样:

const int N = 1e4 + 10, M = 5e4 + 10, K = 15, E = (M * K) << 2;//必须开4倍,会有4条边产生

边数 \(E=(M \times K) \times 4\),这里根据题意而定。

另外这两道题也不尽相同:P2939 [USACO09FEB] Revamping Trails GP4822 [BJWC2012] 冻结,冻结需要让边长除以二,但是基本相同。

P5340 [TJOI2019] 大中锋的游乐场

题目描述

大中锋正在一个游乐场里玩耍。游乐场里有 \(n\) 个娱乐设施,娱乐设施之间相互有共 \(m\) 条道路相连,经过每一条路都需要花费一定的时间。为了方便游客,每一个娱乐设施旁都会配有一个小卖部,一部分小卖部会销售可乐,另一部分会销售汉堡。

由于大中锋十分贪吃,所以每当他走到一个娱乐设施,他都会先去购买一杯可乐或一个汉堡,并把它们吃掉。但如果大中锋吃掉的汉堡数量比他喝掉的可乐数量多于 \(k\) ,那他就会感到很渴;如果喝掉的可乐数量比吃掉的汉堡数量多于 \(k\) ,那他就会感到很饿。

现在大中锋正在第 \(a\) 个娱乐设施,他想前往第 \(b\) 个娱乐设施,但在他前进的路途中他不希望自己很渴或很饿。大中锋想知道自己在路上少花费多少时间。但由于大中锋很懒惰,他不想思考这个问题。你能帮助他解决这个问题吗?

注意:大中锋非常贪吃,所以他到达每个点的第一件事是去吃(或者喝),才考虑其他的事情,所以在起始点和终点他都会去买汉堡(可乐),你也需要保证在这两个点他不会感到很饿或者很渴。

题目补充说明

  • 路径不一定是简单路径。
  • 大中锋可以多次经过一个节点,同时每次都会取得汉堡/可乐。

注意到 \(k\) 的范围比较小,我们可以考虑套路性的采用分层图做法。将一个点拆分多个点,分布在 \(2 \times k\) 层图上,并且向下层图中走为买了可乐,向上层图中走为买了汉堡,对于每一层图不进行连边(这是由于题目限制每次经过一个点必须购买汉堡或者可乐)。

还有几点需要注意到,由于起点的点权也要加上,所以需要对起点的点权类型进行特判。多测需要清空。

# include <bits/stdc++.h>
# define sec second
# define mem(a, b) memset(a, b, sizeof(a))
using namespace std;

typedef pair<int, int>  PII;

const int N = 9e5, M = 5e6;

int T, n, m, k, s, t, idx;
int op[N], dis[M];
int h[M], e[M << 1], w[N << 1], ne[M << 1];
bool vis[M];

int getid(int pos, int f){
   return n * (10 + f) + pos;
}

void add(int a, int b, int c){
   e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

void dijkstra(int start){
   mem(dis, 0x3f);
   mem(vis, false);
   priority_queue<PII, vector<PII>, greater<PII> > heap;
   dis[start] = 0;
   heap.push({0, start});
   
   while(!heap.empty()){
   	int u = heap.top().sec;
   	heap.pop();
   	if(vis[u]) continue;
   	for(int i = h[u]; ~i; i = ne[i]){
   		int v = e[i];
   		if(vis[v] || dis[v] <= dis[u] + w[i]) continue;
   		dis[v] = dis[u] + w[i];
   		heap.push({dis[v], v});
   	}
   	vis[u] = true;
   }
}

int main(){
   cin >> T;
   while(T --){
   	idx = 0;
   	mem(h, -1);
   	cin >> n >> m >> k;
   	for(int i = 1; i <= n; i ++){
   		cin >> op[i];
   	}
   	
   	for(int i = 1, a, b, c; i <= m; i ++){
   		cin >> a >> b >> c;
   		for(int j = -k; j <= k; j ++){
   			if(j + 1 <=  k && op[b] == 1) add(getid(a, j), getid(b, j + 1), c);
   			if(j - 1 >= -k && op[b] == 2) add(getid(a, j), getid(b, j - 1), c);
   			if(j + 1 <=  k && op[a] == 1) add(getid(b, j), getid(a, j + 1), c);
   			if(j - 1 >= -k && op[a] == 2) add(getid(b, j), getid(a, j - 1), c);
   		}
   		
   	}
   	
   	cin >> s >> t;
   	dijkstra(getid(s, op[s] == 1 ? 1 : -1));
   	int ans = 0x3f3f3f3f;
   	for(int j = -k; j <= k; j ++)
   		ans = min(ans, dis[getid(t, j)]);
   	
   	if(ans == 0x3f3f3f3f) cout << -1 << "\n";
   	else cout << ans << "\n";
   }
   
   return 0;
}

現代的な屋敷 (Modern Mansion)

题面翻译

有一座东西 \(M\) 列南北 \(N\) 行的大宅。

任何相邻的两间房之间都有一扇门连接,若一扇门是打开状态,则可以从门的一边走到另一边,并且花费 \(1\) 分钟。

\(K\) 个房间设有开关,按下开关会导致所有门的开关状态切换,并且花费 \(1\) 分钟。

最开始连接东西相邻房间的所有门都关闭,连接南北相邻房间的所有门都打开,输出从房间 \((1,1)\) 移动到房间 \((M,N)\) 的最短时间。

(续写 \(by\) \(2024/10/17\))

集训的时候做的一道题。

对于同一个有开关房间,它有南北开通和东西开通两种状态,并且这两种状态相互转换需要 \(1\) 的代价,那么我们可以将一个房间变成两个房间,分别表示上述的状态。存图的话,因为有开关的房间有 \(k\) 个,所以你只需要在原来的点的基础上加上 \(k\) 就可以存下了。也就是自己建向自己的另一种状态,双向边,边权为 \(1\)

\(\Bbb {THE}\) \(\rm END.\)

posted @ 2024-10-30 15:31  MrTourist  阅读(12)  评论(0编辑  收藏  举报