【NOIP 模拟题】T2(next数组|线段树+最长上升子序列)

59 篇文章 0 订阅
45 篇文章 0 订阅

【题目大意】

  给定一个序列,包含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;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值