【DP】hihocoder1596 Beautiful Sequence

该博客讨论了如何计算一个正整数序列在随机洗牌后成为'美丽序列'的概率,其中美丽序列定义为满足相邻元素之和大于等于两倍当前元素的条件。分析表明,序列中不能存在波峰,因此可以采用动态规划(DP)策略进行求解。博主通过将序列排序并分为两部分,然后建立状态转移方程,计算合法方案数。最终,概率乘以n!取模10^9+7得到答案。博主分享了在竞赛中解决此问题的经历,强调坚持到最后的重要性。
摘要由CSDN通过智能技术生成

描述

Consider a positive integer sequence a[1], …, a[n] (n ≥ 3). If for every 2 ≤ i ≤ n-1, a[i-1] + a[i+1] ≥ 2 × a[i] holds, then we say this sequence is beautiful.

Now you have a positive integer sequence b[1], …, b[n]. Please calculate the probability P that the resulting sequence is beautiful after uniformly random shuffling sequence b.

You’re only required to output (P × (n!)) mod 1000000007. (Obviously P × (n!) is an integer)


分析

答案其实求的就是合法的方案总数,
考虑它的限定条件: a[i1]+a[i+1]2a[i]
用图像来表示这个数列:
这里写图片描述
很容易发现,一旦存在一个波峰:
那么 a[i]>a[i1],a[i]>a[i+1],a[i]2>a[i1]+a[i+1] ,一定矛盾
因此,不可能存在波峰情况。

因此,这个数列的图像的大致趋势就可以确定了:
这里写图片描述
(图中的波谷可能在边界,此时就是一个递增/递减的序列了)

基于这种情况,我们就可以通过DP来解决问题了:
首先将b数组排序,再将其分成两组,
我们设dp[i][j][k][l]表示:
最大值所在的序列最大的数为 bi
最大值不在的序列最大的数为 bj
最大值所在的序列次大的数为 bk
最大值不在的序列次大的数为 bl

转移方程就很好想了:
枚举每一个状态,向其中加入 bi+1

dp[i+1][j][i][l]+=dp[i][j][k][l]

dp[i+1][i][j][k]+=dp[i][j][k][l]

注意:最小值不参与这些计算。

答案就是

0jn,0k<n0<l<jdp[n][j][k][l]

不用特别判断 j=k 等非法的情况,因为那些情况中dp值一定为0

因为最小值的取值是任意的,所以设有 K 个最小值,
那么答案就得再乘上K

初始状态 dp[1][1][0][0]=1 ,这个状态有点非法,但综合一下我考场上的几种写法,这样做相对来说后面好写一点。
因此,在转移的时候就要特别考虑到从边界转移的问题,所以要加几个恶心的max,来保证初始状态能够转移出去。
这里写图片描述


当时那场hihocoder我就是做了这道垃圾水题,就进了前20,拿了一个滴滴出行的玩偶。
第一次网赛拿奖品,还是挺开心的。这道题我当时调了很久,考试结束前18分钟才做出来(蒟蒻属性暴露无遗),最后半个小时的时候,真心觉得调不出来了,但想了想还是拼了一下,居然过了。不到最后一刻真不能认怂啊。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define SF scanf
#define PF printf
using namespace std;
void Read(int &x){
    char c;
    bool flag=0;
    while(c=getchar(),c!=EOF&&(c<'0'||c>'9')&&c!='-');
    if(c=='-'){ c=getchar();flag=1;}
    x=c-'0';
    while(c=getchar(),c!=EOF&&c>='0'&&c<='9')x=x*10+c-'0';
    if(flag)x=-x;
} 

#define MAXN 65
#define MOD 1000000007
long long dp[MAXN][MAXN][MAXN][MAXN];
int n,k,tot;
long long a[MAXN],b[MAXN];
long long jc(long long x){
    long long res=1;
    while(x){
        res*=x;
        res%=MOD;
        x--;
    }
    return res%MOD;
}
int main(){
    Read(n);
    for(int i=1;i<=n;i++)
        SF("%lld",&b[i]);
    sort(b+1,b+1+n);
    int st=1;
    for(;b[st]==b[1]&&st<=n;st++);
    a[1]=b[1];
    for(int i=2;i<=n-st+2;i++)
        a[i]=b[st+i-2];
    if(st>n){
        PF("%lld",jc(st-1));
        return 0;
    }
    n=n-st+2;
    dp[1][1][0][0]=1;
    for(int i=1;i<n;i++)
        for(int j=1;j<max(2,i);j++)
            for(int k=0;k<max(1,i);k++)
                for(int l=0;l<max(1,j);l++){
                    if((a[i+1]>=2*a[i]-a[k])||k==0){
                        dp[i+1][j][i][l]+=dp[i][j][k][l];
                        dp[i+1][j][i][l]%=MOD;  
                    }
                    if((a[i+1]>=2*a[j]-a[l])||l==0){
                        dp[i+1][i][j][k]+=dp[i][j][k][l];
                        dp[i+1][i][j][k]%=MOD;
                    }
                }
    /*for(int i=0;i<=n;i++)
        for(int j=0;j<=i;j++)
            for(int k=0;k<=i;k++)
                for(int l=0;l<=j;l++)
                    PF("(%lld %d %d %d %d)\n",dp[i][j][k][l],i,j,k,l);*/
    long long ans=0;
    for(int i=0;i<n;i++)
        for(int k=0;k<n;k++)
            for(int l=0;l<max(1,i);l++){
                ans+=dp[n][i][k][l];
                ans%=MOD;   
            }
    PF("%lld",(ans*jc(st-1))%MOD);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值