题目大意
给出长度为n的排列,将其划分成单调子序列(上升or下降),满足子序列个数不超过长度为n的所有排列的划分最大值,即可以不需要把当前的划分成最优
题解
错误的做法:每次找出最长的上升/下降子序列,原因同只划上升序列
像这样的数据就会挂掉:
1
35
30 18 12 5 21 23 13 31 33 19 27 28 22 7 3 17 6 8 1 35 10 32 2 15 29 34 24 9 26 25 11 16 4 14 20
因为不需要最优,所以先考虑求出f(n)
结论:(f(n)=min(k)-1,k(k+1)/2>n)
上界可以通过1,3,2,6,5,4,10,9,8,7,...来构出,手玩一下即可
每次先找出LIS,如果LIS长度>=k就直接把LIS提出来,提出来之后变成(k(k-1)/2>n),归纳得到次数为k-1
如果LIS长度<k,根据Dilworth定理可知 最少全集划分=最长反链,对于这题用人话来说就是 最少上升子序列划分=最长下降子序列
那么把上升子序列当成反链找下降子序列的划分,每次把最大元(没有i<j且ai<aj)找出来即可
证明感受一下,大概是找的多不如剩下一个小的
code
#include <bits/stdc++.h>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define low(x) ((x)&-(x))
#define ll long long
//#define file
using namespace std;
int a[100001],ed[100001],b[100001],c[100001],C[100001],Ans[100001],pre[100001],T,n,i,j,k,l,r,mid,ans,tot,t,mx;
bool bz[100001];
int main()
{
#ifdef file
freopen("CF1097E.in","r",stdin);
// freopen("CF1097E.out","w",stdout);
#endif
fo(i,1,100000)
{
while (k*(k+1)/2<=i) ++k;
Ans[i]=k-1;
}
scanf("%d",&T);
for (;T;--T)
{
scanf("%d",&n);memset(bz,0,n+1);
fo(i,1,n) scanf("%d",&a[i]);
ans=tot=0;
while (tot<n)
{
memset(c,0,(n+1)*4);t=0;
fo(i,1,n)
if (!bz[i])
{
if (!t)
t=1,c[1]=a[i],C[1]=i,pre[i]=0;
else
{
l=1;r=t;
while (l<r)
{
mid=(l+r)/2;
if (c[mid]<a[i])
l=mid+1; else r=mid;
}
if (c[l]<a[i]) ++t,++l;
c[l]=a[i],C[l]=i,pre[i]=C[l-1];
}
}
if (t<Ans[n-tot]+1)
{
while (tot<n)
{
t=0;
fo(i,1,n)
if (!bz[i])
{
while (t && a[i]>c[t]) --t;
++t,c[t]=a[i],C[t]=i;
}
++ans;
fd(i,t,1) b[++tot]=c[i],bz[C[i]]=1;
ed[ans]=tot;
}
break;
}
else
{
++ans;k=0;
for (i=C[t]; i; i=pre[i]) b[++tot]=a[i],bz[i]=1,++k;
ed[ans]=tot;
}
}
printf("%d
",ans);
fo(i,1,ans)
{
printf("%d ",ed[i]-ed[i-1]);
fd(j,ed[i],ed[i-1]+1)
printf("%d ",b[j]);
printf("
");
}
}
}