《关于我们WA题太多导致皮卡丘看不下去把妙蛙种子电死|

superPG

园龄:5年1个月粉丝:2关注:10

2021-07-09 20:44阅读: 47评论: 0推荐: 0

斜率优化DP

斜率优化DP

类似于:F [ i ] = min[ L(i) <= j <= R(i)] {F[ j ] + val( i, j)},其中,多项式val(i , j )的每一项仅与 i 和 j 中的一个有关。这种情况的时候,我们一般采用 单调队列优化。如果 多项式 val(i , j )包含 i, j 的乘积项,即存在一个同时与 i 和 j 有关的部分时,应该采用 斜率优化

300. 任务安排1 - AcWing题库

状态表示: f[ i ] [ j ] 表示前 i 个任务,分成 j 批来完成的最小花费。 属性 :最小值。

状态计算:从题干中得知,任务分批完成,且花费的计算是按照 整批任务全部完成的时间 来计算,容易想到,处理时分批计算。

​ F [ i ] [ j ] = min (0 <= k < i) { F[k] [ j - 1 ] + (S * j + sumT[ i ]) * ( sumC[ i ] - sumC[ k ]) }

这样的解法时间复杂度为 O(N ^3)。

事实上,我们不容易直接得知在此之前及其启动过几次。但我们指导,机器因执行这批任务而花费的启动时间S,会累加到在此之后所有任务的完成时刻上。所以,如果把 分成的批次 j 这一维度优化掉,设 F [ i ] 为前 i 个任务分成若干批执行的最小费用,状态转移方程也容易得出:

​ F [ i ] = min ( 0 <= j < i ) { F[ j ] + sumT[ i ] * (sumC[ i ] - sumC[ j ]) + S * (sumC [ N ] - sumC [ j ] )}

我个人认为,这一维度的优化,是因为 在进行状态计算的过程中,都是以最后一个状态 选 or 不选 来进行转移的。也就是说,是否知道之前已经进行过的轮数是不必要的,属于冗余运算。所以可以优化掉这一维度的时间复杂度。

这样计算,我们没有直接求出每批任务的完成时刻,而是在一批任务”开始“对后继任务产生的影响是,就先把费用累加到答案中。这就是” 费用提前计算 “的思想。

经过优化后,时间复杂度降低为 O(n^2)。

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 5e3+10;
LL f[N],sumt[N],sumc[N];
int n,s;

int main()
{
	scanf("%d %d",&n,&s);
	for(int i=1;i<=n;i++)
	{
		int t,c;
		scanf("%d %d",&t,&c);
		sumt[i] = sumt[i-1] + t;
		sumc[i] = sumc[i-1] + c;	
	} 
	memset(f,0x3f,sizeof f);
	f[0] = 0;
	for(int i=1;i<=n;i++)
		for(int j=0;j<i;j++)
		{
			f[i] = min(f[i], f[j] + sumt[i]*(sumc[i] - sumc[j]) + s*(sumc[n] - sumc[j]));
		}
	
	cout<<f[n]<<endl;
	
	return 0;
}

301. 任务安排2 - AcWing题库

寄了,还好之前学过计算几何,要不真听不懂。细节方面还是lyd的蓝书和Y总的视频。(这节蓝书竟然能看懂,雷姆~)

AcWing 301. 任务安排2(算法提高课) - AcWing

大体的思想就是:把状态计算的式子去min,然后进行线性规划,把含 j 的两个式子分别当作 x 和 y,然后对 所有的 j (现在对应到图中的所有点)看哪一个点能取到最小的截距(因为F[ i ]是截距中的唯一变量,且符号为正,截距最小就是F[ i ]最小)。

然后就是单调队列 + 维护下凸包。

具体内容从蓝书P323开始,这里先记录以下算法过程:

  1. 检查队头的两个决策变量 q[ l ] 和 q[ l + 1] ,若斜率 (F[q[ l + 1]] - F[ q[ l ]]) / (sumC[q[ l + 1]] - sumC[q[ l ]]) <= S + sumT[ i ],则把q[ l ] 出队,继续检查新的队头。
  2. 直接取队头 j = q[ l ] 为最优决策,执行状态转移,计算出 F [ i ]。
  3. 把新决策 i 从队尾插入,在插入之前,若三个决策点 j1 = q[r-1], j2 = q[ r ], j3 = i 不满足斜率单调递增,(即不满足下凸性,j2就是无用决策),则直接从队尾把q[ r ]出队,继续检查心的队尾。

image-20210709153749627

image-20210709160515470

#include<bits/stdc++.h>
using namespace std;

const int N = 3e5+10;

typedef long long LL;

int n,s;
LL c[N], t[N];
LL f[N];
int q[N];

int main()
{
    scanf("%d %d",&n,&s);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld %lld",&t[i],&c[i]);
        t[i] += t[i-1];
        c[i] += c[i-1];
    }
    
    int hh = 0, tt = 0;
    q[0] = 0;
    
    for(int i=1;i<=n;i++)
    {
        while(hh < tt && (f[q[hh+1]] - f[q[hh]]) <= (c[q[hh+1]] - c[q[hh]]) * (s + t[i]) ) hh++;
        int j = q[hh];
        f[i] = f[j] - (t[i] + s) * c[j] + t[i] * c[i] + s * c[n];
        while(hh < tt && (__int128)(f[q[tt]] - f[q[tt-1]])*(c[i] - c[q[tt - 1]]) >= (__int128)(f[i] - f[q[tt - 1]]) * (c[q[tt]] - c[q[tt - 1]])) tt--;
        q[++tt] = i;
    }
    
    printf("%lld\n",f[n]);
    
    return 0;
}

302. 任务安排3 - AcWing题库

执行时间T可能是符数,所以sumT不具有单调性,即我们的直线斜率不具有单调性,但是凸包还是有单调性的。我们就不能O(1)的取队头元素作为最优解,而是应该在整个队列中二分当前直线斜率,即 S + sumT[ i ] ,的位置。

image-20210709164215831

image-20210709165351967

#include<bits/stdc++.h>
using namespace std;

const int N = 3e5+10;

typedef long long LL;

int n,s;
LL c[N], t[N];
LL f[N];
int q[N];

int main()
{
    scanf("%d %d",&n,&s);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld %lld",&t[i],&c[i]);
        t[i] += t[i-1];
        c[i] += c[i-1];
    }
    
    int hh = 0, tt = 0;
    q[0] = 0;
    
    for(int i=1;i<=n;i++)
    {
        int l =hh, r = tt;
        while(l < r)
        {
            int mid = (l + r) >> 1;
            if(f[q[mid+1]] - f[q[mid]] > (c[q[mid+1]] - c[q[mid]]) * (s + t[i])) r = mid;     //check函数:如果某点的斜率比当前点大, 则返回true,否则返回false
            else l = mid + 1;
        }

        int j = q[l];
        f[i] = f[j] - (t[i] + s) * c[j] + t[i] * c[i] + s * c[n];
        while(hh < tt && (__int128)(f[q[tt]] - f[q[tt-1]])*(c[i] - c[q[tt - 1]]) >= (__int128)(f[i] - f[q[tt - 1]]) * (c[q[tt]] - c[q[tt - 1]])) tt--;
        q[++tt] = i;
    }
    
    printf("%lld\n",f[n]);
    
    return 0;
}

303. 运输小猫 - AcWing题库

  1. 分析问题:差分、前缀和、分析题目性质,看是否需要排序来消除后效性。
  2. 确定状态:一般来说是按经验。(😓)
  3. 状态计算:根据确定状态的最后一个,选,还是不选,或是选几个,来分割状态,进行状态转移。并写出状态转移方程。
  4. DP优化:看状态转移方程是否符合 F [ i ] = min[ L(i) <= j <= R(i)] {F[ j ] + val( i, j)} ,并根据多项式 val( i , j ) 的特性(是只含i / j 还是有i , j 的乘积)来确定采用 单调队列 / 斜率 优化的方式。
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e5+10, M = 1e5+10, P = 110;

int n, m, p;
LL d[N], t[N], a[N], s[N];
LL f[P][M];
int q[M];

LL get_y(int k, int j)
{
    return f[j - 1][k] + s[k];
}

int main()
{
    scanf("%d %d %d",&n,&m,&p);
    
    for(int i=2;i<=n;i++)
    {
        scanf("%lld",&d[i]);
        d[i] += d[i-1];
    }
    
    for(int i=1;i<=m;i++)
    {
        int h;
        scanf("%d %lld",&h, &t[i]);
        a[i] = t[i] - d[h];
    }
    
    sort(a+1,a+1+m);
    
    for(int i=1;i<=m;i++) s[i] = s[i-1] + a[i];
    
    memset(f,0x3f,sizeof f);
    for(int i=0;i<=p;i++) f[i][0] = 0;
    
    for(int j=1;j<=p;j++)
    {
        int hh = 0, tt = 0;
        q[0] = 0;
        
        for(int i=1;i<=m;i++)
        {
            while(hh < tt && (get_y(q[hh+1],j) - get_y(q[hh],j)) <= a[i] * (q[hh + 1] - q[hh])) hh++;
            int k = q[hh];
            f[j][i] = f[j - 1][k] - a[i] * k + s[k] + a[i] * i - s[i];
            while (hh < tt && (get_y(q[tt], j) - get_y(q[tt - 1], j)) * (i - q[tt]) >=
                (get_y(i, j) - get_y(q[tt], j)) * (q[tt] - q[tt - 1])) tt -- ;
            q[ ++ tt] = i;
        }
    
    }
    
    printf("%lld\n",f[p][m]);
    
    return 0;
}

本文作者:superPG

本文链接:https://www.cnblogs.com/superPG/p/14992420.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   superPG  阅读(47)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 いつも何度でも 伊藤サチコ
いつも何度でも - 伊藤サチコ
00:00 / 00:00
An audio error has occurred.

呼んでいる 胸のどこか奥で

いつも心踊る 夢を見たい

かなしみは 数えきれないけれど

その向こうできっと あなたに会える

繰り返すあやまちの そのたび ひとは

繰り返すあやまちの そのたび ひとは

ただ青い空の 青さを知る

果てしなく 道は続いて見えるけれど

この両手は 光を抱ける

さよならのときの 静かな胸

さよならのときの 静かな胸

ゼロになるからだが 耳をすませる

生きている不思議 死んでいく不思議

花も風も街も みんなおなじ

nananan lalala lululu

nananan lalala lululu

呼んでいる 胸のどこか奥で

いつも何度でも 夢を描こう

かなしみの数を 言い尽くすより

同じくちびるで そっとうたおう

閉じていく思い出の そのなかにいつも

閉じていく思い出の そのなかにいつも

忘れたくない ささやきを聞く

こなごなに砕かれた 鏡の上にも

新しい景色が 映される

はじまりの朝の静かな窓

はじまりの朝の静かな窓

ゼロになるからだ 充たされてゆけ

海の彼方には もう探さない

輝くものは いつもここに

わたしのなかに 見つけられたから

nananan lalala lululu

nananan lalala lululu