【NOIP 模拟题】T2(next数组|线段树+最长上升子序列)
【题目大意】
给定一个序列,包含n个元素a[i],求最长上升子序列长度。另读入一个类型opt,若opt=1,则输出方案数,否则,只输出最长上升子序列的长度。
【输入说明】
第一行两个整数n和opt。opt表示数据类型;第二行n个整数,n<=100000, a[i]<=100000
【输出说明】
第一行输出长度;如果opt=1,则第二行输出方案数,对123456789取模。
【输入样例】
5 1
1 3 2 5 4
【输出样例】
3
4
【题解】【next数组|线段树+最长上升子序列】
【一眼O(n²)dp,哎呀,这不水题?!看到数据范围,hhh~】
【O(nlogn)求最长上升子序列,借助栈来处理,每次新加入一个元素,如果大于栈顶元素,那么更新栈的大小,将当前元素放在栈顶;如果小于栈顶元素,二分查找比当前值大的最小值所在位置,并用当前元素替换原来的元素。最后输出栈中元素个数top。至此,opt≠1的情况完成!】
【麻烦就在于如何处理方案数,一时脑抽不会写线段树的窝,依赖“友好的数据”用奇怪的next数组方法搞掉了。 和在做图类问题时差不多,相当于从每个元素所在的位置向元素的值连边,边权为当前的方案数,然后每次新加入一个元素,从它的上一位的方案数转移过来,当每次枚举到一个比当前值大的数,就结束,并连边】
【由于数据太弱,这样水过了。然而其实应该用线段树或树状数组来搞,原理差不多,但实现更科学】
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 123456789
using namespace std;
int f[100010],top,g[100010],ans[100010];
int a[100010],nxt[100010],p[100010],num[100010],tot;
int n,opt;
inline void add(int x,int y,int val)
{
tot++; a[tot]=y; nxt[tot]=p[x]; p[x]=tot; num[tot]=val;
}
inline void solve(int x,int val)
{
if(x==1) {add(x,val,1); ans[x]++; return;}
int cnt=0;
for(int i=p[x-1];i!=-1;i=nxt[i])
if(a[i]>=val) break;
else cnt+=num[i],cnt%=mod;
ans[x]+=cnt; ans[x]%=mod;
add(x,val,cnt%mod);
}
inline void ask(int nm,int len,int x)
{
int l=1,r=len,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(f[mid]<x) l=mid+1;
else r=mid-1;
}
f[l]=x;
if(opt==1) g[nm]=l,solve(l,x);
}
int main()
{
//freopen("hamon9.in","r",stdin);
int i,j;
memset(p,-1,sizeof(p));
memset(nxt,-1,sizeof(nxt));
scanf("%d%d",&n,&opt);
for(i=1;i<=n;++i)
{
int x;
scanf("%d",&x);
if(x>f[top])
{
f[++top]=x;
if(opt==1) g[i]=top,solve(top,x);
}
else ask(i,top,x);
}
if(opt==1) printf("%d\n%d\n",top,ans[top]%mod);
else printf("%d\n",top);
return 0;
}
既然无能更改,又何必枉自寻烦忧