【GMOJ3859】孤独一生
题目
题目链接:https://gmoj.net/senior/#main/show/3859
给出\(n\)个数\(h_i\),定义\(h_0=0\),将这\(n\)个数分别划分到两个集合中(集合可以为空),对于集合\(S\)的代价是\(\sum^{|S|}_{i=1}h_i-h_{i-1}\)。求两个集合的最小代价之和。
思路
50pts
我们发现转移时只与两个集合的最后一个元素有关,所以设\(f[i][j]\)表示两个集合的最后一个位置分别为\(i\)和\(j\)时,两个集合的代价和的最小值。要求\(i>j\)。
那么如果\(j<i-1\),显然\(f[i][j]\)只能由\(f[i-1][j]\)转移而来,代价为\(|h_i-h_{i-1}|\),所以有
如果\(j=i-1\),那么\(i\)有可能从\([1,j-1]\)中任意一个位置转移而来,所以有
时间复杂度\(O(n^2)\),期望得分\(50pts\)。
100pts
显然最终的划分方式可以表示为若干个分为同一集合的区间连在一起。
设\(f[i]\)为处理到\(i\)且\(i\)为一个新的区间的开始,也就是\(i\)在一个集合,\(i-1\)在另一个集合。那么我们枚举\(j\)表示\(j\sim i-1\)的数都划分到了除\(i\)外的另一个集合中,那么有方程
其中\(sum[i]=\sum^{i}_{j=1}|h_j-h_{j-1}|\)。
方程的意思就是前\(i\)个的最小代价=前\(j\)个的最小代价+\([j+1,i-1]\)划分为同一个的最小代价+\(h_i-h_{j-1}\)。
那么我们考虑将绝对值符号去掉,这样就得到了两个方程。而这两个方程都是仅关于\(i\)和\(j\)且取最小值。
那么可以用线段树维护,线段树的叶子结点\([i,i]\)记录\(f[i]-sum[i]\pm h_{i-1}\),分别对应两个方程。
那么我们在转移到\(i\)的时候,只要在线段树中分别取两个最小值计算答案即可。
但是这样我们必须保证线段树的每一段区间的\(h\)都是递增的,这样才可以在区间取最小值。
所以我们按\(h\)排序,然后给每个元素一个大小排名\(id\),在线段树插入时就在叶子\([id,id]\)插入,询问就分别在\([1,id][id,n]\)查询即可。
时间复杂度\(O(n\log n)\)。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=500010;
const ll Inf=1e18;
ll h[N],f[N],sum[N],ans;
int n,id[N];
struct node
{
int h,pos;
}b[N];
bool cmp(node x,node y)
{
return x.h<y.h;
}
struct Treenode
{
int l,r;
ll f1,f2;
};
struct SegTree
{
Treenode tree[N*4];
void build(int x,int l,int r)
{
tree[x].l=l; tree[x].r=r;
tree[x].f1=tree[x].f2=Inf;
if (l==r) return;
int mid=(l+r)>>1;
build(x*2,l,mid);
build(x*2+1,mid+1,r);
}
void pushup(int x)
{
tree[x].f1=min(tree[x*2].f1,tree[x*2+1].f1);
tree[x].f2=min(tree[x*2].f2,tree[x*2+1].f2);
}
void update(int x,int k,int p)
{
if (tree[x].l==k && tree[x].r==k)
{
tree[x].f1=f[p]-h[p-1]-sum[p];
tree[x].f2=f[p]+h[p-1]-sum[p];
return;
}
int mid=(tree[x].l+tree[x].r)>>1;
if (k<=mid) update(x*2,k,p);
else update(x*2+1,k,p);
pushup(x);
}
ll query(int x,int l,int r,int type)
{
if (tree[x].l==l && tree[x].r==r)
return type==1 ? tree[x].f1 : tree[x].f2;
int mid=(tree[x].l+tree[x].r)>>1;
if (r<=mid) return query(x*2,l,r,type);
if (l>mid) return query(x*2+1,l,r,type);
return min(query(x*2,l,mid,type),query(x*2+1,mid+1,r,type));
}
}Tree;
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%lld",&h[i]);
b[i].h=h[i]; b[i].pos=i;
sum[i]=sum[i-1]+abs(h[i]-h[i-1]);
}
sort(b+1,b+1+n,cmp);
for (int i=1;i<=n;i++)
id[b[i].pos]=i+1;
id[0]=1;
f[1]=h[1]; ans=sum[n];
Tree.build(1,1,n+1);
Tree.update(1,id[0],1);
for (int i=2;i<=n;i++)
{
long long x=Tree.query(1,1,id[i],1);
long long y=Tree.query(1,id[i],n+1,2);
f[i]=min(x+h[i]+sum[i-1],y-h[i]+sum[i-1]);
ans=min(ans,f[i]+sum[n]-sum[i]);
Tree.update(1,id[i-1],i);
}
printf("%lld",ans);
return 0;
}