洛谷3971 BZOJ5158 TJOI2014 Alice and Bob 構造 貪心 拓撲排序 dp 堆
題意:
給你一個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;
}