zoukankan      html  css  js  c++  java
  • 【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}|\),所以有

    \[f[i][j]=f[i-1][j]+|h_i-h_{i-1}|\ \ \ (j<i-1) \]

    如果\(j=i-1\),那么\(i\)有可能从\([1,j-1]\)中任意一个位置转移而来,所以有

    \[f[i][j]=f[j][k]+|h_i-h_k|\ \ \ (j=i-1,k\in[1,j-1]) \]

    时间复杂度\(O(n^2)\),期望得分\(50pts\)

    100pts

    显然最终的划分方式可以表示为若干个分为同一集合的区间连在一起。
    \(f[i]\)为处理到\(i\)\(i\)为一个新的区间的开始,也就是\(i\)在一个集合,\(i-1\)在另一个集合。那么我们枚举\(j\)表示\(j\sim i-1\)的数都划分到了除\(i\)外的另一个集合中,那么有方程

    \[f[i]=min(f[j]+sum[i-1]-sum[j]+|h_i-h_{j-1}|) \]

    其中\(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;
    }
    
  • 相关阅读:
    python自定义编写有关用户登录注册程序代码
    项目经理多年的经验之谈
    Linux虚拟机克隆后,启动系统发现网卡无法启动
    mysql出现服务器异常后,重启服务器后无法开启数据库处理方法
    计算机毕业四年,我都做了什么?
    个人Blog(采用Django+uwsgi+nginx)里面包含很多技术文章
    Delphi to C# Equivalancesdelphi和C#类似的地方
    Javascript浏览器关于scrollLeft,scrollTop的兼容性
    Mozilla Firefox15怎么样才能把标签页弄到下面去,就和360的一样,Mozilla Firefox15没有取消标签置顶这个选项……
    Delphi过程函数传递参数的几种方式
  • 原文地址:https://www.cnblogs.com/stoorz/p/12252180.html
Copyright © 2011-2022 走看看