P6302 [NOI2019] 回家路线 加强版(斜率优化dp)

44 篇文章 0 订阅
41 篇文章 1 订阅
题目大意:

给定 n n n个点,从 1 1 1走到 n n n,其中有 m m m条路径,第i条路径从 x i x_i xi y i y_i yi,时间从 p i p_i pi q i q_i qi,每次等车t的时间会增加 A t 2 + B t + C At^2+Bt+C At2+Bt+C的花费,最后花费加上到达时间即为最终花费,使最终花费最小。

解题思路:

可能可以实现但太麻烦了的想法:

  • d p [ i ] [ j ] dp[i][j] dp[i][j]为到达第i个点j时刻的最小花费,然后 d p dp dp方程自然而然可以通过相连的边来转移,但是因为这样子空间时间都超了时间可以用斜率优化,而空间则可以发现每个点仅仅只有那些入边的到达时间才会以此为基础,转移到出边的到达时间,所以可以对每个点分别 n e w new new一个大小为入边个数的空间来转移,空间大小为 O ( n + m ) O(n+m) O(n+m)

容易实现的想法:

  • d p [ i ] dp[i] dp[i]为走到 i i i路径的最小总花费,那么 d p [ i ] = m i n ( d p [ i ] , d p [ j ] + A ∗ ( p i − q j ) 2 + B ∗ ( p i − q j ) + C ) dp[i]=min(dp[i],dp[j]+A*(p_i-q_j)^2+B*(p_i-q_j)+C) dp[i]=min(dp[i],dp[j]+A(piqj)2+B(piqj)+C)
  • 可将其转换为: d p [ j ] + A q j 2 − B q j = 2 A p i q j + d p [ i ] − A p i 2 − B p i 2 − C dp[j]+Aq_j^2-Bq_j=2Ap_iq_j+dp[i]-Ap_i^2-Bp_i^2-C dp[j]+Aqj2Bqj=2Apiqj+dp[i]Api2Bpi2C
  • x = q j , y = d p [ j ] + A q j 2 − B q j , k = 2 A p i x=q_j,y=dp[j]+Aq_j^2-Bq_j,k=2Ap_i x=qj,y=dp[j]+Aqj2Bqj,k=2Api,求截距最小即是斜率优化的内容
  • 把m条路径按照 p p p从小到大处理, k k k单调不减,即可进行斜率优化(剩下这段话来自Mentos_Cola)
  • 但是还有两个限制,一个是 y j = x i y_j=x_i yj=xi,另一个是 q j ≤ p i q_j\le p_i qjpi
  • 第一个好办,我们开 n n n个单调队列,i进队时就丢进第 y i y_i yi个单调队列里,求 d p [ i ] dp[i] dp[i]时就从第 x i x_i xi个单调队列里调就完了。
  • 第二个则需要我们算出 d p [ i ] dp[i] dp[i]后不将它马上插到对应的单调队列里,而是按照 q q q把它丢到桶里。对每个 d d d算所有 p i = d p_i=d pi=d d p [ i ] dp[i] dp[i]之前,就把所有 q j = d q_j=d qj=d j j j插入对应单调队列
  • 可行的原因是因为,在计算 d p [ i ] dp[i] dp[i]时,桶里面所有 q j ≤ p i q_j\le p_i qjpi都会进入单调队列中,而所有 q j ≤ p i q_j \le p_i qjpi的j必然 p j < p i p_j < p_i pj<pi,他们一定之前就会进入桶中,所以就不会有任何遗漏
AC代码:
#include <bits/stdc++.h>
#define ft first
#define sd second
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define seteps(N) fixed << setprecision(N)
#define endl "\n"
const int maxn = 1e5 + 10;
const int maxm = 1e6 + 10;
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<ll, ll> pll;
const ll mod = 1e9 + 7;
int n, m;
ll ans = -1;
ll a, b, c, x[maxm], y[maxm], p[maxm], q[maxm], dp[maxm];
int h[maxn], t[maxn];
//因为不可能开maxn*maxm的队列,所以开vector,这样动态开出的空间为maxn+maxm
vector <int> ton[maxn], g[maxn], dq[maxn]; 
pll operator - (pll p1, pll p2) {return {p1.ft - p2.ft, p1.sd - p2.sd};}
ll operator ^ (pll p1, pll p2) {return p1.ft * p2.sd - p2.ft * p1.sd;}
ll sqr(ll x) {return x * x;}
bool cal(int i, int j, int k) {
    pll pi = {q[i], dp[i] + a * sqr(q[i]) - b * q[i]}, pj = {q[j], dp[j] + a * sqr(q[j]) - b * q[j]}, pk = {q[k], dp[k] + a * sqr(q[k]) - b * q[k]};
    return ((pj - pi) ^ (pk - pi)) <= 0; //存储的是一个下凸包,这里要等于不然会有错误
}
bool cmp(int i, int j, int k) {
    return ((dp[i] + a * sqr(q[i]) - b * q[i]) - (dp[j] + a * sqr(q[j]) - b * q[j])) < k * (q[i] - q[j]);
}
int main() {
    // freopen("data.txt", "r", stdin);
    ll mx = 0;
    cin >> n >> m >> a >> b >> c;
    for (int i = 1; i <= n; i++) h[i] = 0, t[i] = -1;
    for (int i = 1; i <= m; i++) {
        cin >> x[i] >> y[i] >> p[i] >> q[i];
        mx = max(mx, q[i]); //最大时间
        g[p[i]].push_back(i);
    }
    for (int ti = 0; ti <= mx; ti++) {//时间从小到大枚举,即相当于枚举从小到大枚举pi了
        for (auto np : ton[ti]) {//先将该时间内的dp值存进相应队列
            int f = y[np];
            while (h[f] < t[f] && cal(dq[f][t[f] - 1], dq[f][t[f]], np)) 
                t[f]--, dq[f].pop_back();
            dq[f].push_back(np);
            t[f]++;
        }
        for (auto np : g[ti]) {
            int f = x[np], k = 2 * a * p[np];
            while (h[f] < t[f] && cmp(dq[f][h[f] + 1], dq[f][h[f]], k)) 
                h[f]++;
            if (h[f] > t[f] && x[np] != 1) continue; //因为起点为1的话,队列里必然是空的,因为根据转移方程不会有这样的边,所以就从0转移
            int g;
            if (h[f] > t[f] && x[np] == 1) g = 0;
            else g = dq[f][h[f]];
            dp[np] = dp[g] + a * sqr(p[np] - q[g]) + b * (p[np] - q[g]) + c;
            if (y[np] == n) { //统计答案
                if (ans == -1) ans = dp[np] + q[np];
                else ans = min(ans, dp[np] + q[np]);
            }
            ton[q[np]].push_back(np);
        }
    }
    // for (int i = 1; i <= m; i++)
    //     cout << dp[i] << " \n"[i == m];
    cout << ans << endl;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
P2375 [NOI2014] 动物园是一道经典的动态规划题目,以下是该题的详细题意和解题思路。 【题意描述】 有两个长度为 $n$ 的整数序列 $a$ 和 $b$,你需要从这两个序列中各选出一些数,使得这些数构成一个新的序列 $c$。其中,$c$ 序列中的元素必须在原序列中严格递增。每个元素都有一个价值,你的任务是选出的元素的总价值最大。 【解题思路】 这是一道经典的动态规划题目,可以采用记忆化搜索的方法解决,也可以采用递推的方法解决。 记忆化搜索的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int dfs(int x, int y) { if (dp[x][y] != -1) return dp[x][y]; if (x == n || y == n) return 0; int res = max(dfs(x + 1, y), dfs(x + 1, y + 1)); if (a[x] > b[y]) { res = max(res, dfs(x, y + 1) + b[y]); } return dp[x][y] = res; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); memset(dp, -1, sizeof(dp)); printf("%d\n", dfs(0, 0)); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值,-1表示未计算过。dfs(x,y)表示选到a数组中第x个元素和b数组中第y个元素时的最大价值,如果dp[x][y]已经计算过,则直接返回dp[x][y]的值。如果x==n或者y==n,表示已经遍历完一个数组,直接返回0。然后就是状态转移方程了,如果a[x] > b[y],则可以尝试选b[y],递归调用dfs(x, y+1)计算以后的最大价值。否则,只能继续遍历数组a,递归调用dfs(x+1, y)计算最大价值。最后,返回dp[0][0]的值即可。 递推的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); for (int i = n - 1; i >= 0; i--) { for (int j = n - 1; j >= 0; j--) { dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]); if (a[i] > b[j]) { dp[i][j] = max(dp[i][j], dp[i][j + 1] + b[j]); } } } printf("%d\n", dp[0][0]); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值。从后往前遍历数组a和数组b,依次计算dp[i][j]的值。状态转移方程和记忆化搜索的方法是一样的。 【参考链接】 P2375 [NOI2014] 动物园:https://www.luogu.com.cn/problem/P2375

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值