题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=6635
题意:给一个长度为N的序列,由1到N这N个正整数组成【即1到N各出现一次】,序列元素起始状态为冻结【不可用】,然后给出b序列代表解冻顺序【若b[1]=2,则a[2]解冻】,询问对于k从1到N,解冻前k个b[i]对应的元素a [b [ i ] ],解冻元素中最长上升子序列的长度是多少。T<=3 N<=5e4
思路:考虑时间倒流,看作一个完整的排列按照一定顺序依次删除每个数,然后每次需要计算 LIS 的长度。 首先在 O(nlogn) 的时间内求出 LIS,并找到一个 LIS。当删除 x 时,如果 x 不在之前找 到的那个 LIS 中,那么显然 LIS 的长度是不会变化的,否则暴力重新计算出新的 LIS 即可。 因为数据随机,因此 LIS 的期望长度是 O(√n),删除的 x 位于 LIS 中的概率是 1 √n,也就 是说期望删除 O(√n) 个数才会修改 LIS,那么 LIS 变化的次数不会很多。 期望时间复杂度为 O(n√nlogn)。
【题解给出的标程用了很多个数组看着特别头疼(看不懂,所以我想自己找一个比较简单的做法,想了一晚上(太菜,终于想到了! ^ ^】
先给出AC代码:
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int maxx=5e4+10;
const int inf=0x7ffffff;
int t,n,pos,tot;
int a[maxx];
int b[maxx];
int f[maxx];
int pre[maxx];
int m[maxx];
int ans[maxx];
int xx[maxx]; ///离散化
inline void build(int w)
{
pos=0;
f[0]=a[0];
for(int i=1; i<=n; i++)
{
if(a[i]==-1)
continue;
if(a[i]>f[pos])
{
f[++pos] = a[i];
pre[a[i]]=f[pos-1];
}
else
{
tot=lower_bound(f,f+pos+1,a[i])-f;
pre[a[i]]=f[tot-1];
f[tot]=a[i];
}
}
for(int i=f[pos],k=pos; k; k--,i=pre[i])
m[i]=w;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
memset(m,-1,sizeof(m));
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
xx[i]=a[i];
}
sort(xx+1,xx+n+1);
for(int i=1;i<=n;i++) a[i]=lower_bound(xx+1,xx+1+n,a[i])-xx;
int id=0;
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
build(0);
ans[n]=pos;
for(int i=n;i>1;i--)
{
if(m[a[b[i]]]!=id)
a[b[i]]=-1;
else
{
a[b[i]]=-1;
build(++id);
}
ans[i-1]=pos;
}
for(int i=1;i<=n;i++)
printf("%d%c",ans[i]," \n"[i==n]);
}
}
我对于贪心二分找最长上升子序列的方法做了一点添加,加了一个pre数组,记录每个元素它前面比它小的最近的元素是哪个,然后以f 数组的最后一个元素为循环起点,一直往前pre pos个位置,就得到最长上升子序列中的元素了,标记的时候用个小技巧就不用清空了。