hdu 2829 Lawrence (dp斜率优化||四边形优化)

来源:互联网 发布:阿里云架设游戏教程 编辑:程序博客网 时间:2024/06/05 20:47

题意:

给出很多一段路线,由n个点组成,每个点有价值,定义某个区间[i,j]上的价值为这个区间没两个点两两相乘的和,现在可以选择m个点炸掉,这样可以分成m+1段,总的价值就是每段价值的和。那么现在问题来了,如何分段使得价值对低呢?

题解:

这题可以用队列优化和四边形优化,首先讲下队列优化的。


队列优化:

方程先写出来 设dp[i][j]表示前j点,炸掉i条边的最小值。j>i

状态方程:  dp[i][j]=min{dp[i-1][k]+w[k+1][j]}

观察没发现任何的单调性。那么变形一下

又由得出

w[1][j]=w[1][k]+w[k+1][j]+s[k]*(s[j]-s[k])


w[k+1][j]=w[1][j]-w[1][k]-s[k]*(s[j]-s[k])
代入DP方程
可以得出 y=dp[i-1][k]-w[1][k]+s[k]^2
x=s[k].
斜率s[j]

那么现在就可以用斜率优化了。

#include<iostream>#include<math.h>#include<stdio.h>#include<algorithm>#include<string.h>#include<vector>#include<map>using namespace std;typedef __int64 lld;#define oo 0x3f3f3f3f#define maxn 1005int dp[maxn][maxn];int q[maxn];int w[maxn],s[maxn];int main(){    int n,m,a,head,tail;    while(scanf("%d %d",&n,&m)!=EOF)    {        if(n==0&&m==0)break;        w[0]=s[0]=0;        for(int i=1;i<=n;i++)        {            scanf("%d",&a);            s[i]=s[i-1]+a;            w[i]=w[i-1]+s[i-1]*a;        }        for(int i=1;i<=n;i++)        {            dp[0][i]=w[i];            dp[i-1][i]=0;        }        for(int i=1;i<=m;i++)        {            head=tail=0;            q[tail++]=i;            for(int j=i+1;j<=n;j++)            {                while(head+1<tail)                {                    int k1=q[head];                    int k2=q[head+1];                    int x1=s[k1];                    int x2=s[k2];                    int y1=dp[i-1][k1]-w[k1]+s[k1]*s[k1];                    int y2=dp[i-1][k2]-w[k2]+s[k2]*s[k2];                    if((y2-y1)<=(x2-x1)*s[j]) head++;                    else break;                }                int k=q[head];                dp[i][j]=dp[i-1][k]+w[j]-w[k]-s[k]*(s[j]-s[k]);                while(head+1<tail)                {                    int k1=q[tail-2];                    int k2=q[tail-1];                    int k3=j;                    int x1=s[k1];                    int x2=s[k2];                    int x3=s[k3];                    int y1=dp[i-1][k1]-w[k1]+s[k1]*s[k1];                    int y2=dp[i-1][k2]-w[k2]+s[k2]*s[k2];                    int y3=dp[i-1][k3]-w[k3]+s[k3]*s[k3];                    if((y3-y2)*(x2-x1)<=(y2-y1)*(x3-x2)) tail--;                    else break;                }                q[tail++]=j;            }        }        printf("%d\n",dp[m][n]);    }    return 0;}

那么现在讲下四边形优化

四边形优化:

方程: dp[i][j]=min{dp[i-1][k]+w[k+1][j]}

像这样 f[i][j] = min{ f[i-1][j] + f[i+1][j] } 形式的方程都可以用四边形优化,证明?胡策,没几个人会证,不过这样形几乎是可以用四边形优化的

那么我们设一个优化的标记mark[i][j]表示最大能达到的

#include<iostream>#include<math.h>#include<stdio.h>#include<algorithm>#include<string.h>#include<vector>#include<map>using namespace std;typedef __int64 lld;#define oo 0x3f3f3f3f#define maxn 1005lld dp[maxn][maxn];int mark[maxn][maxn];lld w[maxn][maxn],val[maxn],sum[maxn][maxn];void getw(int n){    for(int i=1;i<=n;i++)    {        sum[i][i]=0;        for(int j=i+1;j<=n;j++)            sum[i][j]=sum[i][j-1]+val[i]*val[j];    }    w[n][n]=0;    for(int i=n-1;i>=1;i--)    {        for(int j=i+1;j<=n;j++)            w[i][j]=w[i+1][j]+sum[i][j];    }}void inst(int n){    memset(dp,0x3f,sizeof dp);    for(int i=1;i<=n;i++)    {        dp[1][i]=w[1][i];        mark[1][i]=0;    }}void Dp(int n,int m){    inst(n);    for(int i=2;i<=m+1;i++)    {        mark[i][n+1]=n;        for(int j=n;j>=i;j--)            for(int k=mark[i-1][j];k<=mark[i][j+1];k++)                if(dp[i][j]>dp[i-1][k]+w[k+1][j])                {                    dp[i][j]=dp[i-1][k]+w[k+1][j];                    mark[i][j]=k;                }    }}int main(){    int n,m;    while(scanf("%d %d",&n,&m)!=EOF)    {        if(n==0&&m==0)break;        for(int i=1;i<=n;i++)            scanf("%I64d",&val[i]);        getw(n);//精髓所在,得到两两点区间的权值        Dp(n,m);//状态转移        if(m+1>n-1)            dp[m+1][n]=0;        printf("%I64d\n",dp[m+1][n]);    }    return 0;}







0 0
原创粉丝点击