[APIO2016]划艇

博客针对值域大的题目,提出将所有区间端点离散化的方法。设dp[i][j]表示前i个学校,第i个学校强制选且选在j区间的方案数,通过枚举第一个选在j区间的学校k进行转移。还探讨了组合数表达式的变化,最后得出O(n^3)dp的解法,并给出代码转载链接。

https://www.luogu.org/problemnew/show/P3643

题解

我们发现这道题的值域很大,所以考虑把所有区间端点离散化。

然后我们就设一个\(dp[i][j]\)表示前i个学校,第i个学校强制选,第i个学校选在了j这个区间的方案数。

转移我们可以枚举第一个选在j这个区间的学校k。
\[ dp[i][j]=blabla*\sum_{x=1}^{i-1}\sum_{y=1}^{j-1}dp[x][y] \]
考虑前面的东西怎么算。

如果每个数都可以选或者不选的话,那么答案就是\(\binom{n+len}{n}\)

现在我们强制头尾必须选。

我们原来是这样的表达式:
\[ C_{num}^0C_{len}^0+C_{num}^1C_{len}^1+C_{num}^2C_{len}^2+... \]
现在变成了:
\[ C_{num-2}^0C_{len}^2+C_{num-2}^1C_{len}^3+C_{num-2}^2C_{len}^4+... \]
看起来好像很复杂,但是如果考虑插板法的话,就是\(\binom{len+num-2}{num}\)

\(O(n^3)dp\)就好了。

代码

#include<bits/stdc++.h>
#define N 509
using namespace std;
typedef long long ll;
const int mod=1000000007;
ll dp[N][N<<1],num[N<<1],l[N],r[N],ni[N],n;
inline ll rd(){
    ll x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
inline ll power(ll x,ll y){
    ll ans=1;
    while(y){if(y&1)ans=ans*x%mod;x=x*x%mod;y>>=1;}
    return ans;
}
inline void MOD(ll &x){x=x>=mod?x-mod:x;}
int main(){
    n=rd();
    for(int i=1;i<=n;++i)ni[i]=power(i,mod-2);
    for(int i=1;i<=n;++i)l[i]=rd(),r[i]=rd()+1,num[++num[0]]=l[i],num[++num[0]]=r[i];
    sort(num+1,num+num[0]+1);
    num[0]=unique(num+1,num+num[0]+1)-num-1;
    for(int i=1;i<=n;++i){
        l[i]=lower_bound(num+1,num+num[0]+1,l[i])-num;
        r[i]=lower_bound(num+1,num+num[0]+1,r[i])-num;
    }
    for(int i=0;i<=num[0];++i)dp[0][i]=1;
    for(int i=1;i<=n;++i){
        dp[i][0]=1;
      for(int j=l[i];j<r[i];++j){
        ll len=num[j+1]-num[j];
        dp[i][j]=dp[i-1][j-1]*len%mod;
        ll su=len-1,cnt=1;
        for(int k=i-1;k>=1;--k)if(l[k]<=j&&r[k]>j){
           cnt++;
           su=su*(len+cnt-2)%mod*ni[cnt]%mod;
           if(!su)break;
           MOD(dp[i][j]+=dp[k-1][j-1]*su%mod);
        }
      }
      for(int j=1;j<num[0];++j)(dp[i][j]+=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+mod)%=mod;
    }
    printf("%lld",(dp[n][num[0]-1]-1+mod)%mod);
    return 0;
}

转载于:https://www.cnblogs.com/ZH-comld/p/10832223.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值