3999:孤独一生
传送门:然而并没有
题目大意:
思路:首先是O(n^2)的DP
设f[i][j]表示第1个集合结尾为i,第2个集合结尾为j,其中i>=j
分两种情况
f[i][j]=f[i-1][j]+|h[i]-h[i-1]| (i>j+1)(第2个集合结尾是j,那么j+1到i这一段都是第一个集合的)
=min(f[j][k]+|h[i]-h[k]|) (i=j+1) (其中第一个集合结尾已经是j了,而另一个集合结尾现在是i,原先则可以是任意的k<j,因为两个集合没有区别,i>=j,所以交换一下位置)
这时我们发现,只有当i=j+1是转移是不确定的
于是,我们考虑用f[i][i-1]来表示所有状态
记g[i]=f[i][i-1],sum[i]=Σ|h[j]-h[j-1]|(j<=i)就是高度差的前缀和
那么g[i]=min(g[j]+sum[i-1]-sum[j]+|h[i]-h[j-1]|)
答案就是min(g[i]+sum[n]-sum[i])
现在这个方程空间复杂度为O(n)但是时间复杂度还是O(n^2)
考虑如何优化
首先取绝对值,分离已知和未知
g[i]=h[i]+sum[i-1]+min(g[j]-sum[j]-h[j-1]) (h[i]>=h[j-1])
=sum[i-1]-h[i]+min(g[j]-sum[j]+h[j-1]) (h[i]<h[j-1])
开两个树状数组,以离散化后的h为下标,分别记录g[j]-sum[j]-h[j-1]和g[j]-sum[j]+h[j-1],每次在两个树状数组中查询最小值即可。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ls (p<<1) #define rs ((p<<1)|1) #define mid ((l+r)>>1) #define aabs(a) (a>0?a:-(a)) const int maxn=500010,maxt=maxn<<2; typedef long long ll; using namespace std; ll sum[maxn],h[maxn],g[maxn],ans=1ll<<55;int a[maxn],b[maxn],tmp[maxn],n,cnt;//a 离散化后的h struct bit{ ll mins[maxn]; void clear(){memset(mins,63,sizeof(mins));} void modify(int x,ll v){for (;x<=cnt;x+=x&(-x)) mins[x]=min(mins[x],v);} ll query(int x){ll res=1ll<<55;for (;x;x-=x&(-x)) res=min(res,mins[x]);return res;} }t1,t2; void init(){ scanf("%d",&n),n++,h[1]=a[1]=tmp[1]=0,t1.clear(),t2.clear(),memset(g,63,sizeof(g)); for (int i=2;i<=n;i++) scanf("%I64d",&h[i]),a[i]=tmp[i]=h[i],sum[i]=sum[i-1]+aabs(h[i]-h[i-1]); sort(tmp+1,tmp+1+n),cnt=unique(tmp+1,tmp+1+n)-tmp-1; for (int i=1;i<=n;i++) a[i]=lower_bound(tmp+1,tmp+1+cnt,a[i])-tmp,b[i]=cnt-a[i]+1; // for (int i=1;i<=n;i++) printf("fuck%d\n",a[i]); } void work(){ t1.modify(a[1],0),t2.modify(b[1],0); for (int i=2;i<=n;i++){ g[i]=min(g[i],h[i]+sum[i-1]+t1.query(a[i]-1)); g[i]=min(g[i],sum[i-1]-h[i]+t2.query(b[i])); t1.modify(a[i-1],g[i]-sum[i]-h[i-1]); t2.modify(b[i-1],g[i]-sum[i]+h[i-1]); } for (int i=1;i<=n;i++) ans=min(ans,g[i]+sum[n]-sum[i]); printf("%I64d\n",ans); } int main(){ init(),work(); return 0; }