题解 CF1381B 【Unmerge】

cosmicAC

2020-07-22 18:09:56

Solution

有一个简单的DP,$f_{i,j,k}$($k\in \{0,1\}$)表示考虑排列$a$由第$i$位开始的后缀中一共有$j$个放进第$0$个数列里,(另$2n+1-i-j$个放在第$1$个数列里),并且第$i$个数放在第$k$个数列里,是否可能有解。 转移就是枚举第$i$个数后方第一个和$a_i$不在同一个序列里的位置$x$, $$f_{i,j,k}=f_{i,j,k} \vee f_{x,j-(1-k)(x-i),1-k}$$ 此处必须要满足$a_x\gt \max_{j=i}^{x-1}a_j$才能先选$\{a_i\dots a_{x-1}\}$再选$a_x$,并且选择的$x$必须让两个序列的长度都不会超过$n$。暴力DP复杂度$O(n^3)$,会TLE on test 2。 考虑优化这个DP。~~直接用`std::bitset`三方过4000~~ 发现$a_x\gt \max_{j=i}^{x-1}a_j$这个限制相当于一个单调栈,可以使用如下过程来维护所有合法的$x$: - 弹栈,直到当前栈顶$\gt a_i$或者栈空了 - 用栈中的所有元素更新$f_{i,*,*}$ - 将$a_i$压入栈中 然而暴力更新复杂度还是错的。观察一下dp转移下标中的$j-(1-k)(x-i)$,发现所有的第二维坐标形成了一条竖直线(当$k=1$)或对角线(当$k=0$),所以对于每一列和每一条副对角线,维护此直线之内所有合法$x$对应的$f$值之和,就可以支持$O(1)$的插入/删除/查询此直线中是否有$1$了。复杂度$O(n^2)$。 代码: ```cpp #include<bits/stdc++.h> using namespace std; int f[4010][2010][2],n,a[4010],T,sta[4010],vsm[2010],dsm[4010]; void ins(int x,int v){ for(int i=0;i<=n;i++)vsm[i]+=v*f[x][i][0]; for(int i=x;i<=2*n && i-x<=n;i++)dsm[i]+=v*f[x][i-x][1]; } int main(){ scanf("%d",&T); while(T--){ scanf("%d",&n); for(int i=1;i<=2*n;i++){ scanf("%d",a+i); for(int j=0;j<=n;j++)f[i][j][0]=f[i][j][1]=0; } for(int i=0;i<=n;i++)vsm[i]=0; for(int i=0;i<=2*n;i++)dsm[i]=0; for(int i=2*n,tp=0;i;i--){ if(i>n)f[i][2*n+1-i][0]=f[i][0][1]=1; while(tp&&a[sta[tp]]<a[i])ins(sta[tp--],-1); for(int j=max(n+1-i,0);j<=n&&j+i<=2*n+1;j++){ f[i][j][1]=vsm[j]; if(j+i<=2*n)f[i][j][0]=dsm[j+i]; } sta[++tp]=i;ins(i,1); } puts(f[1][n][0]|f[1][n][1]?"YES":"NO"); } return 0; } ```