上升序列,指一个序列中每一个数都是递增的。最长上升子序列,就是一个序列中找出最长的一个每一个数都递增的子序列,其余的同理。比如: 12 9 31 42 23 19 21 73 12 9 31 42 23 19 21 73 12 9 31 42 23 19 21 73一个上升子序列: 12 31 73 12 31 73 12 31 73最长上升子序列: 12 19 21 73 12 19 21 73 12 19 21 73这个也是: 9 31 42 73 9 31 42 73 9 31 42 73所以,最长上升子序列不是唯一的。这个最长上升子序列的长度是 4 4 4。特殊的,空序列也是上升/不上升/下降/不下降子序列。
这里以上升子序列为例。可以思考,当一个序列结束的一个数小于该数,则该数可以加在这个序列上。于是,方案就出来了:设 f i f_i fi为以 i i i结束的最长上升子序列,那么 f i = max ( f j , j < i , a [ j ] < a [ i ] ) + 1 f_i=\max(f_j,jfi=max(fj,j<i,a[j]<a[i])+1。其余的同理。
这个题可以发现,如果从左到右就是一个最长上升子序列问题。那么从右到左呢?因为这个题数据不大,可以从右往左再做一遍。当然,也不难发现,如果从右往左是上升子序列,那么从左往右看就是下降子序列。于是,本题就能解决了。
#include
using namespace std;
const int NN=104;
int a[NN],f1[NN],f2[NN];
int main()
{
int k;
scanf("%d",&k);
while(k--)
{
int n,ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
f1[i]=f2[i]=1;
for(int j=1;j<=i-1;j++)
{
if(a[j]>a[i])
f1[i]=max(f1[i],f1[j]+1);
if(a[j]<a[i])
f2[i]=max(f2[i],f2[j]+1);
}
ans=max(ans,max(f1[i],f2[i]));
}
printf("%d\n",ans);
}
return 0;
}
这个题发现不太对,居然是先上升后下降的样子。但是我们可以把它拆成两个部分:上升部分和下降部分。所以我们可以枚举一个分界点,前面上升后面下降。这样就是以 i i i结束的最长上升和下降子序列问题,直接相加就行了。注意,中间点 i i i被计算了两次,要减一。但是上一题处理下降子序列的简便方式在这里并不适用,因为那个存的是 i i i开始的最长下降子序列,这个题是要求 i i i结束的,所以只能从右往左再算一遍。
#include
using namespace std;
const int NN=1004;
int a[NN],f1[NN],f2[NN];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int ans=0;
for(int i=1;i<=n;i++)
{
f1[i]=1;
for(int j=1;j<=i-1;j++)
if(a[j]<a[i])
f1[i]=max(f1[i],f1[j]+1);
}
for(int i=n;i>=1;i--)
{
f2[i]=1;
for(int j=i+1;j<=n;j++)
if(a[j]<a[i])
f2[i]=max(f2[i],f2[j]+1);
}
for(int i=1;i<=n;i++)
ans=max(f1[i]+f2[i]-1,ans);
printf("%d\n",ans);
return 0;
}
这个题和上一题没有任何区别。出队的越少留下的就越多,用 n n n减去最长的一个上升又下降的子序列即可。
#include
using namespace std;
const int NN=1004;
int a[NN],f1[NN],f2[NN];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int ans=0;
for(int i=1;i<=n;i++)
{
f1[i]=1;
for(int j=1;j<=i-1;j++)
if(a[j]<a[i])
f1[i]=max(f1[i],f1[j]+1);
}
for(int i=n;i>=1;i--)
{
f2[i]=1;
for(int j=i+1;j<=n;j++)
if(a[j]<a[i])
f2[i]=max(f2[i],f2[j]+1);
}
for(int i=1;i<=n;i++)
ans=max(f1[i]+f2[i]-1,ans);
printf("%d\n",n-ans);
return 0;
}
这个题目感觉和最长上升子序列没有关系,但是我们发现,两个相交当且仅当 u i > u j u_i>u_j ui>uj且 v i < v j v_i
#include
using namespace std;
const int NN=5004;
pair<int,int>a[NN];
int f[NN];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i].first,&a[i].second);
sort(a+1,a+1+n);
int ans=0;
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
if(a[j].second<a[i].second)
f[i]=max(f[i],f[j]+1);
ans=max(ans,f[i]);
}
printf("%d",ans);
return 0;
}
这个题从原来的求最长上升子序列变成了求和最大的上升子序列。之前新加一个数 a i a_i ai子序列的长度长度加 1 1 1,现在和却增加了 a i a_i ai,所以状态转移方程就变成了 max ( f j , a j < a i , j < i ) + a i \max(f_j,a_j
#include
using namespace std;
const int NN=1004;
int a[NN],f[NN];
int main()
{
int n,ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
f[i]=a[i];
for(int j=1;j<i;j++)
if(a[j]<a[i])
f[i]=max(f[i],f[j]+a[i]);
ans=max(ans,f[i]);
}
printf("%d",ans);
return 0;
}
这个题第一问就是一个典型的最长不上升子序列,直接 d p dp dp解决。那么第二问呢?可以记录每一个防御系统最后一次处理的高度,如果没有能处理新的的防御系统那么就新加一个。但是如果有选用哪个呢?选最小的,因为其他的还可以处理更高的,所以就用一个"最差的"就行了。
#include
using namespace std;
const int NN=1004;
int a[NN],f[NN],h[NN];
int main()
{
int i=0,ans=0,num=0;
h[0]=1e9;
while(scanf("%d",&a[++i])!=EOF)
{
f[i]=1;
for(int j=1;j<i;j++)
if(a[j]>=a[i])
f[i]=max(f[i],f[j]+1);
ans=max(ans,f[i]);
int k=0;
for(int j=1;j<=num;j++)
if(h[j]>=a[i]&&h[j]<h[k])
k=j;
if(!k)
{
num++;
k=num;
}
h[k]=a[i];
}
printf("%d\n%d",ans,num);
return 0;
}
这个题目可以考虑上一题那道题的贪心算法。但是这个题目可以选择上升或者下降,贪心无法在新加一个时确定是上升还是下降。于是我们再分析,发现题目中 n n n很小,不难想到可以暴搜。每次两种决策,在下降的选一个或者新加,在上升的选一个或者新加。这样分成了两种,就可以分别贪心了。注意,如果当前用的个数大于答案就可以退出了,因为最小的一定会是这个方案。用 u u u表示上升的序列的个数, d d d表示下降, t t t表示处理第几个。
#include
using namespace std;
const int NN=54;
int a[NN],up[NN],down[NN],n,ans;
void dfs(int u,int d,int t)
{
if(u+d>=ans)
return;
if(t==n)
{
ans=u+d;
return;
}
int k=u+1;
for(int i=1;i<=u;i++)
if(up[i]<a[t])
{
k=i;
break;
}
int temp=up[k];
up[k]=a[t];
dfs(max(u,k),d,t+1);
up[k]=temp;
k=d+1;
for(int i=1;i<=d;i++)
if(down[i]>a[t])
{
k=i;
break;
}
temp=down[k];
down[k]=a[t];
dfs(u,max(d,k),t+1);
down[k]=temp;
}
int main()
{
while(scanf("%d",&n)!=EOF&&n)
{
ans=1e9;
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
dfs(0,0,0);
printf("%d\n",ans);
}
return 0;
}
两个序列,分别选出一个子序列,要求这两个子序列相等,这就是公共子序列。最长公共子序列就是最长的一个。比如: 1 6 2 4 5 8 3 7 1 6 2 4 5 8 3 7 1 6 2 4 5 8 3 7 2 6 3 5 8 7 1 4 2 6 3 5 8 7 1 4 2 6 3 5 8 7 1 4一个公共子序列: 6 3 7 6 3 7 6 3 7一个最长公共子序列: 2 5 8 7 2 5 8 7 2 5 8 7这个也是: 6 5 8 7 6 5 8 7 6 5 8 7可见,最长公共子序列也不是唯一的。
首先,拿到这个题很容易把这个状态表示出来: f i , j f_{i,j} fi,j表示 s 1 1... i s1_{1...i} s11...i和 s 2 1... j s2_{1...j} s21...j的最长公共子序列长度。于是,我们就会出现几种情况:
然后我们根据这个来写代码是能得到正确答案的。但是仔细思考会发现一个事:有一些状态之间是包含了的。比如第一条,其实就被第二条和第三条包含了。因为之前 f i − 1 , j f_{i-1,j} fi−1,j在之前用转移三,则迭代成了 f i − 1 , j − 1 f_{i-1,j-1} fi−1,j−1,第三条同理。所以,我们可以去掉第一条。那么再考虑,发现第四条肯定是最优的,因为 f i − 1 , j f_{i-1,j} fi−1,j可以从 f i − 2 , j f_{i-2,j} fi−2,j、 f i − 1 , j − 1 f_{i-1,j-1} fi−1,j−1以及 f i − 2 , j − 1 + 1 f_{i-2,j-1}+1 fi−2,j−1+1迭代,其中第二个绝对不可能比转移四优,第三个其实少一个字符参加计算肯定也不会更有。第一个只要一出现 j − 1 j-1 j−1就一定不是最优,那么只能一直 f i − k , j f_{i-k,j} fi−k,j地迭代,所以最后会迭代到 f 0 , j f_{0,j} f0,j,为 0 0 0,肯定不是最优。所以,当两个相等直接计算第四个状态转移方程即可。
这个居然还加了一个上升的条件,那么怎么办呢?我们可以思考一下,这不就是最长上升子序列和最长公共子序列的结合体吗?所以我们可以结合一下,首先,最长上升子序列的状态是 f i f_i fi表示以 i i i结束的最长上升子序列,最长公共子序列的状态是 f i , j f_{i,j} fi,j表示 s 1 1... i s1_{1...i} s11...i和 s 2 1... j s2_{1...j} s21...j的最长公共子序列。所以,我们可以设一个状态 f i , j f_{i,j} fi,j表示用 s 1 1... i s1_{1...i} s11...i且以 s 2 j s2_j s2j结束的一个最长公共上升子序列。因为公共子序列是一样的,所以只用一个表示以某个结束即可。思考状态转移, f i , j f_{i,j} fi,j首先可以转移成不用 s 2 i s2_i s2i,则 f i , j = f i − 1 , j f_{i,j}=f_{i-1,j} fi,j=fi−1,j,但是第二个要求以 s 2 j s2_j s2j结束,所以不能不用 s 2 j s2_j s2j。于是就思考,在两个相等的时候怎么转移。其实就是这个公共子序列上一个是什么,然后再补一个新加入的我们。
所以,就可以三层循环,计算每一个 f i , j f_{i,j} fi,j。但是这个题 n n n最大是 3000 3000 3000,时间复杂度根本受不了。不难发现如果内层循环计算 s 2 s2 s2和枚举每一个 j j j,所以可以边循环边统计。因为 s 2 s2 s2放在内层循环,则数是实时变化的,但都要求等于 s 1 i s1_i s1i,所以改成和 s 1 i s1_i s1i比较是否上升即可。最后依次计算,如果两个相等就是可以进行第二条状态转移的,现在直接和统计的 max \max max比较即可。注意要先转移完了再更新 max \max max。
#include
using namespace std;
const int NN=3004;
int a[NN],b[NN],f[NN][NN];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
{
int maxx=1;
for(int j=1;j<=n;j++)
{
f[i][j]=f[i-1][j];
if(a[i]==b[j])
f[i][j]=max(f[i][j],maxx);
if(a[i]>b[j])
maxx=max(maxx,f[i-1][j]+1);
}
}
int res=0;
for(int i=1;i<=n;i++)
res=max(res,f[n][i]);
printf("%d",res);
return 0;
}