给定一个长度为 n n n的序列,将其划分为两个严格单调递增的子序列,求这两个序列长度的最小差值,若无解,输出-1(保证合法的方案数不超过 1 0 18 10^{18} 1018)
数据范围: n ≤ 1 0 5 n\leq 10^5 n≤105
假设只有一种合法方案,设两个序列分别为 q 1 , q 2 q_1,q_2 q1,q2,能接到 q 1 q_1 q1就接 q 1 q_1 q1,否则就接 q 2 q_2 q2,接不了则无解,复杂度 O ( n ) O(n) O(n)
而我们知道,一个单调递增的序列,必然可以拆分成若干个连续的单调递增的子串(后面会给出证明),如果这些子串满足它们的方案唯一,那么我们就可以通过拼凑这些子串来得到最终答案
假设我们已经拆分出了 c n t cnt cnt个这样的子串,每个子串要么去到 q 1 q_1 q1,要么去到 q 2 q_2 q2,所以是一个0/1序列,方案最多为 2 c n t 2^{cnt} 2cnt种,题目已经给出答案最多有 1 0 18 10^{18} 1018种,所以 c n t cnt cnt最大也就是六十几,我们完全可以做 d p dp dp解决
问题是如何找到这些子串(如何划分)呢?
首先我们知道,如果存在 ∀ i < j \forall i
所以我们就要继续思考,继续发现对于 ∀ i < k < j \forall i
所以这样的划分块一定是连续的(也就是我们刚刚说的,它一定会是若干个子串)
那么,我们就要找出这样块的分界点,满足它前面的不可能和后面的连边,转换成几何语言就是找到所有的 i i i,满足 m a x ( a 1 ∼ i ) ≤ m i n ( a i + 1 ∼ n ) max(a_{1\sim i})\leq min(a_{i+1\sim n}) max(a1∼i)≤min(ai+1∼n),这样的 i i i必然是这些块的分界点,这个东西是可以 O ( n ) O(n) O(n)维护的
而我们又知道,每个这样的块中,它的答案是唯一的,我们只需要开两个队列,按照刚开始说的那种方法,求出每个块队列长度的差值 z [ i ] = q 1 . s i z e ( ) − q 2 . s i z e ( ) z[i]=q_1.size()-q_2.size() z[i]=q1.size()−q2.size()【这样子复杂度显然是 O ( n ) O(n) O(n)的】,然后做 d p dp dp
设 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个块,两个单调递增子序列长度的差值为 j j j的情况是否存在,则对于每一个块,我们可以选择放入 q 1 q_1 q1,也可以选择放入 q 2 q_2 q2,即
f [ i ] [ j ] = f [ i − 1 ] [ ∣ j + z [ i ] ∣ ] ∣ f [ i − 1 ] [ ∣ j − z [ i ] ∣ ] f[i][j]=f[i-1][|j+z[i]|]|f[i-1][|j-z[i]|] f[i][j]=f[i−1][∣j+z[i]∣]∣f[i−1][∣j−z[i]∣]
时间复杂度: O ( c n t n ) O(cntn) O(cntn),其中 c n t cnt cnt是一个上限为64的常数
#include
#include
#include
#include
#define N 100010
#define LL long long
using namespace std;int t,n,a[N],maxn[N],minn[N],st[N],ed[N],cnt,z[70];
queue<int>q1,q2;
bool flag,f[70][N];
inline LL read()
{
char c;LL d=1,f=0;
while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+c-48;
while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
return d*f;
}
signed main()
{
t=read();
while(t--)
{
n=read();maxn[0]=-1e9;minn[n+1]=1e9;
for(register int i=1;i<=n;i++) a[i]=read(),maxn[i]=max(maxn[i-1],a[i]);
for(register int i=n;i>=1;i--) minn[i]=min(minn[i+1],a[i]);
cnt=0;
for(register int i=1;i<=n;i++)
if(maxn[i]<=minn[i+1]||i==n)
{
st[++cnt]=ed[cnt-1]+1;
ed[cnt]=i;
}
if(cnt>67) {puts("-1");continue;}
flag=false;
for(register int i=1;i<=cnt;i++)
{
while(q1.size()) q1.pop();
while(q2.size()) q2.pop();
for(register int j=st[i];j<=ed[i];j++)
{
if(q1.empty()||a[j]>q1.back()) q1.push(a[j]);else
if(q2.empty()||a[j]>q2.back()) q2.push(a[j]);else
{flag=true;break;}
}
if(flag) break;
z[i]=q1.size()-q2.size();
}
if(flag) {puts("-1");continue;}
f[0][0]=1;
for(register int i=1;i<=cnt;i++)
for(register int j=0;j<=n;j++)
f[i][j]=f[i-1][abs(j+z[i])]|f[i-1][abs(j-z[i])];
for(register int j=0;j<=n;j++)
if(f[cnt][j]) {printf("%d\n",j);break;}
}
}