题目:
题解:
Emmm首先一眼dp了吧。
O(n3)
很蒻的博主就设计了一个浅显的dp[i][j]表示前i个分成j组的最小费用, O(n3) 而且看上去根本没有办法优化的样子?!
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int n,s,T[10005],F[10005],dp[5005] [5005],ans;
int main()
{
scanf("%d%d",&n,&s);
for (int i=1;i<=n;i++)
{
scanf("%d%d",&T[i],&F[i]);
T[i]+=T[i-1]; F[i]+=F[i-1];
}
memset(dp,0x7f,sizeof(dp)); ans=dp[0][0]; dp[0][0]=0;
for (int i=1;i<=n;i++) dp[i][1]=(s+T[i])*F[i];
for (int i=2;i<=n;i++)
for (int k=1;k<i;k++)
for (int j=2;j<=k+1;j++)
dp[i][j]=min(dp[i][j],dp[k][j-1]+(F[i]-F[k])*(j*s+T[i]));
for (int i=1;i<=n;i++) ans=min(ans,dp[n][i]);
printf("%d",ans);
}
O(n2)
不行啊,上面这一坨看上去根本没法优化,起码需要一个
O(n2)
的方法啊
从头来过,f(i)表示前i个的最小花费… 但是时间有后效性啊,这个后效性主要就在这个s上,因为你不知道前面分了多少组啊
那么我们把s单独抽出来考虑,假设我们将整个序列划分成了m段,每一段的F的和依次是
p1,p2..pm
,那么s带来的花费就是
s∗p1+2∗s∗p2+3∗s∗p3…+m∗s∗pm
=s∗(p1+2∗p2+3∗p3+…+m∗pm)
观察前面的那个式子,系数是等差的,其实稍微转化一下就是另一种形式:
(p1+p2+p3+…+pm)+(p2+p3+…+pm)+(p3+…+pm)+…+(pm)
F的后缀和?!
这样的话,我们就非常完美地将s所带来的影响消除了
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int n,s,T[10005],F[10005],dp[10005],ans;
int main()
{
scanf("%d%d",&n,&s);
for (int i=1;i<=n;i++) scanf("%d%d",&T[i],&F[i]),T[i]+=T[i-1];
for (int i=n-1;i>=1;i--) F[i]+=F[i+1];
memset(dp,0x7f,sizeof(dp)); dp[0]=0;
for (int i=1;i<=n;i++)
for (int j=0;j<i;j++)
dp[i]=min(dp[i],dp[j]+s*F[j+1]+(F[j+1]-F[i+1])*T[i]);
printf("%d",dp[n]);
}
O(nlogn)
上面的
f[i]=min(f[j]+F[j+1]∗(s+Ti)−F[i+1]∗T[i])
T如果是不小于0的话随便看看就应该知道单调队列就可以做
我一看SDOI题目数据范围,-(2^8)<=Ti<=2^8,你的T都能小于0了你咋不上天呐
这一波画画柿子就可以斜率优化dp做了吧
f[j]+s∗F[j+1]=−Ti∗F[j+1]+F[i+1]∗Ti+f[i]
这就是斜率为-Ti,截距为F[i+1]*Ti+f[i],当截距最小时,由于F[i+1]*Ti为定值,所以f[i]最小,维护一个下凸壳
因为要枚举j找一个最优值,我们可以把(F[j+1],f[j]+s*F[j+1])当做一个点,这不就跟刚才的题目一样啦
当然也可以用splay维护凸包(挖坑
卡常数技巧:
把不用longlong的开成int!
用快读
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
using namespace std;
const int INF=1e9;
const int N=300005;
struct hh{LL y,d;int x,k,id;}Q[N],jl[N];
int n,stack[N],T[N],F[N],s;LL dp[N];
int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*f;
}
double getk(hh a,hh b)
{
if (a.x==b.x) return -INF;
return ((double)(a.y-b.y)/(double)(a.x-b.x));
}
void merge(int l,int r)
{
int mid=(l+r)>>1,t1=l,t2=mid+1;
for (int i=l;i<=r;i++)
if ((t1<=mid && Q[t1].x<Q[t2].x) || t2>r) jl[i]=Q[t1++];
else jl[i]=Q[t2++];
for (int i=l;i<=r;i++) Q[i]=jl[i];
}
void cdq(int l,int r)
{
if (l==r){Q[l].x=F[l+1]; Q[l].y=dp[l]+(LL)s*F[l+1]; return;}
int mid=(l+r)>>1,t1=l,t2=mid+1,top=0;
for (int i=l;i<=r;i++)
if (Q[i].id<=mid) jl[t1++]=Q[i];
else jl[t2++]=Q[i];
for (int i=l;i<=r;i++) Q[i]=jl[i];
//按照时间分开,此时右边已经是想要的形式,左边会在cdq分治里完成
cdq(l,mid);
for (int i=l;i<=mid;i++)//维护一个下凸壳
{
while (top>1 && getk(Q[stack[top]],Q[i])<=getk(Q[stack[top-1]],Q[stack[top]])) top--;
stack[++top]=i;
}
int now=1;
for (int i=mid+1;i<=r;i++)
{
while (now<top && getk(Q[stack[now]],Q[stack[now+1]])<jl[i].k) now++;
int j=stack[now];
dp[jl[i].id]=min(dp[jl[i].id],Q[j].y-(LL)Q[j].x*jl[i].k-jl[i].d);
}
cdq(mid+1,r); merge(l,r);
}
int cmp(hh a,hh b){return a.k<b.k;}
int main()
{
n=read(); s=read();
for (int i=1;i<=n;i++) T[i]=read(),F[i]=read(),T[i]+=T[i-1];
for (int i=n;i>=0;i--) F[i]+=F[i+1];
for (int i=0;i<=n;i++) Q[i].k=-T[i],Q[i].id=i,Q[i].d=(LL)F[i+1]*T[i];
sort(Q,Q+n+1,cmp);
memset(dp,0x7f,sizeof(dp)); dp[0]=0;
cdq(0,n);
printf("%lld",dp[n]);
}