【2011集训队出题】拆迁队

Description:

lanxisi带领着他的拆迁队来整治一个街道。这个街道由N个旧房子组成,从左到右编号为1..N。每个旧房子i有一个正整数的美观度Ai。
  lanxisi希望整个街道从左到右美观度严格递增,也就是保证 Ai<Aji<j 。但是旧的街道明显不符合这个要求,于是lanxisi希望拆迁一些旧房子并在原地建立新房子来满足这一要求。但是与很多拆迁队一样,很多钉子户拒绝拆迁。所以lanxisi希望能保留最多的旧房子来满足某些钉子户,当然,保留一个旧房子需要给房子主人Bi的赔偿金。最后,总花费=整治好以后所有房子美观度之和+赔偿金之和。新的美观值也都必须是正整数。
  现在,请你求出lanxisi最多能保留多少旧房子和整治这个街道所需要的最少总花费(当然是在保留旧房子最多这个前提下的)。
 1<=N<=100000,0<=Ai,Bi<=100000000。

:题解:

先思考最朴素的O(n^2)dp是如何运作的。

fi 是以i结尾最长不下降子序列的长度。
si 是以i结尾的最小总花费。

fi=max(fi+1)j<i,a[j]<a[i],a[j]j<=a[i]i)
si=min(sj+)j<i,a[j]<a[i],a[j]j<=a[i]i,f[i]=f[j]+1

如果有:
j<k,a[j]j<=a[k]k
那么一定有:
a[j]<a[k]

所以可以去掉一个条件。

这样就可以可以用O(n log n)的时间按照a[i]-i为关键字dp出f。

接着按f分层dp。

打省略号的部分可以化成三部分:
1.和j有关的部分
2.和i有关的部分
3.(a[j]-j)*i

在同一层中,如果序号j递增,则a[j]-j递减,不然它们就不会在同一层。

所以满足斜率优化形式。

但是并没有这么简单。

注意条件除了a[j]-j<=a[i]-i,还有 j<i

在分层后,序号并不是递增,高层的序号可能比低层小,而且a[j]-j同样只是在同一层内有序,不同层的大小关系无法判断。

简化问题后可以看作对一层的连续的一段j求
min(sj+a[j]ji)

到这里可以同线段树的分治思想来拆分询问。

对同一层建个线段树,每个点存这个点所代表的区间的j的单调栈。

因为线段树只有 log 层,每层最多n个,所以总共是 O(nlogn)

接着把询问利用线段树拆成 log 段,每一段二分又是 log ,总复杂度是 O(nlog2n)

Code:

#include<cstdio>
#include<algorithm>
#define ll long long
#define ld long double
#define fo(i, x, y) for(ll i = x; i <= y; i ++)
#define fd(i, x, y) for(ll i = x; i >= y; i --)
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;

const ll N = 1e5 + 5;

ll n, a[N], b[N], f[N], l[N], ml;
ll s[N], g[N], h[N];
ll final[N], next[N], to[N], tot;
ll st, en, ans;

ld ji(ll k, ll b, ll l, ll r) {
    return (ld) (r - b) / (ld)(k - l);
}
ld jj(ll x, ll y) {
    return ji(h[x], g[x], h[y], g[y]);
}

int d[N * 30], d0, z[N];
struct tree {
    int l, r;
} t[N * 4];

void Build(int i, int x, int y) {
    t[i].l = d0 + 1; t[i].r = d0;
    fo(j, x, y) {
        while(t[i].l < t[i].r && jj(d[t[i].r - 1], d[t[i].r]) > jj(d[t[i].r], z[j]))
            t[i].r --;
        d[++ t[i].r] = z[j];
    }
    d0 = t[i].r;
    if(x == y) return;
    int m = x + y >> 1;
    Build(i + i, x, m); Build(i + i + 1, m + 1, y);
}

ll xx;

ll find(int i, int x, int y, int l, int r) {
    if(l > r) return 1e18;
    if(x == l && y == r) {
        int as = t[i].l;
        for(int p = t[i].l + 1, q = t[i].r; p <= q; ) {
            int m = p + q >> 1;
            if(jj(d[m - 1], d[m]) <= xx) as = m, p = m + 1; else q = m - 1; 
        }
        return h[d[as]] * xx + g[d[as]];
    }
    int m = x + y >> 1;
    if(r <= m) return find(i + i, x, m, l, r);
    if(l > m) return find(i + i + 1, m + 1, y, l, r);
    ll p = find(i + i, x, m, l, m), q = find(i + i + 1, m + 1, y, m + 1, r);
    return min(p, q);
}

int main() {
    scanf("%lld", &n);
    fo(i, 1, n) scanf("%lld", &a[i]);
    fo(i, 1, n) scanf("%lld", &b[i]);
    ml = 0; l[0] = 0;
    fo(i, 1, n) {
        if(a[i] - i < 0) {
            f[i] = 0; continue;
        }
        for(ll L = 1, r = ml; L <= r; ) {
            ll m = L + r >> 1;
            if(l[m] <= a[i] - i) f[i] = m, L = m + 1; else r = m - 1;
        }
        f[i] ++;
        if(f[i] > ml) l[++ ml] = a[i] - i;
        l[f[i]] = min(l[f[i]], a[i] - i);
    }
    fd(i, n, 1) next[++ tot] = final[f[i]], to[tot] = i, final[f[i]] = tot;
    fo(l, 1, ml) {
        z[0] = 0; d0 = 0;
        for(ll y = final[l - 1]; y; y = next[y])
            z[++ z[0]] = to[y];
        if(z[0]) Build(1, 1, z[0]);
        int st = 1, en = 0;
        for(ll ii = final[l]; ii; ii = next[ii]) {
            ll x = to[ii]; xx = x;
            while(en < z[0] && z[en + 1] < x) en ++;
            while(st <= en && a[z[st]] - z[st] > a[x] - x) st ++;
            s[x] = (a[x] - x >= 0 && l == 1) ? (b[x] + x * (x - 1) / 2) : 1e18;
            ll p = find(1, 1, z[0], st, en) + x * (x - 1) / 2 + b[x];
            s[x] = min(s[x], p);
            g[x] = s[x] - a[x] * x + (x + 1) * x / 2;
            h[x] = a[x] - x;
        }
    }
    ans = 1e18;
    fo(i, 1, n) if(f[i] == ml)
        ans = min(ans, s[i] + (n - i + 1) * a[i] + (n - i) * (n - i + 1) / 2);
    printf("%lld %lld", ml, ans);
}
本站公众号
   欢迎关注本站公众号,获取更多程序园信息
开发小院