POJ3709 K-Anonymous Sequence(k匿名序列)(斜率优化dp)

来源:互联网 发布:学linux能干哪些工作 编辑:程序博客网 时间:2024/04/25 19:15

poj3709题目传送
看到这名字第一反应是那个面具
斜率优化的动态规划,第一次做斜率优化的dp题。。。而且自己也不会,是看着书慢慢弄懂的。书是《挑战》p341 ,这一面有很多错误,刚开始自己写完后照书上一对,一模一样后发现答案很不对,后来才发现书上的状态转移方程存在错误(在下面的公式里你自己对一遍就知道了) 总之就是,书上的不一定对,自己领悟并理解才最重要。

首先不难得出,a0是最小的值,为了使操作的次数较小,我们不可能把最小值拿着减。所以我们发现,对于a0这样的较小的项,我们可以选择从小到大来选择需要减少到a0的项。设dpi为只考虑前i项的前提下最少的操作次数,则有dpi=min{dpj+(a[j+1]-a[j])+…+(a[i1]-a[j])},其中0jik

直接计算自然没问题,但是O3的复杂度不太好的样子,于是考虑优化:

例如我们记一个前缀和,

Si=a[0]+...+a[i1]

dpi=min{dpj+S[i]-S[j+1]-aj*(i-j-1)}
观察方程我们可以发现,若我们设一个函数(用x替换i)fj(x)=ajx+dp[j]S[j+1]+aj(j+1),则有dp[i]=S[i]+minfj(i),于是计算dp[n]就变为了在i-k+1条直线中寻找x=i的最小值就好了。
于是我们考虑用双端队列去维护所有可能成为最小值的直线的集合,并以从小到大的顺序排列。那么我们如何判断要去掉某条直线呢?

设:

f1(x)=a1x+b1
f2(x)=a2x+b2
f3(x)=a3x+b3

我们有(a2a1)(b3b2)(b2b1)(a3a2)
时,f2为要排除的直线,为什么呢?
简证:当f1f3的交点在f2以下时,联立f1f3
x0=(b1b3a3a1)
然后对于f1,f3,有f(x0)=a1(b1b3a3a1)+b1=(b1a3b3a1a3a1)
f2(x0)=a2(b1b3a3a1)+b2
f2(x0)f1(x0)f3(x0)时,有a2(b1b3a3a1)+b2(b1b3a3a1)
拿纸来算吧 化简就有(a2a1)(b3b2)(b2b1)(a3a2)了。

所以我们就可以根据上述方法来做双端队列,详见标程:

#include <iostream>#include <cstdio>#include <cstdlib>#include <cstring>#include <algorithm>#include <vector>#include <queue>using namespace std;typedef long long ll;const ll maxn=505000;ll cas=0;ll n,k;ll a[maxn];ll deq[maxn];//虽然是双端队列 但存的是值ll dp[maxn*10];ll sh[maxn];//前缀和bool check(ll f1,ll f2,ll f3){    ll a1=-a[f1];    ll b1=dp[f1]-sh[f1+1]+a[f1]*(f1+1);    ll a2=-a[f2];    ll b2=dp[f2]-sh[f2+1]+a[f2]*(f2+1);    ll a3=-a[f3];    ll b3=dp[f3]-sh[f3+1]+a[f3]*(f3+1);    return (a2-a1)*(b3-b2)>=(b2-b1)*(a3-a2);}ll f(ll j,ll x){    return -a[j]*x+dp[j]-sh[j+1]+a[j]*(j+1);}int main(){    //freopen("1.in","r",stdin);    //freopen("2.out","w",stdout);    scanf("%lld",&cas);    for(ll z=0;z<cas;z++)    {        memset(sh,0,sizeof(sh));        memset(a,0,sizeof(a));        memset(dp,0,sizeof(dp));        memset(deq,0,sizeof(deq));        scanf("%lld%lld",&n,&k);        for(ll i=0;i<n;i++)        {            scanf("%lld",&a[i]);        }        for(ll i=0;i<n;i++)        {            sh[i+1]=sh[i]+a[i];        }        ll s=0;        ll t=1;        deq[s]=0;        dp[0]=0;        for(ll i=k;i<=n;i++)        {            if(i-k>=k)            {                while(s+1<t && check(deq[t-2],deq[t-1],i-k))                {                    t--;                }                deq[t]=i-k;                t++;            }            while(s+1<t && f(deq[s],i)>=f(deq[s+1],i))            {                s++;            }            dp[i]=sh[i]+f(deq[s],i);        }        printf("%lld\n",dp[n]);    }    return 0;}

书上没看懂得就来看一下这个吧QAQ

0 0
原创粉丝点击