• P2365 任务安排 斜率优化dp入门(分组dp)


    P2365 任务安排 斜率优化入门

    题意

    给出n个任务,必须按顺序完成,每个任务都有一个需要的时间和代价系数。可以把任务分批(就是把原序列分成多段),每一批内部的任务同样也是按顺序完成,但是最后计算代价的时候是该批中最后一个任务完成得时间*批中每一个任务得代价系数。同时,分批有一个代价时间S,表示启动机器的时间,也就是每分一批要时间S。

    思路

    刚开始没看懂题,感觉全部弄到一批就行了,其实不对,例如有两个任务,第一个时间为1,第二个时间为100000,代价均为一个比较大的值,S为1,那么肯定第一和第二分开代价比较小。
    n<=5000 考虑一下怎么dp
    基本套路是前i个分成j个的代价,这样的复杂度是((n^3))肯定是过不了的
    造成这个代价的最主要原因是记录了分成几组,如果不记录会造成后效性。实际上,我们可以考虑,假设分组增加了1,那么造成的代价是可以O(1)计算出来的,例如 设j<=i 如果我们将j---i分成一组,那么也就相当于 j----n的时间都要增加S,也就是增加了代价s*(sum[n]-sum[j-1]),这样就能避免后效性了,相当于我可以提前计算分组造成的代价,这个代价是分组必然造成的,而对后面的任务没有影响,假如在这个分组后又有分组,也是可以提前计算,这样就可以把复杂度优化到(O(n^2))
    我们设(dp[i])表示1--i都完成了需要的最小代价,那么转移方程为
    (dp[i]=min(dp[j-1]+sumt[i]*(sumf[i]-sumf[j-1])+s*(sumf[n]-sumf[j-1])))

    其实这题还可以更优化,我们把上述转移方程拿出来
    (dp[i]=dp[j-1]+sumt[i]*(sumf[i]-sumf[j-1])+s*(sumf[n]-sumf[j-1]))

    移动一下
    (dp[i]=dp[j-1]+sumt[i]*(sumf[i]-sumf[j-1])+s*(sumf[n]-sumf[j-1]))

    (dp[j-1]=dp[i]-sumt[i]*(sumf[i]-sumf[j-1])-s*(sumf[n]-sumf[j-1]))
    化简一下
    (dp[j-1]=(s+sumt[i])*sumf[j-1]+dp[i]-sumt[i]*sumf[i]-s*sumf[n])
    把dp[j-1]当成因变量,sumf[j-1]看作自变量,那么截距就是(dp[i]-sumt[i]*sumf[i])要使dp[i]最小也就是使截距最小

    可以看出斜率k=(s+sumt[i])是单调递增的
    找到dp[i]的最大值就当于把斜率为k的直线向上平移,在碰到的第一个(dp[j-1],sumt[j-1])点取得最小值。

    什么时候能碰到,假设碰到的点为x1 那么设(x1,x0)斜率为k0和(x1,x2)斜率为k1 那么取得最小值的斜率关系有(k0=<k=<k1)所以只要维护一个下凸壳就行,每次取能取第一个大于斜率(s+sumf[i])点(当点不足得时候只能取那一个点) ,并且维护一个下凸壳即可

    (n^2)代码

    #include<bits/stdc++.h>
    using namespace std;
    #define pb push_back
    #define F first
    #define S second
    #define mkp make_pair
    #define pii pair<int,int>
    typedef long long ll;
    const int inf=0x3f3f3f3f;
    const int maxn=1e6+10;
    const int mod=1e9+7;
    int dp[maxn],sumt[maxn],sumf[maxn];
    int main(){
    	int n,s;
    	scanf("%d%d",&n,&s);
    	for(int i=1;i<=n;i++){
    		int x,y;scanf("%d%d",&x,&y);
    		sumt[i]=sumt[i-1]+x;
    		sumf[i]=sumf[i-1]+y;
    	}
    	for(int i=1;i<=n;i++)dp[i]=inf;
    	dp[0]=0;
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=i;j++){
    			dp[i]=min(dp[i],dp[j-1]+sumt[i]*(sumf[i]-sumf[j-1])+s*(sumf[n]-sumf[j-1]));
    		}
    	}
    	cout<<dp[n];
    	return 0;
    }
    

    斜率优化代码

    
    #include<bits/stdc++.h>
    using namespace std;
    #define pb push_back
    #define F first
    #define S second
    #define mkp make_pair
    #define pii pair<int,int>
    typedef long long ll;
    const int inf=0x3f3f3f3f;
    const int maxn=1e6+10;
    const int mod=1e9+7;
    int dp[maxn],sumt[maxn],sumf[maxn],q[maxn];
    int main(){
    	int n,s;
    	scanf("%d%d",&n,&s);
    	for(int i=1;i<=n;i++){
    		int x,y;scanf("%d%d",&x,&y);
    		sumt[i]=sumt[i-1]+x;
    		sumf[i]=sumf[i-1]+y;
    	}
    	for(int i=1;i<=n;i++)dp[i]=inf;
    	dp[0]=0;
    	int l,r=1;
    	r=l=1;
    	for(int i=1;i<=n;i++){
    		while(l<r&&(dp[q[l+1]]-dp[q[l]])<=(s+sumt[i])*(sumf[q[l+1]]-sumf[q[l]]))l++;
    		dp[i]=dp[q[l]]+sumt[i]*(sumf[i]-sumf[q[l]])+s*(sumf[n]-sumf[q[l]]);
    		while(l<r&&(dp[q[r]]-dp[q[r-1]])*(sumf[i]-sumf[q[r]])>=(dp[i]-dp[q[r]])*(sumf[q[r]]-sumf[q[r-1]]))r--;
    		q[++r]=i;
    	}
    	cout<<dp[n];
    	return 0;
    }
    
  • 相关阅读:
    mysql导入导出sql文件
    linux 监控文件变化
    LeetCode:595.大的国家
    LeetCode:176.第二高的薪水
    LeetCode:182.查找重复的电子邮箱
    Excel学习笔记:行列转换
    通过数据分析题目实操窗口函数
    Oracle学习笔记:窗口函数
    Python学习笔记:利用爬虫自动保存图片
    电商数据分析基础指标体系(8类)
  • 原文地址:https://www.cnblogs.com/ttttttttrx/p/12650507.html
Copyright © 2020-2023  润新知