BZOJ2090 POI2010 Monotonicity

POI 2010 专栏收录该内容
11 篇文章 2 订阅

POI2010 题解整理

Description

给出 N 个正整数a[1N],再给出 K 个关系符号(>、<或=)s[1k]。选出一个长度为 L 子序列,要求这个子序列的第i项和第 i+1 项的的大小关系为 s[(i1)%K+1] 。求出 L 的最大值。

Input

  • 第一行两个正整数,分别表示N K(N,K5105)

    • 第二行给出 N 个正整数,第i个正整数表示ai(ai106)
    • 第三行给出 K 个空格隔开关系符号(>、<或=),第i个表示 si

Output

  • 一个正整数,表示 L 的最大值。
  • 第二行L个整数,表示一种方案序列(Multiple Answers)。

Sample Input

7 3
2 4 3 1 3 5 3
< > =

Sample Output

6
2 4 3 3 5 3


Solution

一开始我是这么想的:对于当前每个值,枚举以它结尾可能的子序列长度,然后再判断前面是否有数能满足。这样的复杂度是 O(N2logN) ,搞笑一般的暴力。

在考虑优化的时候,当前的最优解分别从所有满足 A[j] (str[Len1] (<、>和=) ) A[i] {A[j],Len} 这个二元组中去更新当前的值,然后还要分别得到 A[i] 以三种符号结尾的最大长度。前面的更新只需要线段树维护(也就是下面的代码),而后面的更新就发现异常困难。

然而实际上并不需要考虑的这么麻烦,定义 dp[i] 表示以 Ai 作为结尾,最长的子序列长度。那么有个玄学的推论:

  • 该点的最优解一定是从前面某位置的最优解更新来的(即如果要从 Ai 来更新,则一定是所有以它为末尾元素的合法子序列中最长的那条,也即 dp[i] )。

讲着是推论其实我不太清楚是怎么推的


(16/10/4晚更新)终于基本搞清楚怎么推了,感谢焱犇神牛的证明。

为证明上述命题,我们需证下图所示的命题:

  • 对于点 i 可能继承的两条子序列Long Short ,当 j 所在的子序列位置不是最优解dp[j]=lengthLong时, dp[i] 不可能达到最优。

这里写图片描述

此时对于 Long 序列,设 j 出现的位置为x Short 序列中出现的位置为 y ,则:

  • y<x(注意 xy1 )。

    • dp[i]=y+1 ,且 i 无法从Long序列继承。
    • 根据上述条件,我们得到以下推论:

      1. 位置 x 与位置y的符号不同。
        假设其相同。由于两个位置上的值同为 aj ,那么显然 Long 序列是可以代替 Short 序列的, Short 序列不会比继承 Long 序列优。所以 aj ai 一定满足 y 位置上的符号关系,而不满足x位置上的符号关系。

      2. aj ai 一定不相等。
        假设其相等。若位置 x =,则一定选Long序列继承;若位置 x 不为=,由于相等, i 仍然可以从x1转移过来,由于 x1y ,所以选 Long 序列不会比 Short 差,不必从 Short 序列继承 y

      3. 位置y的符号一定不是 =
        因为由上述两推论可以知道,由于一定要从 Short 序列更新,且 ajai ,所以一定不是 =

      这里写图片描述

      我们可以在 Long 序列中找到位置 y 对应的原序列位置k。那么有结论:

      • ak ai 不满足位置 y 的符号关系。
        显然如果满足了那就不必从Short序列更新了。

      根据上述结论:

      • 假设 x 位置是小于或等于号:y位置的符号就是大于号(推论1,推论3),所以 ak<ai (推论4)。由于 x 位置无法被i继承,所以应该有 aj>ai (推论2)。紧接着我们就可以得到
        ak<ai<aj(k<j<i)
        又因为 k 位置的符号是大于号,所以必然能找到
        ak>al<aj(k<l<j)
        l 位置的符号必然是<。那么现在, dp[k]=y dp[l]=y+1 dp[i]=y+2>y+1
      • 假设 x 位置是小于或等于号,同理可证依旧有dp[i]>y+1

      综上所述,无论 Short y 位置的符号如何,我们总能在Long内找到一点,使得 i 可以从该点继承,并且该点可以继承Long内的 y 位置,保证最优结果一定y+2

      这TM能做


      后面就简单多了。

      dp[i]=maxdp[j]A[j]>A[i],str[dp[j]]=>A[j]=A[i],str[dp[j]]==A[j]<A[i],str[dp[j]]=<
      分别采用能够区间查询最值的数据结构维护一下就可以了,下面给出线段树和树状数组(写树状数组的一般都很神奇)的写法。

      线段树

      #include <bits/stdc++.h>
      #define M 500005
      #define P 1000000
      using namespace std;
      int A[M];char str[M],buf[5];
      struct Segment{
          int tree[P+5<<2];
          void up(int p){tree[p]=max(tree[p<<1],tree[p<<1|1]);}
          void update(int L,int R,int x,int val,int p){
              if(L==R){
                  tree[p]=max(tree[p],val);
                  return;
              }
              int mid=L+R>>1;
              if(x<=mid)update(L,mid,x,val,p<<1);
              else update(mid+1,R,x,val,p<<1|1);
              up(p);
          } 
          int query(int L,int R,int l,int r,int p){
              if(l>r)return 0;
              if(L==l&&R==r)return tree[p];
              int mid=L+R>>1;
              if(r<=mid)return query(L,mid,l,r,p<<1);
              else if(l>mid)return query(mid+1,R,l,r,p<<1|1);
              else return max(query(L,mid,l,mid,p<<1),query(mid+1,R,mid+1,r,p<<1|1));
          }
      }Mx,Mi;//> <
      int Past[P+5],dp[M];
      int main(){
          int n,m;
          scanf("%d %d",&n,&m);
          for(int i=1;i<=n;i++)scanf("%d",&A[i]);
          for(int i=1;i<=m;i++){
              scanf("%s",buf);str[i]=buf[0];
          }
      
          int ans=0,tot=0;
          for(int i=1;i<=n;i++){
              dp[i]=dp[Past[A[i]]]+1;
              dp[i]=max(dp[i],Mx.query(1,P,A[i]+1,P,1)+1);
              dp[i]=max(dp[i],Mi.query(1,P,1,A[i]-1,1)+1);
      
              if(ans<dp[i])ans=dp[i],tot=i;
      
              char c=str[(dp[i]-1)%m+1];
              if(c=='=')Past[A[i]]=i;
              else if(c=='>')Mx.update(1,P,A[i],dp[i],1);
              else Mi.update(1,P,A[i],dp[i],1);
          }
      //  for(int i=0;i<=n;i++)printf("%d\n",dp[i]);
          printf("%d\n",ans);
          stack<int>Ans;
          int val=ans,hre=A[tot];
          Ans.push(hre);
          for(;tot;--tot)
              if(dp[tot]+1==val){
                  char c=str[(dp[tot]-1)%m+1];
                  if(c=='='&&A[tot]==hre||c=='>'&&A[tot]>hre||c=='<'&&A[tot]<hre){
                      val--;hre=A[tot];Ans.push(hre);
                  }   
              }
          while(!Ans.empty())printf("%d%c",Ans.top(),Ans.size()==1?'\n':' '),Ans.pop();
      }

      树状数组

      #include <bits/stdc++.h>
      #define M 500005
      #define P 1000000
      using namespace std;
      int A[M];char str[M],buf[5];
      struct Binary_Indexed{
          int tree[P+5];
          #define lowbit(x) x&(-x)
          void add_Mi(int pos,int val){
              while(pos<=P){
                  tree[pos]=max(val,tree[pos]);
                  pos+=lowbit(pos);
              }
          }
          int query_Mi(int pos){
              int ans=0;
              while(pos){
                  ans=max(ans,tree[pos]);
                  pos-=lowbit(pos);
              }
              return ans;
          }
          void add_Mx(int pos,int val){
              while(pos){
                  tree[pos]=max(val,tree[pos]);
                  pos-=lowbit(pos);
              }
          }
          int query_Mx(int pos){
              int ans=0;
              while(pos<=P){
                  ans=max(ans,tree[pos]);
                  pos+=lowbit(pos);
              }
              return ans;
          }
      }Mx,Mi;
      int Past[P+5],dp[M];
      int main(){
          int n,m;
          scanf("%d %d",&n,&m);
          for(int i=1;i<=n;i++)scanf("%d",&A[i]);
          for(int i=1;i<=m;i++){
              scanf("%s",buf);
              str[i]=buf[0];
          } 
          int ans=0,tot=0;
          for(int i=1;i<=n;i++){
              dp[i]=dp[Past[A[i]]]+1;
              dp[i]=max(dp[i],Mx.query_Mx(A[i]+1)+1);
              dp[i]=max(dp[i],Mi.query_Mi(A[i]-1)+1);
      
              if(ans<dp[i])ans=dp[i],tot=i;
      
              char c=str[(dp[i]-1)%m+1];
              if(c=='=')Past[A[i]]=i;
              else if(c=='>')Mx.add_Mx(A[i],dp[i]);
              else Mi.add_Mi(A[i],dp[i]);
          }
      
          printf("%d\n",ans);
          stack<int>Ans;
          int val=ans,hre=A[tot];
          Ans.push(hre);
          for(;tot;--tot)
              if(dp[tot]+1==val){
                  char c=str[(dp[tot]-1)%m+1];
                  if(c=='='&&A[tot]==hre||c=='>'&&A[tot]>hre||c=='<'&&A[tot]<hre){
                      val--;hre=A[tot];Ans.push(hre);
                  }   
              }
          while(!Ans.empty())
              printf("%d%c",Ans.top(),Ans.size()==1?'\n':' '),Ans.pop();
      }
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值