【BZOJ4897】成绩单(THUSC2016)-玄幻区间DP
测试地址:成绩单
做法:本题需要用到区间DP。
容易想到每次取的都是一个子序列。直觉上想到的一个状态定义是,令为删掉区间中所有数的最小代价,但我们发现这没法转移,又注意到转移和所取的子序列中最大数和最小数有关,那么便有了如下状态定义和状态转移方程:
令为删掉区间中的一部分数,使得剩下的数都在范围内的最小代价,为删掉区间中所有数的代价,则有:
其中为区间中从左边/右边开始数第一个不在内的数的位置。这个方程通过一种玄幻的方式枚举了所有可能是最优的方案,可以感性理解一下……
然后我们有:
这个方程就很显然了。那么我们把所有数离散化后做DP,就可以解决这一题了,时间复杂度为。
(这题真的好难啊……为什么还有dalao说是DP入门题啊……可能是国家队入门吧)
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=1000000000ll*1000000000ll;
int n,now[55];
ll a,b,pos[55],f[55][55][55][55],g[55][55];
struct forsort
{
int id;
ll val;
}F[100];
bool cmp(forsort a,forsort b)
{
return a.val<b.val;
}
int main()
{
scanf("%d%lld%lld",&n,&a,&b);
for(int i=1;i<=n;i++)
{
scanf("%lld",&F[i].val);
F[i].id=i;
}
sort(F+1,F+n+1,cmp);
int tot=0;
for(int i=1;i<=n;i++)
{
if (i==1||F[i].val!=F[i-1].val) pos[++tot]=F[i].val;
now[F[i].id]=tot;
}
for(int i=1;i<=n;i++)
{
g[i][i]=a;
for(int l=1;l<=tot;l++)
for(int r=l;r<=tot;r++)
{
if (now[i]>=l&&now[i]<=r) f[i][i][l][r]=0;
else f[i][i][l][r]=a;
}
}
for(int len=2;len<=n;len++)
for(int i=1;i+len-1<=n;i++)
{
int j=i+len-1,maxv=0,minv=tot+1;
for(int x=i;x<=j;x++)
maxv=max(maxv,now[x]),minv=min(minv,now[x]);
g[i][j]=a+b*(pos[maxv]-pos[minv])*(pos[maxv]-pos[minv]);
for(int l=1;l<=tot;l++)
for(int r=l;r<=tot;r++)
{
int tl=i-1,tr=j+1;
for(int k=i;k<=j;k++)
{
if (now[k]>=l&&now[k]<=r) tl=k;
else break;
}
for(int k=j;k>=i;k--)
{
if (now[k]>=l&&now[k]<=r) tr=k;
else break;
}
if (tl>=tr) f[i][j][l][r]=0;
else f[i][j][l][r]=g[tl+1][tr-1];
for(int k=i;k<j;k++)
f[i][j][l][r]=min(f[i][j][l][r],f[i][k][l][r]+f[k+1][j][l][r]);
}
for(int l=1;l<=tot;l++)
for(int r=l;r<=tot;r++)
g[i][j]=min(g[i][j],f[i][j][l][r]+a+b*(pos[r]-pos[l])*(pos[r]-pos[l]));
}
printf("%lld",g[1][n]);
return 0;
}