emmmm,马上要比赛了,最近正在复习打牌
又看了遍斜率优化dp,感觉对他的理解上了一个新的高度呐
斜率优化dp可以优化掉一个n
一般来说,如果要求一个状态时要遍历前面已经求过的所有状态的话,就可以考虑使用斜率优化
再求第k个状态时对于前面的所有状态如果k2比k1更优就有
Y(k2)-Y(k1)X(k2)-X(k1)<f(k)
我们就可以使用斜率优化
这里我们要保证x(k2)-x(k1)>0所以一般需要排序,或者x(k)是sum之类的且f(k)也是递增的
(说实话感觉如果把上面的<换成>,然后f(k)是递减的也能使用斜率优化,这个等下问问我的牌友)
斜率优化是我们有一个队列,这个队列的斜率是递增排列的
然后在计算dp[k]的时候,我们从队列头开始找,如果第二个比第一个更优,我们就把head出队
知道找到第二个没有第一个优秀
那么我们可知这时候第一个是最优的(斜率是递增的嘛)
而且被出队的点对后面也没有影响,因为斜率是递增的,也就是
那么dp[k]就可以通过这个状态递推出来
然后我们下一个要求第k+1个状态嘛,我们就要保证能递推出k+1这个状态的状态必须进过队列一次(出不出队就是另外一回事了)
然后比如说我要把状态为j的入队,这个时候我就要保证我序列斜率的单调性就行了,这里可能会导致一些节点出队,可以分类讨论论证这些出队的节点绝对不会是最优的
说了这么多,还是看一个题目最为直接
HDU3045
题意大概就是我有n头牛,每头牛有一个快乐值,他们要出去玩,我要把这群牛分成几组,每组的人数不能少于m
然后对于每组的牛,他们的快乐值会减少为这一组中快乐值最少的牛的快乐值
然后问如何可以让所有牛快乐值的减少量最小
还有一个超级重要的东西(幸亏今天又做了一道dp题
就是我们在初始化的时候,不能简单的memset为0
有的时候最初的情况是不能从0推出来的,这时候就需要我们手动初始化一下
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#define maxn 400100
#define ll long long
using namespace std;
ll a[maxn];
ll dp[maxn];
ll que[maxn],sum[maxn];
ll getup(ll k2,ll k1)//求yk2-yk1
{
return (dp[k2]-sum[k2]+a[k2+1]*k2)-(dp[k1]-sum[k1]+a[k1+1]*k1);
}
ll getdown(ll k2,ll k1)//求xk2-xk1
{
return (a[k2+1]-a[k1+1]);
}
void getdp(int i,int j)
{
dp[i]=dp[j]+(sum[i]-sum[j])-a[j+1]*(i-j);//状态转移方程,从j退出i
}
int main()
{
ll n,m;
while(~scanf("%lld %lld",&n,&m))
{
for (ll k=1;k<=n;k++)
scanf("%lld",&a[k]);
sort(a+1,a+n+1);
sum[0]=0;a[0]=0;
for (int k=1;k<=n;k++)
sum[k]=sum[k-1]+a[k];
ll head(0),tail(1);
que[head]=0;dp[0]=0;
for (ll k=1;k<=n;k++)
{
while(head+1<tail&&getup(que[head+1],que[head])<=k*(getdown(que[head+1],que[head])))//找到最优的那个点
head++;
getdp(k,que[head]);//状态转移
ll j=k-m+1;
if (j<m)//看对下一个状态,我必须把什么入队
continue;
while(head+1<tail&&getup(j,que[tail-1])*getdown(que[tail-1],que[tail-2])<=getup(que[tail-1],que[tail-2])*getdown(j,que[tail-1]))
tail--;
que[tail]=j;tail++;//入队操作
}
printf("%lld\n",dp[n]);
}
return 0;
}
对于初始化的题目,这里有一道hdu3669
这道题的排序也很值得写博客,开始自己把方程推出来之后还感觉要用线段树求区间最值
排序之后,真的是很神奇
这题的思路就不写了,网上的博客一搜一大堆
下面放上代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
#define maxn 50500
#define ll long long
using namespace std;
struct Data
{
ll x,y;
};
bool cmp(Data aa,Data bb)
{
if (aa.x==bb.x)
return aa.y<bb.y;
else
return aa.x>bb.x;
}
Data a[maxn],b[maxn];
ll que[maxn],head,tail;
ll dp[maxn][110];
ll getup(ll j,ll k2,ll k1)
{
return dp[k2][j-1]-dp[k1][j-1];
}
ll getdown(ll j,ll k2,ll k1)
{
return b[k1+1].x-b[k2+1].x;
}
void getdp(ll i,ll j,ll k)
{
dp[i][j]=dp[k][j-1]+b[k+1].x*b[i].y;
return;
}
int main()
{
ll n,m;
while(~scanf("%lld %lld",&n,&m))
{
for (ll k=1;k<=n;k++)
scanf("%lld %lld",&a[k].x,&a[k].y);
sort(a+1,a+n+1,cmp);
ll nb(0),Max(0);
for (ll k=1;k<=n;k++)
if (Max<a[k].y)
{
nb++;b[nb]=a[k];
Max=a[k].y;
}
//x从大到小,y从小到大
memset(dp,0,sizeof(dp));
for (ll k=1;k<=nb;k++)
dp[k][1]=b[1].x*b[k].y;//初始化
for (ll j=2;j<=m;j++)
{
head=0;tail=1;
que[head]=1;dp[1][j]=b[1].x*b[1].y;//初始化
for (ll i=2;i<=nb;i++)
{
while(head+1<tail&&getup(j,que[head+1],que[head])<=getdown(j,que[head+1],que[head])*b[i].y)
head++;
getdp(i,j,que[head]);
while(head+1<tail&&getup(j,i,que[tail-1])*getdown(j,que[tail-1],que[tail-2])<=getup(j,que[tail-1],que[tail-2])*getdown(j,i,que[tail-1]))
tail--;
que[tail]=i;tail++;
}
}
ll Min(10000001000000);
for (ll j=1;j<=m;j++)
Min=min(Min,dp[nb][j]);
printf("%lld\n",Min);
}
return 0;
}
(感觉还是自己的代码长得漂亮哈