题意翻译
给你一个长度为
n
n
n的
B
B
B数组,
A
A
A表示
B
B
B数组复制
t
t
t遍后首尾相连后的数组,求
A
A
A的最长上升子序列 有
k
k
k组询问
m
a
x
b
maxb
maxb表示
B
B
B数组中最大的数.
原题链接.
解题思路
首先我们可以得到:当
t
t
t大于等于当前数列不同数字的个数时,答案就是不同数字的个数。
- 证明:
假设当前不同数字的个数为 3 3 3, 即 s u m = 3 sum=3 sum=3;
此时共有 t t t 个相同的序列,
那么我们在第 t t t 个序列选择第 t 小的数 ( t < = s u m ) (t<=sum) (t<=sum),
此时一定能保证生成含有 s u m sum sum 个元素的最长上升子序列。
图示:
接下来处理上述不成立的情况:
我们可以设一个
f
[
i
]
f[i]
f[i] 表示当前周期的最长序列长度,由于数据太大,所以要开滚动数组。
当枚举到当前周期时,我们考虑怎样转移。
最长上升子序列的动态转移方程为
f
[
i
]
=
m
a
x
(
f
[
j
]
)
+
1
(
j
<
i
,
a
[
j
]
<
a
[
i
]
)
。
f
[
i
]
=
m
a
x
(
f
[
j
]
)
+
1
(
j
<
i
,
a
[
j
]
<
a
[
i
]
)
f[i]=max(f[j])+1(j<i,a[j]<a[i])。f[i]=max(f[j])+1(j<i,a[j]<a[i])
f[i]=max(f[j])+1(j<i,a[j]<a[i])。f[i]=max(f[j])+1(j<i,a[j]<a[i])
其实这道题也是一样的,我们找到比
a
[
j
]
a[j]
a[j] 小的数,也就是
a
[
j
]
−
1
a[j]−1
a[j]−1,
我们就可以想到用树状数组维护,显然,我们用树状数组维护的是最大值。
代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,k,maxb,t,ans,sum,a[100010],tree[100010],b[100010],f[100010];
void find(int x,int y){
for(int i=x;i<=maxb;i+=i&(-i))
tree[i]=max(tree[i],y);//求区间最大值
}
int query(int x){
int bsy=0;
for(int i=x;i;i-=i&(-i))
bsy=max(bsy,tree[i]);//修改区间最大值
return bsy;
}
int main(){
scanf("%d%d%d%d",&k,&n,&maxb,&t);
while(k--){
int sum=0,ans=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(b[a[i]]!=k+1)
sum++;
b[a[i]]=k+1;//标记求sum,不用赋初值,因为每次k+1都不一样
}
if(t>=sum)//特判
{
printf("%d\n",sum);
continue;
}
memset(f,0,sizeof(f));
memset(tree,0,sizeof(tree));
for(int i=1;i<=t;i++)
{
for(int j=1;j<=n;j++)
{
int c=query(a[j]-1)+1;
if(c>f[j])
{
f[j]=c;
ans=max(ans,f[j]);
find(a[j],c);
}
if(ans>sum)//不能存在ans>sum的情况
break;
}
}
printf("%d\n",ans);
}
}