题目类型 dp, 数据结构[左偏树, 划分树, 函数式线段树等]
题目意思
给出一个最多包含 2000 个在 [0, 1e9] 范围内的数的数列 问把这个数列修改成一个非递增或非递减数列的最小代价
代价=原数列的每一个元素和修改后的数列的相应位置的元素的差的绝对值的和
解题方法
1. dp (离散化)
dp[i][j] : 前 i 个元素组成一个非递减数列且第 i 个元素的值为 j 的最小代价 看起来计算要O(n^3) 其实 O(n^2)就可以了
因为在计算 dp[i][j] 的时候可以直接用计算 dp[i][j-1]时得到的 (dp[i-1][0] -> dp[i-1][j-1]这些值中的最小值) 这个信息
非递增数列的代价同样可以O(n^2)时间内算出
2. 参考论文的解法 -> 左偏树的特点及其应用
2.1 知道方法后问题的重点就变成找某一个变化数列的中位数 论文的方法是用一棵左偏树(树根保存着最大值且为中位数)保存一个数列的左半部分, 需 要合并另一个数列的时候就把那个数列的左半部分形成的左偏树和当前左偏树合并 注意当合并的两个数列的元素个数都为奇数时合并后需要把根删 掉 (例如数列1 2 3和数列4 5 6合并后就应该得到一棵只包含3个元素的左偏树而不是4个)
2.2 其实就是找某一段区间的第 k 小数 (求中位数可以由数列长度算出k) 所以这一部分也可以用 划分树 或 函数式线段树做
参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
1.dp
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 2000 + 10;
const LL INF = 1LL<<60;
int a[maxn], b[maxn];
LL dp[2][maxn];
int Abs(int x) { return x < 0 ? -x : x; }
int main() {
// freopen("in", "r", stdin);
int n;
while(scanf("%d", &n) != EOF) {
for( int i=0; i<n; i++ ) { scanf("%d", &a[i]); b[i] = a[i]; }
sort(b, b + n);
int nb = unique(b, b + n) - b;
for( int i=0; i<nb; i++ ) dp[0][i] = Abs(b[i] - a[0]);
int now = 0;
for( int i=1; i<n; i++ ) {
LL nmin = INF;
for( int j=0; j<nb; j++ ) {
nmin = min(nmin, dp[now][j]);
dp[now^1][j] = nmin + Abs(b[j] - a[i]);
}
for( int j=0; j<nb; j++ ) dp[now][j] = INF;
now ^= 1;
}
LL ans = INF;
for( int i=0; i<nb; i++ ) ans = min(ans, dp[now][i]);
for( int i=0; i<nb; i++ ) dp[0][i] = Abs(b[i] - a[n-1]);
now = 0;
for( int i=n-2; i>=0; i-- ) {
LL nmin = INF;
for( int j=nb-1; j>=0; j-- ) {
nmin = min(nmin, dp[now][j]);
dp[now^1][j] = nmin + Abs(b[j] - a[i]);
}
for( int j=0; j<nb; j++ ) dp[now][j] = INF;
now ^= 1;
}
for( int i=0; i<nb; i++ ) ans = min(ans, dp[now][i]);
printf("%lld\n", ans);
}
return 0;
}
2.1 左偏树
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <cstdlib>
using namespace std;
const int maxn = 2000 + 10;
#define B printf("BUG\n");
int a[maxn];
int ans[maxn];
class Node {
public :
int v;
int h;
Node * ch[2];
Node(int v) : v(v) { ch[0] = ch[1] = NULL; h = 0; }
};
class Seg {
public :
int n;
Node * rt;
Seg(Node * rt, int n) : rt(rt), n(n) {}
};
class Leftist {
public :
stack<Seg *>S;
Node * merge(Node * t1, Node * t2) {
if(t1 == NULL) return t2;
else if(t2 == NULL) return t1;
if(t1->v < t2->v) swap(t1, t2);
if(t1->ch[1] == NULL) {
t1->ch[1] = t2;
if(t1->ch[0] == NULL) { swap(t1->ch[0], t1->ch[1]); t1->h = 0; }
else {
if(t1->ch[0]->h < t1->ch[1]->h) swap(t1->ch[0], t1->ch[1]);
t1->h = t1->ch[1]->h + 1;
}
}
else {
t1->ch[1] = merge(t1->ch[1], t2);
if(t1->ch[0] == NULL) { swap(t1->ch[0], t1->ch[1]); t1->h = 0; }
else {
if(t1->ch[0]->h < t1->ch[1]->h) swap(t1->ch[0], t1->ch[1]);
t1->h = t1->ch[1]->h + 1;
}
}
return t1;
}
void solve(int x) {
int num = 1;
Node * t1 = new Node(x);
while(!S.empty()) {
Seg * s2 = S.top();
if(t1->v < s2->rt->v) {
int flag = false;
if(num % 2 && s2->n % 2) flag = true;
t1 = merge(t1, s2->rt);
num += s2->n;
if(flag) {
Node * tmp = t1;
t1 = merge(t1->ch[0], t1->ch[1]);
delete tmp;
}
S.pop();
delete s2;
}
else break;
}
Seg * s2 = new Seg(t1, num);
S.push(s2);
}
void removetree(Node * rt) {
if(rt->ch[0] == NULL && rt->ch[1] == NULL) {
delete rt;
rt = NULL;
return ;
}
if(rt->ch[0]) removetree(rt->ch[0]);
if(rt->ch[1]) removetree(rt->ch[1]);
delete rt;
rt = NULL;
}
void remove() {
while(!S.empty()) {
removetree(S.top()->rt);
delete S.top();
S.pop();
}
}
};
int main() {
freopen("in", "r", stdin);
int n;
while(scanf("%d", &n) != EOF) {
Leftist lt;
for( int i=0; i<n; i++ ) {
scanf("%d", &a[i]);
lt.solve(a[i]);
}
int k = 0;
while(!lt.S.empty()) {
Seg * f = lt.S.top(); lt.S.pop();
for( int i=0; i<f->n; i++ ) {
ans[k++] = f->rt->v;
}
}
int res = 0;
for( int i=k-1; i>=0; i-- ) res += abs(ans[i] - a[n-i-1]);
lt.remove();
Leftist lt2;
for( int i=0; i<n/2; i++ ) swap(a[i], a[n-i-1]);
for( int i=0; i<n; i++ ) lt2.solve(a[i]);
k = 0;
while(!lt2.S.empty()) {
Seg * f = lt2.S.top(); lt2.S.pop();
for( int i=0; i<f->n; i++ ) {
ans[k++] = f->rt->v;
}
}
int res2 = 0;
for( int i=k-1; i>=0; i-- ) res2 += abs(ans[i] - a[n-i-1]);
lt2.remove();
res = min(res, res2);
printf("%d\n", res);
}
return 0;
}
2.2 划分树
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <cstdlib>
#include <algorithm>
using namespace std;
const int maxn = 2000 + 10;
#define B printf("BUG\n");
int a[maxn], b[maxn], A[maxn];
int ans[maxn];
int n, N;
class Seg {
public :
int l, r;
int nmid;
Seg(int l, int r, int nmid) : l(l), r(r), nmid(nmid) {}
};
int tree[20][2010], movel[20][2010];
class DivTree {
public :
void build(int rt, int l, int r) {
if(l == r) { tree[rt][l] = tree[rt-1][l]; return ; }
int mid = (l+r)>>1;
int c = 0, d = 0;
for( int i=l; i<=r; i++ ) {
int pos = tree[rt-1][i];
movel[rt][i] = i==l?0:movel[rt][i-1];
if(A[pos] <= mid) {
tree[rt][l+c] = pos;
c++; movel[rt][i]++;
}
else {
tree[rt][mid+1+d] = pos;
d++;
}
}
build(rt+1, l, mid); build(rt+1, mid+1, r);
}
int query_mid(int rt, int l, int r, int L, int R, int k) {
int mid = (l+r)>>1;
if(l == r) return a[tree[rt][l]];
int mlp1 = L==l?0:movel[rt][L-1];
int mtol = movel[rt][R] - mlp1;
if(mtol >= k) return query_mid(rt+1, l, mid, mlp1+l, mlp1+mtol-1+l, k);
else return query_mid(rt+1, mid+1, r, L-l-mlp1+mid+1, L-l-mlp1+(R-L+1-mtol)-1+mid+1, k-mtol);
}
}dt;
stack<Seg *>S;
void solve(int x) {
int num = 1;
Seg * t1 = new Seg(x, x, a[x]);
while(!S.empty()) {
Seg * s2 = S.top();
if(t1->nmid < s2->nmid) {
t1->nmid = dt.query_mid(1, 0, n-1, s2->l, t1->r, (t1->r-s2->l+1+1)/2);
//printf("x = %d nmid = %d %d-> %d\n", x+1, t1->nmid, s2->l, t1->r);
t1->l = s2->l;
S.pop();
delete s2;
}
else break;
}
S.push(t1);
}
bool cmp(const int & lhs, const int & rhs) {
return a[lhs] < a[rhs];
}
int main() {
freopen("in", "r", stdin);
while(scanf("%d", &n) != EOF) {
for( int i=0; i<n; i++ ) {
scanf("%d", &a[i]);
b[i] = i;
}
sort(b, b + n, cmp);
for( int i=0; i<n; i++ ) A[b[i]] = i;
for( int i=0; i<n; i++ ) tree[0][i] = i;
dt.build(1, 0, n-1);
for( int i=0; i<n; i++ ) solve(i);
int k = 0;
while(!S.empty()) {
Seg * f = S.top(); S.pop();
for( int i=0; i<f->r-f->l+1; i++ ) {
ans[k++] = f->nmid;
}
}
int res = 0;
for( int i=k-1; i>=0; i-- ) res += abs(ans[i] - a[n-i-1]);
while(!S.empty()) S.pop();
for( int i=0; i<n/2; i++ ) swap(a[i], a[n-i-1]);
for( int i=0; i<n; i++ ) b[i] = i;
sort(b, b + n, cmp);
for( int i=0; i<n; i++ ) A[b[i]] = i;
dt.build(1, 0, n-1);
for( int i=0; i<n; i++ ) solve(i);
k = 0;
while(!S.empty()) {
Seg * f = S.top(); S.pop();
for( int i=0; i<f->r-f->l+1; i++ ) {
ans[k++] = f->nmid;
}
}
int res2 = 0;
for( int i=k-1; i>=0; i-- ) res2 += abs(ans[i] - a[n-i-1]);
res = min(res, res2);
printf("%d\n", res);
}
return 0;
}
2.3 函数式线段树
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <cstdlib>
#include <algorithm>
using namespace std;
const int maxn = 2000 + 10;
#define B printf("BUG\n");
int a[maxn], b[maxn];
int ans[maxn];
int n, N;
class Seg {
public :
int l, r;
int nmid;
Seg(int l, int r, int nmid) : l(l), r(r), nmid(nmid) {}
};
class Node {
public :
Node * ch[2];
int n;
Node(int n) : n(n) { ch[0] = ch[1] = NULL; }
Node() {}
};
class SegTree {
public :
Node * tree[maxn];
void insert(Node *& rt, Node * pre, int l, int r, int x) {
if(x == 0) {
}
if(l == r) {
if(pre != NULL) rt->n += pre->n;
rt->n++;
return ;
}
int mid = (l+r)>>1;
if(x <= mid) {
rt->ch[0] = new Node(0);
if(pre == NULL) insert(rt->ch[0], NULL, l, mid, x);
else { rt->ch[1] = pre->ch[1]; insert(rt->ch[0], pre->ch[0], l, mid, x); }
}
else {
rt->ch[1] = new Node(0);
if(pre == NULL) insert(rt->ch[1], NULL, mid+1, r, x);
else { rt->ch[0] = pre->ch[0]; insert(rt->ch[1], pre->ch[1], mid+1, r, x); }
}
rt->n = (rt->ch[0] == NULL ? 0 : rt->ch[0]->n) + (rt->ch[1] == NULL ? 0 : rt->ch[1]->n);
}
void build() {
tree[0] = new Node(0);
for( int i=1; i<=n; i++ ) {
tree[i] = new Node(0);
int x = lower_bound(b, b + N, a[i-1]) - b;
insert(tree[i], tree[i-1], 0, N-1, x);
}
}
int query(Node * L, Node * R, int l, int r, int k) {
if(l == r) {
return b[l];
}
int tL = ((L == NULL || L->ch[0] == NULL) ? 0 : L->ch[0]->n);
int tR = ((R == NULL || R->ch[0] == NULL) ? 0 : R->ch[0]->n);
int tn = tR - tL;
if(k <= tn) return query(L==NULL?NULL:L->ch[0], R->ch[0], l, (l+r)/2, k);
else return query(L==NULL?NULL:L->ch[1], R->ch[1], (l+r)/2 + 1, r, k - tn);
}
int query_mid(int L, int R) { // 询问区间 [L, R] 的中位数
int mid = (R-L+1+1)>>1;
return query(tree[L], tree[R+1], 0, N-1, mid);
}
}st;
stack<Seg *>S;
void solve(int x) {
int num = 1;
Seg * t1 = new Seg(x, x, a[x]);
while(!S.empty()) {
Seg * s2 = S.top();
if(t1->nmid < s2->nmid) {
t1->nmid = st.query_mid(s2->l, t1->r);
t1->l = s2->l;
S.pop();
delete s2;
}
else break;
}
S.push(t1);
}
int main() {
//freopen("in", "r", stdin);
while(scanf("%d", &n) != EOF) {
for( int i=0; i<n; i++ ) {
scanf("%d", &a[i]);
b[i] = a[i];
}
sort(b, b + n);
int tb = unique(b, b + n) - b;
N = tb;
st.build();
for( int i=0; i<n; i++ ) solve(i);
int k = 0;
while(!S.empty()) {
Seg * f = S.top(); S.pop();
for( int i=0; i<f->r-f->l+1; i++ ) {
ans[k++] = f->nmid;
}
}
int res = 0;
for( int i=k-1; i>=0; i-- ) res += abs(ans[i] - a[n-i-1]);
while(!S.empty()) S.pop();
for( int i=0; i<n/2; i++ ) swap(a[i], a[n-i-1]);
st.build();
for( int i=0; i<n; i++ ) solve(i);
k = 0;
while(!S.empty()) {
Seg * f = S.top(); S.pop();
for( int i=0; i<f->r-f->l+1; i++ ) {
ans[k++] = f->nmid;
}
}
int res2 = 0;
for( int i=k-1; i>=0; i-- ) res2 += abs(ans[i] - a[n-i-1]);
res = min(res, res2);
printf("%d\n", res);
}
return 0;
}