【分治+斜率优化】BZOJ2149拆迁队 CF660F Bear and Bowling 4

BZOJ2149拆迁队

【题目】
原题地址
题目大意不想写。

【题目分析】
斜率优化的dp是显然的,然后就是怎么维护的问题了。

【解题思路】
这题显然就是一个斜率优化的dp,然后分治的时候维护一下凸壳,但是我写了很久
首先考虑暴力的dp怎么做
f [ i ] 表示保留旧房子 i 时, 1 i 最多能保留多少个旧房子
g [ i ] 表示 f [ i ] 为最优决策时, 1 i 的最小总花费
然后我们有dp式

f [ i ] = max { f [ j ] + 1 } ( j = 0.. i 1 , a [ i ] a [ j ] i j )

g [ i ] = min { ( 2 a [ j ] + i j ) ( i j 1 ) 2 + g [ j ] } + a [ i ] + b [ i ]

f 的优化显然,接下来考虑对 g 的优化,去掉后面的 i 相关常数可以得到。

i ( a [ j ] j ) + i ( i 1 ) 2 + ( j ( j + 1 ) 2 j a [ j ] a [ j ] + g [ j ] )

然后这就是一个斜率优化的形式,我们把这个东西写得简单一点。
c [ i ] = g [ i ] + i ( i + 1 ) 2 a [ i ] × i a [ i ] d [ i ] = a [ i ] i
最终我们可以得到:
g [ i ] = m i n ( i × d [ j ] + c [ j ] ) + b [ i ] + a [ i ] + i ( i 1 ) 2

要满足:
j < i , f [ j ] + 1 = f [ i ] d [ j ] d [ i ]

去掉关于 i 的常数后就是每次插入一个点 ( d [ i ] , c [ i ] ) 转移的时候拿斜率 i 的直线卡最小截距。

这个东西怎么实现呢?分治!
处理左半边对右半边的影响就是按照 d [ i ] 排序,然后插入斜率。
因为插入顺序单调,所以我们只需要维护一个下凸壳,查询时在凸壳上三分。
时间复杂度 O ( n l o g 2 n )

【参考代码】

#include<bits/stdc++.h>
#define mkp(x,y) make_pair(x,y)
using namespace std;

typedef long long LL;
typedef pair<int,LL> pii;
const LL INF=(LL)1e18;
const int N=1e5+10;
int n,mx;
int a[N],b[N],d[N],f[N],id[N];
LL ans,c[N],g[N];
vector<pii>vc;

bool cmp(int x,int y)
{
    if(d[x]==d[y])
        return x<y;
    return d[x]<d[y];
}

pii operator -(pii A,pii B){
    return mkp(A.first-B.first,A.second-B.second);
}

LL operator *(pii A,pii B){
    return A.first*B.second-A.second*B.first;
}

void solve(int l,int r)
{
    if(l==r)
        return;
    int mid=(l+r)>>1,cnt=0;
    solve(l,mid);
    for(int i=l;i<=r;++i)
        id[++cnt]=i;
    sort(id+1,id+cnt+1,cmp);
    pii now;
    int mx=-1,L,R,m1,m2;
    for(int i=1;i<=cnt;++i)
        if(id[i]<=mid)
        {
            if(f[id[i]]<mx)
                continue;
            if(id[i]!=0 && !f[id[i]])
                continue;
            if(f[id[i]]>mx)
                vc.clear(),mx=f[id[i]];
            now=mkp(2*d[id[i]],c[id[i]]+2*g[id[i]]);
            while(vc.size()>1 && (vc[vc.size()-1]-vc[vc.size()-2])*(now-vc[vc.size()-1])<=0)
                vc.pop_back();
            vc.push_back(now);
        }
        else
        {
            if(f[id[i]]>mx+1)
                continue;
            if(f[id[i]]<mx+1)
                g[id[i]]=INF;
            f[id[i]]=mx+1;
            L=0,R=vc.size()-1;
            while(L+2<R)
            {
                m1=(R-L)/3+L;m2=(R-L)/3*2+L;
                if(1ll*vc[m1].first*id[i]+vc[m1].second>1ll*vc[m2].first*id[i]+vc[m2].second)
                    L=m1+1;
                else
                    R=m2-1;
            }
            for(int j=L;j<=R;++j)
                g[id[i]]=min(g[id[i]],(1ll*vc[j].first*id[i]+1ll*id[i]*(id[i]-1)+vc[j].second)/2+a[id[i]]+b[id[i]]);
        }
    solve(mid+1,r);
}

int main()
{
    freopen("BZOJ2149.in","r",stdin);
    freopen("BZOJ2149.out","w",stdout);

    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;++i)
        scanf("%d",&b[i]);
    for(int i=1;i<=n;++i)
    {
        g[i]=INF;d[i]=a[i]-i;
        c[i]=1ll*i*(i+1)-2ll*i*a[i]-2ll*a[i];
    }
    solve(0,n);
    for(int i=1;i<=n;++i)
    {
        g[i]+=1ll*(n-i)*(a[i]+1)+1ll*(n-i)*(n-i-1)/2;
        if(mx<f[i])
            mx=f[i],ans=g[i];
        else
        if(mx==f[i])
            ans=min(ans,g[i]);
    }
    printf("%d %lld\n",mx,ans);

    return 0;
}

CF660F Bear and Bowling 4

【题目】
原题地址
题目大意:给一个长度为 n 的序列,第 i 个数是 a i ,选取一个连续子序列 { a x , a x + 1 , . . . , a x + k 1 } 使得 i = 1 k i a x + i 1 最大。

【题目分析】
同上。

【解题思路】
p r e [ i ] v a l [ i ] 的前缀和。 p r e 2 [ i ] 为每一个数值乘以所在位置的值的前缀和。
那么 s u m ( i , j ) = p r e 2 [ j ] p r e 2 [ i 1 ] ( i 1 ) ( p r e [ j ] p r e [ i 1 ] ) 。所求的即 s u m ( i , j ) 的最大值。

扫描二维码关注公众号,回复: 124142 查看本文章

然后这个显然是一个斜率优化的形式。接下来对于一个点 j ,我们往前找 i 要满足 ( i 1 ) p r e [ j ] + ( i 1 ) p r e [ i 1 ] p r e 2 [ i 1 ] 这个值最大。就是维护 ( i , i p r e [ i ] p r e 2 [ i ] ) 的下凸壳。凸壳上二分就行了。

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const LL N=2e5+10;
LL n,res;
LL a[N],pre[N],pre2[N],c[N];
vector<LL>q;

double slope(LL x,LL y)
{
    return (double)(c[x]-c[y])/(x-y);
}

LL binary(LL x,LL l,LL r)
{
    while(l+1<r)
    {
        LL mid=(l+r)>>1;
        if(slope(q[mid],q[mid-1])>x)
            l=mid;
        else
            r=mid;
    }
    return q[l];
}

int main()
{
    freopen("CF660F.in","r",stdin);
    freopen("CF660F.out","w",stdout);

    scanf("%I64d",&n);
    for(LL i=1;i<=n;++i)
    {
        scanf("%I64d",&a[i]);
        pre[i]=pre[i-1]+a[i];
        pre2[i]=pre2[i-1]+a[i]*i;
        c[i]=1ll*i*pre[i]-pre2[i];
    }

    q.push_back(0);
    for(LL i=1;i<=n;++i)
    {
        LL j=binary(pre[i],0,q.size());
        res=max(res,pre2[i]-pre2[j]-j*(pre[i]-pre[j]));
        while(q.size()>1 && slope(q[q.size()-1],q[q.size()-2])<slope(i,q[q.size()-1]))
            q.pop_back();
        q.push_back(i);
    }
    printf("%I64d\n",res);

    return 0;
}

【总结】
为了讲分治写的,没想到斜率优化在 x 不连续的时候还可以这么玩。

猜你喜欢

转载自blog.csdn.net/dream_lolita/article/details/79488678