题意:
给你一个a数组,a中的每一个元素表示以该元素开头的在数组x中的最长上升子序列长度,要你自己构造x数组,使得对x数组求最长下降子序列后每个位置开始的最长下降子序列长度之和最大。n<=1e5,保证a可以用过一个
的排列得来。
题解:
一个看起来比较常规的思路是我们想办法构造出x,然后通过x数组来nlogn求出每个位置的最长下降子序列长度,最后加起来就行了。
那么我们接下来的任务是考虑构造x数组。首先我们感觉这个东西可能需要用到一些贪心的思想来构造。我们会发现,在我们构造的数组中如果有两个数字是相同的,那么它一定不优,原因是我们如果把两个相同的数字中前面的那一个变大1,其他更大的也依次加1,那么对于原来相同的靠前的那个数,它的最长下降子序列会变长,其他的位置的最长下降子序列会不变。于是我们会发现我们要构造的其实一定就是一个1到n的排列。我们可以由a数组中最长上升子序列的长度关系和刚才说的不存在相同,来确定一些位置之间的数字大小关系。我们考虑这个大小关系如果用连边表示,那么我们一定能得到一个DAG,我们连边的规则是从小的数向大的数连边。接下来我们对这个DAG进行拓扑排序,我们用一个大根堆维护目前在队列里的位置,然后我们从小到大赋值,如果有多个没有限制的位置的话,我们贪心地把最后一个位置设为这个最小值,答案会最优。然后拓扑排序的过程中就可以构造出这个x数组。构造出x数组之后就是nlogn求一个最长下降子序列了。
这里还是说一下怎么nlogn求最长下降子序列吧。我们的dp[i]的含义不再是i这个位置的最长下降子序列的长度了,而是长度为i的最长下降子序列的最后(其实是数组下标最小)一个数最小是多少。我们从n到1扫一遍,每次二分查找小于当前位置数值的最长下降子序列长度,然后答案就是那个长度再加1,然后尝试用当前数字更新这个长度下的最小结尾,这样就可以nlogn了。当然也可以用线段树和树状数组做。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,aa[100010],b[100010],dp[100010],pre[100010],hed[100010],cnt;
int du[100010];
long long ans;
priority_queue<int> q;
struct node
{
int to,next;
}a[200010];
inline void add(int from,int to)
{
a[++cnt].to=to;
a[cnt].next=hed[from];
hed[from]=cnt;
}
inline int check(int x)
{
int l=0,r=n,mid,gg=0;
while(l<=r)
{
mid=(l+r)>>1;
if(dp[mid]<x)
{
gg=mid;
l=mid+1;
}
else
r=mid-1;
}
return gg;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&aa[i]);
for(int i=1;i<=n;++i)
{
if(pre[aa[i]-1])
{
add(pre[aa[i]-1],i);
++du[i];
}
if(pre[aa[i]])
{
add(i,pre[aa[i]]);
du[pre[aa[i]]]++;
}
pre[aa[i]]=i;
}
cnt=0;
for(int i=1;i<=n;++i)
{
if(du[i]==0)
q.push(i);
}
while(!q.empty())
{
int x=q.top();
q.pop();
b[x]=++cnt;
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
--du[y];
if(du[y]==0)
q.push(y);
}
}
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for(int i=n;i>=1;--i)
{
int ji=check(b[i])+1;
dp[ji]=min(dp[ji],b[i]);
ans+=ji;
}
printf("%lld\n",ans);
return 0;
}