[CSP-S模拟测试]:trade(反悔贪心)
题目传送门(内部题62)
输入格式
第一行有一个整数$n$。
第二行有$N$个整数:$a_1\ a_2\ a_3\cdot\cdot\cdot a_n$。
输出格式
一行一个整数表示最大收益。
样例
样例输入:
5
1 1 5 3 6
样例输出:
9
数据范围与提示
样例解释:
第$1,2$天分别买入一件货物,第$3,5$天分别卖出一件货物,第$4$天不进行交易。
$-1-1+5+6=9$。
数据范围:
对于所有数据,$n\leqslant 10^5$,$0\leqslant a_i\leqslant 10^6$。
题解
一个很显然的问题,最后一定是要把所有买的物品卖光。
那么,我们先来考虑$DP$,设$dp[i][j]$表示到了第$i$天,手里有$j$个物品的最大收益即可。
则转移方程为:
$$dp[i][j]=\max(dp[i-1][j],dp[i-1][j-1]-a[i],dp[i-1][j+1]+a[i])$$
在来考虑一下$j$上界的问题,因为我们到了第$i$天最多会有$i$件物品,最后还要卖光,所以$j$的区间其实是如下图中红色区域:
考虑这样一个有关考试策略的问题,我们可以将其上界设为$1000$左右,这样对于$70\%$的数据,上界最多会是$500$,然而对于$100\%$的数据我们还有可能过掉,何乐而不为?
用滚动数组即可,还不用清空。
其实上界设成$471$就可以$AC$啦~
现在来考虑正解,当时我以为是线段树优化$DP$,因为那个式子简直太像了!!!
然而这却是一道反悔贪心……
考虑新的一天如果有单价为$b$的货物,之前有单价为$a(a<b)$的货物,那么我们的策略一定是卖$b$买$a$,即$b-a$。
显然,买$a$这个决策在现在和以后一定是最优的,但是$a$与$b$配对并不一定是最优的,以后可能会出现$c(c>b)$,$c−a$才是最优策略。这时,我们就采用可反悔的贪心策略,用小跟堆维护即可。
时间复杂度:$\Theta(n\log n)$。
期望得分:$100$分。
实际得分:$100$分。
代码时刻
$DP$:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include<bits/stdc++.h> using namespace std; int n; long long dp[2][100001]; long long ans; bool now; int main() { scanf( "%d" ,&n); memset(dp,-0x3f, sizeof (dp)); dp[0][0]=0; for ( int i=1;i<=n;i++) { int a,minn=min(471,min(i,n-i)); scanf( "%d" ,&a);now^=1; for ( int j=0;j<=minn;j++) { dp[now][j]=dp[!now][j]; dp[now][j]=max(dp[now][j],dp[!now][j-1]-a); dp[now][j]=max(dp[now][j],dp[!now][j+1]+a); } } printf( "%lld" ,dp[now][0]); return 0; } |
反悔贪心:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include<bits/stdc++.h> using namespace std; int n,a; priority_queue< int ,vector< int >,greater< int > > q; long long ans; int main() { scanf( "%d%d" ,&n,&a); q.push(a);ans=-a; for ( int i=2;i<=n;i++) { scanf( "%d" ,&a); q.push(a); ans-=a; if (q.top()<a) { q.push(a); q.pop(); } } while (q.size()) { ans+=q.top(); q.pop(); } printf( "%lld" ,ans); return 0; } |
rp++
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步