CF 1366F Jog Around The Graph(贪心,DP)

本文探讨了一种解决特定无向图问题的算法,旨在计算从起点出发,经过指定数量的边,所能达到的最远距离。算法核心分为两部分:首先使用动态规划求解从起点到各点的最长路径;其次,通过比较不同边上的路径长度,确定最优解。文章详细解释了算法的实现过程,包括如何处理大规模数据,以及如何通过数学方法简化计算。
摘要由CSDN通过智能技术生成

题目描述

题目链接
  题目大意是给你一个n个点m条边的无向图,从一号点出发,走i条边,找出满足这个条件的路径的最长的长度,记为 l i l_i li。然后求出 l 1 , l 2 , ⋯   , l p l_1,l_2,\cdots,l_p l1,l2,,lp之和,答案对 1 0 9 + 7 10^9 + 7 109+7取模。注意每条边都可以重复走。

数据范围

2 ≤ n ≤ 2000 2 \leq n\leq 2000 2n2000
n − 1 ≤ m ≤ 2000 n - 1 \leq m \leq 2000 n1m2000
m ≤ q ≤ 1 0 9 m\leq q \leq 10^9 mq109

思路

  通过简单的模拟可以看出,每一条最长路都应有两部分组成。第一部分是从1号点走过 k k k条边到达某条边 e e e的任意一个端点的最长路径,第二部分是在这条边 e e e上来回走 i − k i-k ik次。
  用数学语言来描述这段路径的长度 S ( e , i , k ) S_{(e, i, k)} S(e,i,k):(设这条边的边权为 w e w_e we它的两个端点为 u u u v v v,用d来表示到某个点的最长距离) S ( e , i , k ) = max ⁡ ( d u , d v ) + ( i − k ) w e S_{(e,i, k)} = \max(d_u, d_v)+(i - k)w_e S(e,i,k)=max(du,dv)+(ik)we 当然 S ( e , i ) S_{(e,i)} S(e,i)要在所有的 k k k中取一个最长的, 即: S ( e , i ) = max ⁡ 1 ≤ k ≤ n − 1 S ( e , i , k ) S_{(e,i)} = \max_{1\leq k \leq n - 1} S_{(e,i, k)} S(e,i)=1kn1maxS(e,i,k)
  至于为什么是 1 ≤ k ≤ n − 1 1\leq k \leq n - 1 1kn1可以想象一下,从一号点出发走不超过 n − 1 n-1 n1条边就可以到达图中任何一个点,当然走的边不能重复,因为要保证尽可能多的在边e上重复走。
  这样对于每一条重复走的边e我们都能求出来对应的 S e ( i ) S_e(i) Se(i)的函数表达式,并且可以发现S与i之间是线性关系。如图所示,不同的边对应的 S e ( i ) S_e(i) Se(i)会在不同的i值区间处于领先状态,最后构成图中红色的折线,把折线上的点的函数值累加起来就是答案了。
在这里插入图片描述

实现

d d d
  相当于求最长路用dp,注意这里要保证严格走i步,所以要加上一维,dp[i][j]表示走了i条边到达点j的最长路,当然可以用滚动数组优化掉i的那一维。这里给的代码用的遍历图的方法类似于Bellman-Ford,好处是不用邻接表存图。
S e ( i ) S_e(i) Se(i)
  由于题目中i的数据范围是很大的,所以在时间和空间上都不允许将每一个 S ( e , i , k ) S_{(e,i,k)} S(e,i,k)计算出来。我们可以把它换一个写法:
S ( e , i , k ) = ( max ⁡ ( d u , d v ) − k w e ) + i w e S_{(e, i,k)} =(\max(d_u, d_v) - kw_e) + iw_e S(e,i,k)=(max(du,dv)kwe)+iwe括号里面的这一项和i无关是可以被计算出来的,用 b ( e , k ) b_{(e,k)} b(e,k)表示:
b ( e , k ) = max ⁡ ( d u , d v ) − k w e b_{(e,k)} = \max(d_u, d_v) - kw_e b(e,k)=max(du,dv)kwe
再令:
b e = max ⁡ 1 ≤ k ≤ n − 1 b ( e , k ) b_e = \max_{1\leq k \leq n - 1} b_{(e,k)} be=1kn1maxb(e,k)
注意到这里的 b e b_e be就是 S e ( i ) S_e(i) Se(i)的截距, w e w_e we则是斜率:
S e ( i ) = w e i + b e S_e(i) = w_e i + b_e Se(i)=wei+be
答案统计:
  首先要确定每一个S_e是在哪一段上领先。方法是通过和其他边对应的 S S S求交点不断缩小上下界。例如对于两个边 i , j i,j i,j: S i ( x ) = w i x + b i S_i(x) = w_ix+b_i Si(x)=wix+bi S j ( x ) = w j x + b j S_j(x) = w_jx + b_j Sj(x)=wjx+bj S i ( x ) > S j ( x ) S_i(x) > S_j(x) Si(x)>Sj(x)
可得:当 w i > w j w_i > w_j wi>wj时得到一个下限 x > b j − b i w i − w j x>\frac{b_j - b_i}{w_i - w_j} x>wiwjbjbi w i < w j w_i < w_j wi<wj 时得到一个上限 x < b j − b i w i − w j x<\frac{b_j - b_i}{w_i - w_j} x<wiwjbjbi
相等的时候需要特殊考虑。
  得到区间l到r后,用等差数列求和公式计算答案贡献:
∑ i = l r ( w e i + b e ) = ( l + r ) ( r − l + 1 ) 2 w e + ( r − l + 1 ) b e \sum_{i=l}^{r}(w_ei+b_e) = \frac{(l + r)(r - l + 1)}{2}w_e + (r - l + 1)b_e i=lr(wei+be)=2(l+r)(rl+1)we+(rl+1)be
具体细节参照代码:

#include <bits/stdc++.h>

using namespace std;
const int MAXN = 2e3 + 5;
const int MAXM = 2e3 + 5;
const int INF = 1e9;
const int Mod = 1e9 + 7;

int u[MAXM], v[MAXM], w[MAXM], n, m, q;
long long dp[2][MAXN], b[MAXN], ans;
signed main() {
	scanf("%d%d%d", &n, &m, &q);
	
	for (int i = 1; i <= m; i++) {
		scanf("%d%d%d", u + i, v + i, w + i);
	}
	
	for (int i = 1; i <= n; i++) dp[0][i] = dp[1][i] = -INF;
	for (int i = 1; i <= m; i++) b[i] = -INF; 
	dp[0][1] = 0;
	//这里枚举的i其实就是上文提到的k 
	for (int i = 1; i < n; i++) {
		for (int j = 1; j <= m; j++) {	
			dp[i & 1][v[j]] = max(dp[i & 1][v[j]], dp[i - 1 & 1][u[j]] + w[j]);
			dp[i & 1][u[j]] = max(dp[i & 1][u[j]], dp[i - 1 & 1][v[j]] + w[j]);
		}

		for (int j = 1; j <= m; j++) 
			b[j] = max(b[j], max(dp[i & 1][u[j]], dp[i & 1][v[j]]) - i * w[j]);
		ans = (ans + *max_element(dp[i & 1] + 1, dp[i & 1] + n + 1)) % Mod;
	} 
	
	for (int i = 1; i <= m; i++) {
		long long l = n, r = q;
		for (int j = 1; l <= r && j <= m; j++) if (i != j) {
			
			long long k = w[i] - w[j];
			long long db = b[j] - b[i];
			
			if (k > 0) 
				//加一是为了防止重复计算 
				l = max(l, db / k + 1);   
			if (k < 0) 
				r = min(r, db / k);
			if (k == 0) {
				//如果wi = wj并且bj < bi 时Si(x)的图像在Sj(x)图像的上面,上下限不做变动
				//如果wi = wj并且bj = bi 说明这两个图像一样,如果j>i说明这部分答案还没算在内,需要算入答案。 
				//如果上面两种情况都不满足,说明这种情况不是答案的一部分直接退出。 
				if (!(db < 0 || (db == 0 && j > i))) 
					r = -1;
			}
			
		}
		if (l <= r) 
		//统计区间和需要等差数列求和公式 
			ans = (ans + (l + r) * (r - l + 1) / 2 % Mod * w[i] % Mod + (b[i] % Mod + Mod) % Mod * (r - l + 1) % Mod) % Mod; 
	}
	printf("%lld\n", ans);
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值