P1410 子序列
dp
https://www.luogu.com.cn/problem/P1410
给定一个长度为 \(N\)( \(N\) 为偶数)的序列,问能否将其划分为两个长度为 \(N/2\) 的严格递增子序列
多测,\(N\le 2000\)
不看题解果然还是没能想出来/kk
容易想到的:\(f(i,j,p,q)\),表示前 \(i\) 个数,有一个长度为 \(j\) 的最长上升子序列(还有一个长度为 \(i-j\) 的),它们的结尾分别是 \(p,q\),这种情况是否存在
发现 \(p,q\) 其中之一等于 \(a_i\),所以减少一维:\(f(i,j,p)\) 表示前 \(i\) 个数,有一个长度为 \(j\) 的最长上升子序列
\(p\) 表示两个子序列其中结尾不等于 \(a_i\) 的那个是多少。存的值当然还是是否存在
换种说法,就是前 \(i\) 个数分成两个子序列,一个长度为 \(j\),并以 \(a_i\) 结尾,另一个以 \(p\) 结尾的情况是否存在
进一步发现,对于给定的 \(i,j\),为了让后面更多的情况成立,总是希望 \(p\) 更小
所以没有必要把每个 \(p\) 的对应状态都存下来
那么:\(f(i,j)\),表示前 \(i\) 个数,有一个长度为 \(j\) 的最长上升子序列并以 \(a_i\) 结尾,另一个子序列(长度 \(i-j\))最小以什么数结尾
如果任意的 \(p\) 都不能使其成立,那么赋为无穷
所以考虑转移
如果 \(a_i<a_{i+1}\),那么可以直接把 \(a_{i+1}\) 选那个长度为 \(j\) 的子序列里去,就是 \(f(i+1,j+1)=\min(f(i+1,j+1),f(i,j))\)
如果 \(f(i,j)<a_{i+1}\),可以把它选入长度为 \(i-j\) 的那个,\(f(i+1,i-j+1)=\min(f(i+1,i-j+1),a_{i})\)
第二种转移改变(或者说成交换)了原来的两个子序列是否以当前的 \(i\) 结尾
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
register int x=0;
register char c=std::getchar();
while(c<'0'||c>'9'){
if((c=std::getchar())==EOF) return -1;
}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return x;
}
int a[2005],f[2005][2005];
int n;
int main(){
n=read();
while(~n){
for(reg int i=1;i<=n;i++) a[i]=read();
a[n+1]=0;
std::memset(f,0x3f,sizeof f);f[1][1]=-1;
for(reg int i=1;i<=n;i++){
for(reg int j=1;j<=i;j++)if(f[i][j]!=0x3f3f3f3f){
if(a[i]<a[i+1]) f[i+1][j+1]=std::min(f[i+1][j+1],f[i][j]);
if(f[i][j]<a[i+1]) f[i+1][i-j+1]=std::min(f[i+1][i-j+1],a[i]);
}
}
std::puts(f[n][n/2]==0x3f3f3f3f?"No!":"Yes!");
n=read();
}
return 0;
}