动态规划的优化
动态规划的优化
方法一:分离变量后使用单调队列
P3648.序列分割
正常dp做法
首先,可以证明切割次序与分数无关。
所以我们用\(f[i][k]\)表示区间前i个数切k次得到的最大分数值,并状态转移
\[f[i][k]=max\{f[i][k],f[j][k-1]+s[j]*(s[i]-s[j])\}
\]
代码:
// Problem: P3648 [APIO2014] 序列分割
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3648
// Memory Limit: 512 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
const int mn=100005,mk=205;
int n,k,f[mn][mk],a[mn],sum[mn],nxt[mn][mk];
void out(int x,int y)
{
if(x==0)return;
if(y==1)return (void)printf("%lld ",x);
out(nxt[x][y-1],y-1);
printf("%lld ",x);
}
signed main()
{
memset(f,-0x3f,sizeof(f));
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
f[i][0]=0;
}
for(int i=2;i<=n;i++)
{
for(int j=1;j<min(i,k+1);j++)
{
for(int l=1;l<i;l++)
{
if(f[i][j]<=f[l][j-1]+sum[l]*(sum[i]-sum[l]))
{
f[i][j]=f[l][j-1]+sum[l]*(sum[i]-sum[l]);
nxt[i][j]=l;
}
}
}
}
printf("%lld\n",f[n][k]);
out(nxt[n][k],k);
return 0;
}
复杂度\(O(n^{2})\),拿到50pts.
空间优化
可以改变循环顺序缩维。
注意到\(f[i][j]\)更新时只会用到\(f[][j-1]\)里的内容
考虑先遍历第二维,这样可以缩掉第二维
// Problem: P3648 [APIO2014] 序列分割
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3648
// Memory Limit: 512 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
const int mn=100005,mk=205;
int n,k,f[mn],a[mn],sum[mn],nxt[mn][mk];
void out(int x,int y)
{
if(x==0)return;
if(y==1)return (void)printf("%lld ",x);
out(nxt[x][y-1],y-1);
printf("%lld ",x);
}
signed main()
{
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
}
for(int j=1;j<=k;j++)
{
for(int i=n;i>=j+1;i--)
{
for(int l=1;l<i;l++)
{
if(f[i]<=f[l]+sum[l]*(sum[i]-sum[l]))
{
f[i]=f[l]+sum[l]*(sum[i]-sum[l]);
nxt[i][j]=l;
}
}
}
}
printf("%lld\n",f[n]);
out(nxt[n][k],k);
return 0;
}
优化
考虑方程现在为
\[
\]
若\(i\)选\(j\)优于\(l\)且\(j<l\)
\[f[j]+s[j]*s[i]-s[j]^2>f[l]+s[l]*s[i]-s[l]^2
\]
\[f[j]-f[l]-s[j]^2+s[l]^2>(s[l]-s[j])*s[i]
\]
\[\frac {f[j]-s[j]^2-(f[l]-s[l]^2)}{-s[l]-(-s[j])}>s[i]
\]
设左式为\(g(j,l)\)右式为\(h(i)\)则
\[j比l更优\leftrightarrow g(j,l)>h(i)
\]
\[j和l一样优\leftrightarrow g(j,l)==h(i)
\]
\[j比l更劣\leftrightarrow g(j,l)<h(i)
\]
本题中,
\[f[m][i]=\displaystyle{max}_{0\le j <i}\{f[m-1][j]+s[j]*(s[i]-s[j])\};
\]
固定m,对于\(j_1<j_2\),\(j_2\)优于\(j_1\)意味着\(g(j_1,j_2)<h(i)\),又已知\(h(i)\)单调递增,所以在某个m后\(j_2\)一直优于\(j_1\),而在此之前则相反。
我们考虑用单调队列维护现存可能合法的\(j\)。
如果存在三个决策\(j_1<j_2<j_3\),且\(g(j_1,j_2)>g(j_2,j_3)\)则可以直接踢掉\(j_2\)。
如果队首两元素的函数值小于当前h(i),可以踢掉第一个元素。
// Problem: P3648 [APIO2014] 序列分割
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3648
// Memory Limit: 512 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define int long long
#define inf 0x3f3f3f3f
using namespace std;
const int mn=100005,mk=205;
int n,k,f[mn],a[mn],sum[mn],nxt[mn][mk],g[mn];
int q[mn],hd,bk;
double slope(int y,int x)
{
if(sum[x]==sum[y])return -inf;
return double(g[x]-sum[x]*sum[x]-g[y]+sum[y]*sum[y])/double(sum[y]-sum[x]);
}
// void out(int x,int y)
// {
// if(x==0)return;
// if(y==1)return (void)printf("%lld ",x);
// out(nxt[x][y-1],y-1);
// printf("%lld ",x);
// }
signed main()
{
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
}
for(int j=1;j<=k;j++)
{
for(int i=1;i<=n;i++)
{
g[i]=f[i];
}
hd=bk=0;
for(int i=1;i<=n;i++)
{
while(bk-hd>=2 && slope(q[hd],q[hd+1])<=sum[i])hd++;
f[i]=0;
if(hd<bk)
{
// nxt[i][j]=q[hd];
f[i]=g[q[hd]]+sum[q[hd]]*(sum[i]-sum[q[hd]]);
}
while(bk-hd>=2 && slope(q[bk-1],q[bk-2])>=slope(i,q[bk-1]))bk--;
q[bk++]=i;
}
}
printf("%lld\n",f[n]);
// out(nxt[n][k],k);
return 0;
}

浙公网安备 33010602011771号