UOJ #204. 【APIO2016】Boat dp+组合数学

8 篇文章 0 订阅

题意

给你n个区间,每个区间可以选的数的范围是[ai,bi]要你求非空上升子序列的个数
n<=500,ai,bi<=10^9

分析

这道题好题啊,但是我好弱啊
先说部份分,对于每个数我们可以拆成两维,(pos,num),然后就可以dp来做,每一次只把pos和num比较小的累计起来,然后简单的二维dp

考虑下正解,肯定是把很多区间给离散化一下,但是这里又害怕取到相同的数,我们可以先把bi+1,然后对于每个区间,就是取 [li,ri) [ l i , r i )

然后现在看怎么dp, f[i][j] f [ i ] [ j ] 表示第i个数必须放,放的高度在j区间里面,枚举上一个放的区间

f[i][j]=k=0i(p=0jf[k][p])calc(k+1,i,j) f [ i ] [ j ] = ∑ k = 0 i ( ∑ p = 0 j f [ k ] [ p ] ) c a l c ( k + 1 , i , j )

这里的 calc(k+1,i,j) c a l c ( k + 1 , i , j ) 表示 [k+1,i) [ k + 1 , i ) 的数不放,或者范围在第j个区间中,假设 [k+1,i) [ k + 1 , i ) 能放的有 m m 个,当前j这个区间的长度为l
calc(k+1,i,j)=i=1m+1(mi1)(li)

这里好像有范德蒙德卷积公式:
calc(k+1,i,j)=(m+lm+1) c a l c ( k + 1 , i , j ) = ( m + l m + 1 )

f[i][j]=k=0i(p=0jf[k][p])(m+lm+1) f [ i ] [ j ] = ∑ k = 0 i ( ∑ p = 0 j f [ k ] [ p ] ) ( m + l m + 1 )

发现这里括号内可以维护个前缀和,就没了

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1010;
const ll Mod = 1e9+7;
inline ll read()
{
  char ch=getchar(); ll p=0; ll f=1;
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}
ll n,a[N],b[N],h[N],c[N];
ll f[N][N],inv[N];
void upd(ll &x,ll y){x = (x+y) % Mod;}
int main()
{
  n = read();
  inv[0] = inv[1] = 1; for(ll i=2;i<=n+1;i++) inv[i] = (Mod - Mod / i) * inv[Mod % i] % Mod;
  for(ll i=1;i<=n;i++) a[i] = read() , b[i] = read() , b[i] ++ ,  h[i*2-1] = a[i],h[i*2] = b[i];
  sort(h+1,h+2*n+1);
  ll len = unique(h+1,h+2*n+1) - (h+1);
  for(ll i=1;i<=n;i++)
  {
    a[i] = lower_bound(h+1,h+len+1,a[i]) - h;
    b[i] = lower_bound(h+1,h+len+1,b[i]) - h - 1;
    // printf("%lld %lld\n",a[i],b[i]);
  }

  len --; for(ll i=1;i<=len;i++) c[i] = h[i+1] - h[i] ;

  for(ll i=0;i<=len;i++) f[0][i] = 1;
  for(ll i=1;i<=n;i++)
  {
    for(ll j=a[i];j<=b[i];j++)
    {
      ll m = 0; ll sum = c[j];
      for(ll k=i-1;k>=0;k--)
      {
        upd(f[i][j],f[k][j-1] * sum % Mod);
        if(a[k] <= j && j <= b[k])
        {
          m++;
          sum = sum * ((c[j] + m) % Mod) % Mod * inv[m+1] % Mod;
        }
      }
    }
    for(ll j=1;j<=len;j++) upd(f[i][j] , f[i][j-1]);
  }

  ll ans = 0;
  for(ll i=1;i<=n;i++)
  {
    // printf("%lld\n",f[i][len]);
    upd(ans , f[i][len]);
  }

  return printf("%lld\n",ans),0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值