【loj2567】【APIO2016】划艇

本文介绍了一种使用离散化DP方法解决特定数学问题的算法,即计算在给定条件下的单调上升序列数量。通过将数字范围离散化并分段,利用动态规划计算方案数,最终在O(n^3)的时间复杂度内得出答案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

\(N\)个位置,每个位置要么不选,要么选\([ a_i, b_i ]\)中的一个数;

问最后的单调上升序列(mod 1e9+7)有多少种;

\(1 \le N \le 500\)

题解

  • orz abclzr

  • 直接\(dp\)最后一位是什么数字的话只能得到31分

  • 将数字离散化分段,第\(i\)段为\([l_i,r_i)\),设\(f_{i,j}\)表示第i个位置选的数字在第j段的方案数(第0段表示没有)
    \[ f_{i,j} \ = \sum_{k=0}^{i-1} \sum_{l=0}^{j-1} f_{k,l} \times cal(k+1,i,j) \\ ans = \sum_{i=1}^n \sum_{j=1}^m f_{i,j} \\ \]

  • 其中 $ cal(l,r,x) $ 表示 $ [l,r) $ 都不选或者选在第j段并且单调上升的方案数

  • 设 $ [ l,r) $ 这里面有 $ S $ 个包含x区间,x区间的长度为 $ L $ 
    \[ cal(l,r,x) = \sum_{i=0}^{S}(^S_i)(^L_{i+1}) = \sum_{i=0}^{S}(^S_{S-i})(^L_{i+1}) \\ 思考组合意义:左边选S-i个再在右边选i+1个相当与一起选S+1个\\ cal(l,r,x) = (^{S+L}_{S+1}) \\ \]

  • 前缀和优化dp即可:\(O(n^3)\)  

    #include<bits/stdc++.h>
    #define ll long long 
    using namespace std;
    const int N=1010,mod=1e9+7;
    int n,tot,sub[N],L[N],R[N],ny[N],l[N],f[N][N];
    void inc(int&x,int y){x+=y;if(x>=mod)x-=mod;}
    int main(){
    //    freopen("boat.in","r",stdin);
    //    freopen("boat.out","w",stdout);
      scanf("%d",&n);
      for(int i=1;i<=n;++i){
          scanf("%d%d",&L[i],&R[i]);
          sub[++tot]=L[i];
          sub[++tot]=++R[i];
      }
      sort(sub+1,sub+tot+1);
      tot=unique(sub+1,sub+tot+1)-sub-1;
      for(int i=1;i<=n;++i){
          L[i]=lower_bound(sub+1,sub+tot+1,L[i])-sub;
          R[i]=lower_bound(sub+1,sub+tot+1,R[i])-sub;
      }
      ny[1]=1;for(int i=2;i<=n;++i)ny[i]=(ll)(mod-mod/i)*ny[mod%i]%mod;
      for(int i=0;i<tot;++i)f[0][i]=1;
      for(int i=1;i<tot;++i)l[i]=sub[i+1]-sub[i];
      for(int i=1;i<=n;++i){
          for(int j=L[i];j<R[i];++j){
              int C=l[j],a=l[j],b=1;
              for(int k=i-1;~k;--k){
                  inc(f[i][j],(ll)f[k][j-1]*C%mod);
                  if(L[k]<=j&&j<R[k])C=(ll)C*(++a)%mod*ny[++b]%mod;
              }
          }
          for(int j=1;j<tot;++j)inc(f[i][j],f[i][j-1]);
      }
      int ans=0;for(int i=1;i<=n;++i)inc(ans,f[i][tot-1]);
      cout<<ans<<endl;
      return 0;
    }
    //菜兔兔写的部分分
    #include<bits/stdc++.h>
    #define pb push_back
    using namespace std;
    const int N=510,M=2000010,mod=1e9+7;
    int n,a[N],b[N],len[N],sub[M],tot;
    int f[M],g[M];
    void inc(int&x,int y){x+=y;if(x>=mod)x-=mod;}
    int main(){
    //    freopen("boat.in","r",stdin);
    //    freopen("boat.out","w",stdout);
      scanf("%d",&n);
      for(int i=1;i<=n;++i){
          scanf("%d%d",&a[i],&b[i]);
          for(int j=a[i];j<=b[i];++j)sub[++tot]=j;
      }
      sort(sub+1,sub+tot+1);
      tot=unique(sub+1,sub+tot+1)-sub-1;
      f[0]=1;for(int i=0;i<=tot;++i)g[i]=1;
      for(int i=1;i<=n;++i){
          a[i]=lower_bound(sub+1,sub+tot+1,a[i])-sub;
          b[i]=lower_bound(sub+1,sub+tot+1,b[i])-sub;
          for(int j=a[i];j<=b[i];++j)f[j]=g[j];
          for(int j=a[i];j<=tot;++j)inc(g[j]=f[j],g[j-1]);
      }
      cout<<g[tot]-1<<endl;
      return 0;
    }

转载于:https://www.cnblogs.com/Paul-Guderian/p/10840912.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值