Description:
lanxisi带领着他的拆迁队来整治一个街道。这个街道由N个旧房子组成,从左到右编号为1..N。每个旧房子i有一个正整数的美观度Ai。
lanxisi希望整个街道从左到右美观度严格递增,也就是保证Ai<Aj(i<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]−j)∗i)
到这里可以同线段树的分治思想来拆分询问。
对同一层建个线段树,每个点存这个点所代表的区间的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);
}