资讯详情

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

题目大意:

给定 n n n个点,从 1 1 1走到 n n n,其中有 m m m第一条路径 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∗(pi​−qj​)2+B∗(pi​−qj​)+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]+Aqj2​−Bqj​=2Api​qj​+dp[i]−Api2​−Bpi2​−C
  • 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]+Aqj2​−Bqj​,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 qj​≤pi​
  • 第一个好办,我们开 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 qj​≤pi​都会进入单调队列中,而所有 q j ≤ p i q_j \le p_i qj​≤pi​的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;
}

标签: p20k3aqj圆形连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台