题目链接: https://www.luogu.org/problemnew/show/P4093
佳媛姐姐过生日的时候,她的小伙伴从某宝上买了一个有趣的玩具送给他。玩具上有一个数列,数列中某些项的值可能会变化,但同一个时刻最多只有一个值发生变化。现在佳媛姐姐已经研究出了所有变化的可能性,她想请教你,能否选出一个子序列,使得在任意一种变化中,这个子序列都是不降的?请你告诉她这个子序列的最长长度即可 。
注意:
每种变化最多只有一个值发生变化!
每种变化最多只有一个值发生变化!
每种变化最多只有一个值发生变化!
重要的事情说三遍!
输入输出格式
输入格式:
输入的第一行有两个正整数n,m,分别表示序列的长度和变化的个数。接下来一行有n个数,表示这个数列原始的状态。接下来m行,每行有2个数x,y,表示数列的第x项可以变化成y这个值。1<=x<= n。
输出格式:
输出一个整数,表示对应的答案
1<=n,m<=1e5
输入样例:
3 4
1 2 3
1 2
2 3
2 1
3 4
输出样例:
3
在样例输入中,所有的变化是:
1 2 3
2 2 3
1 3 3
1 1 3
1 2 4
----------------------------------------------------------------------------------------------------------------------
很明显,虽然第i位可能会有很多种变化,但真正有用的只有原来的值a[i]、最大值maxx[i]、最小值minn[i]
此题先考虑普通动态规划,可以很容易得到状态转移方程:
设f[i]为以第i位结尾的最长长度,则有f[i]=max(f[j])+1 (j
如此,我们得到了一个O(n^2)的动规解法
但是对于1e5的数据,这显然是不能通过的
再考虑用数据结构优化DP:
对于每一个位置,把它视为坐标系上的一个点,坐标系的xy轴分别为a和maxx
那么我们对于每一个i,查找max(f[j])就变为一个二维求区间最大值问题
即:(0,0)至(minn[i],a[i])这个矩形中的点的最大值
每次求出f[i]后,再在坐标系中插入点(a[i],maxx[i])
至于二维求区间最大值问题,可以用二维线段树实现
不会二维线段树的请参考我的另一篇博客:http://blog.csdn.net/pb122401/article/details/79325126
具体代码如下:
#include
#define sz 100010
using namespace std;
long long n,m,cnt1,cnt2,id,root,ans;
long long minn[sz],maxx[sz],a[sz];
struct hh
{
long long lson,rson,rt;//rt是这个点的内层线段树的根节点编号
}out[sz<<1];//外层线段树,维护x坐标
struct hhh
{
long long lson,rson,v;//v为最大值
}in[sz<<7];//内层线段树,维护y坐标区间最大值
//注意:由于外层的每一个节点都对应着一棵内层线段树,in数组至少开sz<<5
long long query_in(long long k,long long l,long long r,long long x)
{
if (!k) return 0;//如果没有这个点答案就为0
if (x>=r) return in[k].v;
long long mid=(l+r)>>1;
if (x<=mid) return query_in(in[k].lson,l,mid,x);
return max(query_in(in[k].lson,l,mid,x),query_in(in[k].rson,mid+1,r,x));
}
long long query_out(long long k,long long l,long long r,long long x)
{
if (!k) return 0;//如果没有这个点答案就为0
if (x>=r) return query_in(out[k].rt,1,sz,minn[id]);
long long mid=(l+r)>>1;
if (x<=mid) return query_out(out[k].lson,l,mid,x);
return max(query_out(out[k].lson,l,mid,x),query_out(out[k].rson,mid+1,r,x));
}
void insert_in(long long &k,long long l,long long r,long long pos,long long v)
{
if (!k) k=++cnt2;//动态开点,不然会MLE
in[k].v=max(in[k].v,v);//更新区间最大值
if (l==r) return;
long long mid=(l+r)>>1;
if (pos<=mid) insert_in(in[k].lson,l,mid,pos,v);
else insert_in(in[k].rson,mid+1,r,pos,v);
}
void insert_out(long long &k,long long l,long long r,long long pos,long long v)
{
if (!k) k=++cnt1;//动态开点,不然会MLE
insert_in(out[k].rt,1,sz,a[id],v);//沿途每一个x区间的线段树都需要更新
if (l==r) return;
long long mid=(l+r)>>1;
if (pos<=mid) insert_out(out[k].lson,l,mid,pos,v);
else insert_out(out[k].rson,mid+1,r,pos,v);
}
int main()
{
scanf("%lld %lld",&n,&m);
for (long long i=1;i<=n;i++) scanf("%lld",&a[i]),minn[i]=maxx[i]=a[i];
for (long long i=1;i<=m;i++)
{
long long x,y;
scanf("%lld %lld",&x,&y);
minn[x]=min(minn[x],y);
maxx[x]=max(maxx[x],y);
}
for (id=1;id<=n;id++)
{
long long v=query_out(root,1,sz,a[id])+1;//动规
ans=max(ans,v);
insert_out(root,1,sz,maxx[id],v);
}
printf("%lld",ans);
}