「PKUSC2018 星际穿越」Solution

题目传送门

[PKUSC2018] 星际穿越

简述题意

给定一张 n n n 个点的无向图和 q q q 次询问,每一个点 i i i ∀ j ∈ [ l i , i − 1 ] \forall j \in [l_i , i -1] j[li,i1] 都存在一条长度为 1 1 1 的无向边,每次询问给定 l i , r i , x i l_i,r_i,x_i li,ri,xi,求 1 r i − l i + 1 ∑ y = l i r i d i s t ( x i , y ) \frac{1}{r_i-l_i+1}{\sum_{y=l_i}^{r_i}{dist(x_i,y)}} rili+11y=liridist(xi,y) 的最简分数形式。

  • n , q ≤ 3 × 1 0 5 , 1 ≤ l i < r i < x i ≤ n n,q\leq 3\times 10^5,1\le l_i < r_i < x_i \le n n,q3×1051li<ri<xin

思路

Subtask1 & 2

直接把图建出来,对每一个点暴力跑一遍 dijkstra \text{dijkstra} dijkstra,每次询问累和即可。
时间复杂度 O ( n 2 l o g n ) O(n^2logn) O(n2logn),可以得到 45 p t s 45pts 45pts

Subtask3

与正解强相关

先把图降维成序列问题,把 n n n 个点抽象成 n n n 个元素的序列 a a a

考虑贪心,由于 l i < r i < x i l_i < r_i < x_i li<ri<xi,因此一定尽可能往右跳,才能最小化步数。
不妨令 n x t i , j nxt_{i,j} nxti,j 表示从 i i i 开始跳 j j j 步,能到达的最小点。

显然 n x t i , 0 = l i nxt_{i,0} = l_i nxti,0=li,然而 n x t i , 1 nxt_{i,1} nxti,1 一定等于 l l x i l_{l_{x_i}} llxi 嘛?

不是。如果有一个点 j ∈ [ l x i , i − 1 ] j \in [l_{x_i},i-1] j[lxi,i1] 满足 l j ≤ l l x i l_j \le l_{l_{x_i}} ljllxi,那么跳到 j j j 一定比跳到 l x i l_{x_i} lxi 更优,因此有:

n x t i , j = min ⁡ { l n x t i , j − 1 , . . . , l i − 1 , l i , l i + 1 , . . . , l n } nxt_{i,j}=\min\{l_{nxt_{i,j-1}},...,l_{i-1},l_i,l_{i+1},...,l_n\} nxti,j=min{lnxti,j1,...,li1,li,li+1,...,ln}

为什么要考虑 [ i + 1 , n ] [i+1,n] [i+1,n] 这一部分呢?因为有可能往右跳比往左跳更优,举个例子可能更形象:
在这里插入图片描述

假如 5 5 5 是起点, 1 1 1 是终点,那么很显然 5 → 6 5 \rightarrow 6 56 5 → 4 5 \rightarrow 4 54 更优。

因此枚举每个点作为起点,进行一次上述过程即可。
最坏时间复杂度 O ( n 2 ) O(n^2) O(n2),可以得到 75 p t s 75pts 75pts

Subtask4

上述做法中,发现我们的时间复杂度瓶颈在于 j j j 这一维,而且正解时间复杂度应该是 O ( n l o g n ) O(nlogn) O(nlogn),这都启发我们倍增。

不妨令 d p i , j dp_{i,j} dpi,j 表示从 i i i 开始跳 2 j 2^j 2j 步能够到达的最小点。

那么有经典倍增式 d p i , j = d p d p i , j − 1 , j − 1 dp_{i,j} = dp_{dp_{i,j-1},j-1} dpi,j=dpdpi,j1,j1

然后显然有 d p i , 0 = l i dp_{i,0}=l_i dpi,0=li?大多数人的第一想法可能都是这样初始化,然而这显然是错的!
再举个例子。

在这里插入图片描述
如上图,如果这样初始化就有 d p 5 , 1 = d p d p 5 , 0 , 0 = d p 3 , 0 = 2 dp_{5,1}=dp_{dp_{5,0},0}=dp_{3,0}=2 dp5,1=dpdp5,0,0=dp3,0=2,然而显然可以 5 → 4 → 1 5 \rightarrow 4 \rightarrow 1 541,为什么会出错呢?由 subtask3 \text{subtask3} subtask3 的做法可得,我们没有考虑往右跳的情况。

然而每个点都会出现往右跳的情况吗?
显然不是。具体地,有如下引理:

引理:
在任意最优情形下,只有起点可能往右跳,其他点一定只会向左跳。
证明也是显然的,如果当前点往右跳到 x x x 更优,那为何不直接从上一个点(一定会有前驱点)跳到 x x x 呢?

因此,我们只需要考虑起点往右跳的情况,就可以保证递推的重要性,因此 d p i , 0 = min ⁡ { l i , l i + 1 , . . . , l n } dp_{i,0}=\min\{l_i,l_{i+1},...,l_n\} dpi,0=min{li,li+1,...,ln}(至于那些 i i i 往右跳无法到达的点一定有 l j > i l_{j} > i lj>i,所以考虑了也无所谓),发现这样初始化代入上述例子也是对的。

考虑如何回答询问。不妨令 s u m i , j sum_{i,j} sumi,j 表示 i i i [ d p i , j , i − 1 ] [dp_{i,j},i-1] [dpi,j,i1] 的距离和,注意 s u m sum sum未包含向右跳的贡献,这一部分要在后面单独讨论!!!

那么有 s u m i , 0 = i − d p i , 0 sum_{i,0}=i-dp_{i,0} sumi,0=idpi,0

  • 先考虑 [ d p i , j − 1 , i − 1 ] [dp_{i,j-1},i-1] [dpi,j1,i1] 这一部分的贡献,即 s u m i , j − 1 sum_{i,j-1} sumi,j1
  • 再考虑 [ d p i , j , d p i , j − 1 − 1 ] [dp_{i,j},dp_{i,j-1}-1] [dpi,j,dpi,j11] 这一部分的贡献,我们已知从 d p i , j − 1 dp_{i,j-1} dpi,j1 这个点走到这些点的距离,即为 s u m d p i , j − 1 , j − 1 sum_{dp_{i,j-1},j-1} sumdpi,j1,j1,所以只需要从 i i i 2 j − 1 2^{j-1} 2j1 d p i , j − 1 dp_{i,j-1} dpi,j1,一共要走 d p i , j − 1 − d p i , j dp_{i,j-1}-dp_{i,j} dpi,j1dpi,j 次。

总结一下有:
s u m i , j = s u m i , j − 1 + s u m d p i , j − 1 , j − 1 + 2 j − 1 × ( d p i , j − 1 − d p i , j ) sum_{i,j}=sum_{i,j-1}+sum_{dp_{i,j-1},j-1}+2^{j-1}\times(dp_{i,j-1}-dp_{i,j}) sumi,j=sumi,j1+sumdpi,j1,j1+2j1×(dpi,j1dpi,j)

注意,在上述推导过程中,我们并未考虑从起点往右跳的贡献,这一部分会在后文详细阐述。

求得 d p , s u m dp,sum dp,sum 以后,即可处理询问:

在这里插入图片描述
该函数旨在求出求出 x x x 走到 [ l p o s , x − 1 ] [lpos, x-1] [lpos,x1] 的距离和。

有注释的部分应该很好理解,瓶颈在于框出的两行代码,网上大多数题解这一部分都没有写明白。

为什么要强制走到 l x l_x lx 呢?注意到,我们之前 s u m sum sum 的推导并没有考虑 x x x 最开始往右跳的贡献,所以我们要强制选择一个点,那么为什么选择了 l i l_i li 呢?

  • 如果 x x x 最开始向右跳到 y y y 更优,那么从 l x l_x lx 出发可以且一定会跳到 y y y,就等价于 x x x 跳到 y y y,区别在于 l x l_x lx 出发可以顺便处理这一部分的贡献。
  • 如果 x x x 最开始向左跳到 x x x 更优,同理,从 l x l_x lx 出发也一定会跳到 y y y

通俗的讲,先强制选择 l x l_x lx 其作用就在于等着被替代成其他点。表面上从 x x x 跳到了 l x l_x lx,实际是从 x x x 跳到了 y y y
x → l x → y x \rightarrow l_x \rightarrow y xlxy 等价于 x → y x \rightarrow y xy,但前者可以处理向右跳的贡献。

结合样例可以更好理解。
在这里插入图片描述

如果 6 6 6 为起点,那么我们会强制跳到 4 4 4,表面上错过了可以直达 1 1 1 5 5 5,但是 4 4 4 是可以且一定会向右跳到 5 5 5 的,因为我们的倍增是最优的。

代码

一道非常好的倍增题!!!

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 3e5 + 5;
int n , q , dp[MAXN][20] , sum[MAXN][20] , l[MAXN];
int solve(int x , int lpos) { // 求出 x 走到 [lpos , x-1] 的距离和
	if (l[x] <= lpos) return x - lpos; // 特判一步走到的情况
	int ans = x - l[x] , step = 1;
	x = l[x];
	for (int i = 19 ; i >= 0 ; i --) { 
		if (dp[x][i] >= lpos) {// 只要还未到达终点,就不断往前倍增跳
			ans += (x - dp[x][i]) * step + sum[x][i]; // 和 sum 的推导类似
			x = dp[x][i] , step += (1 << i); // step 表示当前走过的步数
		}
	}
	step ++;
	if (x > lpos) ans += (x - lpos) * step; // 处理不能恰好到达 x 的情况
	return ans;	
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	cin >> n;
	for (int i = 2 ; i <= n ; i ++) cin >> l[i] , dp[i][0] = l[i];
	for (int i = n - 1 ; i >= 2 ; i --) dp[i][0] = min(dp[i + 1][0] , dp[i][0]);
	for (int i = 1 ; i <= n ; i ++) sum[i][0] = i - dp[i][0];
	for (int j = 1 ; j <= 19 ; j ++) {
		for (int i = 2 ; i <= n ; i ++) {
			dp[i][j] = dp[dp[i][j - 1]][j - 1];
			sum[i][j] = sum[i][j - 1] + sum[dp[i][j - 1]][j - 1] + (dp[i][j - 1] - dp[i][j]) * (1 << j - 1);
		}
	}
	cin >> q;
	while(q --) {
		int ql , qr , x;
		cin >> ql >> qr >> x;
		int div = (qr - ql + 1) , w = solve(x , ql) - solve(x , qr + 1);
		int g = __gcd(div , w);
		w /= g , div /= g; // 约分
		cout << w << '/' << div << '\n';
	}
    return 0;
}
  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
星际穿越》是一部由克里斯托弗·诺兰执导的科幻电影。在这部电影中,地球面临着严重的食物危机和可持续性问题,登陆者接受了一个任务,穿越黑洞寻找适合人类居住的星球。 如果我要用Scratch来描述《星际穿越》这部电影,我会创建一个交互式作品,展示故事的关键场景和情节。首先,我会用Scratch制作一个地球模型,通过展示不断扩大的饥荒和干旱来说明人类面临的食物危机。我还会制作一个角色,代表主人公康威博士,他将带领一队探险家前往未知的星球。 接下来,我会创建一个黑洞的动画,利用Scratch的特效功能来呈现一个漩涡状的黑洞。通过展示人们如何拟合此黑洞,我可以说明故事中所描述的尝试进入黑洞的危险任务。 我还可以用Scratch制作一个飞船模型,描述康威博士和他的队员如何穿越黑洞。通过展示飞船经过曲线轨道、时间变慢等特效,我可以引导观众进入导演所描述的奇幻航行。 最后,我会创造一个新星球的场景,让康威博士和他的队员登陆到一个未知的星球上。我会利用Scratch的绘图功能,绘制一个美丽的星球,并展示队员在这个星球上的探索和挑战。 通过使用Scratch创作《星际穿越》的交互式作品,观众可以更好地理解和感受这部电影所传递的科幻故事。同时,这也是一个很好的机会,来锻炼自己的创造力和Scratch编程能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值