[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$:
#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;
}
反悔贪心:
#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++