CEOI2017 Building Bridges

传送门

题解:
对w求前缀和s,可以得到一个 O ( n 2 ) O(n^2) O(n2)的dp:令f[i]为桥到i结束时花的最小代价,则 f [ i ] = m i n { f [ j ] + ( h [ i ] − h [ j ] ) 2 + ( s [ i − 1 ] − s [ j ] ) } f[i]=min\{ f[j]+(h[i]-h[j])^2+(s[i-1]-s[j]) \} f[i]=min{f[j]+(h[i]h[j])2+(s[i1]s[j])}
这个时间复杂度显然不够,考虑斜率优化。
经过一通化简,令 g [ i ] = f [ i ] + h [ i ] 2 − s [ i ] g[i]=f[i]+h[i]^2-s[i] g[i]=f[i]+h[i]2s[i],若 j 1 j_1 j1 j 2 j_2 j2优,得 2 h [ i ] ( h [ j 1 ] − h [ j 2 ] ) > g [ j 1 ] − g [ j 2 ] 2h[i](h[j_1]-h[j_2])>g[j_1]-g[j_2] 2h[i](h[j1]h[j2])>g[j1]g[j2]
h没有单调性,考虑cdq分治。
至今还是不熟悉cdq分治优化dp和斜率优化的套路(承认吧我就没做几道这种题)

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 100005
#define D 20
#define LL long long
#define INF 1e16
using namespace std;
int n,tp,num[D][maxn];
LL f[maxn];
struct node { int i,h; LL s; } a[maxn],tmp[maxn];
bool cmpi(node p,node q) { return p.i<q.i; }
struct Point
{
    LL x,y,i;
    Point(LL x=0,LL y=0,LL i=0):x(x),y(y),i(i){};
}stk[maxn];
typedef Point Vector;
Vector operator - (Vector a,Vector b) { return Vector(a.x-b.x,a.y-b.y,0); }
LL Cross(Vector a,Vector b) { return a.x*b.y-a.y*b.x; }
double T(Point a,Point b)
{
    if(a.x==b.x) return a.y<b.y?INF:-INF;
    return 1.0*(a.y-b.y)/(a.x-b.x);
}
void Stk(int l,int r)
{
    tp=0;
    for(int i=l;i<=r;i++)
    {
        Point now=Point(a[i].h,f[a[i].i]+1ll*a[i].h*a[i].h-a[i].s,a[i].i);
        while(tp>1&&Cross(stk[tp]-stk[tp-1],now-stk[tp])<=0) tp--;
        stk[++tp]=now;
    }
}
void cdq(int k,int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    cdq(k+1,l,mid);
    for(int i=l;i<=r;i++) a[i]=tmp[num[k+1][i]];
    Stk(l,mid);
    int fr=1;
    for(int i=mid+1;i<=r;i++)
    {
        while(fr<tp&&2.0*a[i].h>T(stk[fr],stk[fr+1])) fr++;
        int j=stk[fr].i,id=a[i].i;
        f[id]=min(f[id],f[j]+1ll*(tmp[j].h-tmp[id].h)*(tmp[j].h-tmp[id].h)+tmp[id-1].s-tmp[j].s);
    }
    for(int i=mid+1;i<=r;i++) a[i]=tmp[i];
    cdq(k+1,mid+1,r);
}
void Merge(int k,int l,int r)
{
    if(l==r) { num[k][l]=l; return; }
    int mid=(l+r)>>1;
    Merge(k+1,l,mid); Merge(k+1,mid+1,r);
    for(int i=l,j=mid+1,cnt=l;cnt<=r;cnt++)
        if(i<=mid&&(j>r||tmp[num[k+1][i]].h<=tmp[num[k+1][j]].h)) num[k][cnt]=num[k+1][i++];
        else num[k][cnt]=num[k+1][j++];
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) { scanf("%d",&a[i].h); a[i].i=i; }
    for(int i=1;i<=n;i++) { scanf("%lld",&a[i].s); a[i].s+=a[i-1].s; }
    memset(f,0x3f,sizeof(f)); f[1]=0;
    memcpy(tmp,a,sizeof(a));
    Merge(1,1,n); cdq(1,1,n);
    printf("%lld\n",f[n]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值