bzoj 1485 //1485: [HNOI2009]有趣的数列 模拟/打表/卡特兰数

bzoj 1485 //1485: [HNOI2009]有趣的数列   //在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=1485

更多题解,详见https://blog.csdn.net/mrcrack/article/details/90228694BZOJ刷题记录

1.模拟

n=1,(1,2)    1解

n=2,(1,2,3,4),(1,3,2,4)    2解

n=3,(1,2,3,4,5,6),(1,2,3,5,4,6),(1,3,2,4,5,6),(1,3,2,5,4,6),(1,4,2,5,3,6)。   5解

n=4,已经够呛了,不过,到这,算是对题意了解了.

2.打表

对奇数位打表

//1485: [HNOI2009]有趣的数列
//在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=1485
//此文https://www.cnblogs.com/zhber/p/4181190.html思路不错,摘抄如下
/*
对应的5个有趣的数列分别为(1,2,3,4,5,6),(1,2,3,5,4,6),(1,3,2,4,5,6),(1,3,2,5,4,6),(1,4,2,5,3,6)。
这题非常蛋疼啊……
如果固定偶数位填入的数,那么各个奇数位也是唯一确定的。因为奇数位也要递增的。
以样例为例,偶数位有{2,4,6},{2,5,6},{3,4,6},{3,5,6},{4,5,6}这五种唯一的方案。
除了要满足严格递增以外,还有一个条件,就是a[i]>=2i。
这个很好yy,因为我们考虑的是偶数位的排列,前面肯定还有奇数位的数,而且大小比它小。
前面的偶数位的数都比它小,前面奇数位的数也比它小,所以前面的数都比它小。那么它至少是2i
问题转变为:有一个长度为n的序列,要往里头填1~2n的数,使得序列严格递增,且对于任意i,有a[i]>=2i
这我只能想到dp:f[i][j]表示前i个位置填了,第i个位置的数大小是j的方案数。转移随便yy,最后f[n][2n]即是所求。这样n^2只有50分。
*/
//此文https://www.cnblogs.com/shjrd-dlb/p/9048894.html代码写得不错
//处理奇数项,一旦确定,偶数项也确定.
//f[i][j]表示处理到第i位,尝试数字j,对应的所有解数.i代表奇数序列中的位置2019-9-15 8:23
#include <stdio.h>
#include <string.h>
#define maxn 1010
int n,P,f[maxn][maxn*2];
int main(){
    int i,j;
    scanf("%d%d",&n,&P);
    memset(f,0,sizeof(f));
    for(i=0;i<=n*2;i++)f[0][i]=1;//此处错写成for(i=1;i<=n*2;i++)f[0][i]=1;
    for(i=1;i<=n;i++)
        for(j=1;j<=n*2;j++)
            if(j<=2*i-1)f[i][j]=f[i][j-1]+f[i-1][j-1];
            else f[i][j]=f[i][j-1];
    printf("%d\n",f[n][n*2]);
    return 0;
}

对偶数位打表

//1485: [HNOI2009]有趣的数列
//在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=1485
//处理偶数项,一旦确定,奇数项也确定.
//f[i][j]表示处理到第i位,尝试数字j,对应的所有解数.i代表奇数序列中的位置2019-9-15 8:26
//50分代码,处理偶数项
#include <stdio.h>
#include <string.h>
#define maxn 1010
int n,P,f[maxn][maxn*2];
int main(){
    int i,j;
    scanf("%d%d",&n,&P);
    memset(f,0,sizeof(f));
    for(i=0;i<=n*2;i++)f[0][i]=1;//此处错写成for(i=1;i<=n*2;i++)f[0][i]=1;
    for(i=1;i<=n;i++)
        for(j=1;j<=n*2;j++)
            if(j>=2*i)f[i][j]=f[i][j-1]+f[i-1][j-1];
            else f[i][j]=f[i][j-1];
    printf("%d\n",f[n][n*2]);
    return 0;
}

打表结果如下

n=1,1

n=2,2

n=3,5

n=4,14

n=5,42

n=6,132

n=7,429

熟悉的,可以发现上述规律就是 卡特兰数h[n]=C(2n,n)/(n+1)=(2n)!/(n!(n+1)!).

3.正解  卡特兰数h[n]=C(2n,n)/(n+1)=(2n)!/(n!(n+1)!)

//此文https://blog.csdn.net/LWD_D/article/details/78313734思路不错,摘抄如下
/*
大体思路:

先用线性筛筛出每个数的质数以及最小质因数
接下来求cnt[x],cnt[x]意为因数x出现的次数。最终的结果是只有cnt[prime]有值,其他都没有值。我们再for一遍素数用快速幂就可以得出结果了。
求cnt[x]的具体操作:

从大到小for(m->1) ,根据该数是在分母还是分子来确定它是+1还是-1(也就是代码中的add(x,-1)还是add(x,1))
if(当i为素数时){cnt[i]++;}
if(当i不为素数时){
cnt[i]++;
cnt[d[i]]+=cnt[i],cnt[i/d[i]]+=cnt[i];
//把cnt[i]的值转移到cnt[d[i]]和cnt[i/d[i]]
cnt[i]=0;
//最后记得清空cnt[i]因为cnt[i]已经转移到cnt[它的因数]去了
}
但是i/d[i]有可能不是素数,没关系,从大到小for,之后又会for到i/d[i],又可以继续分解它了。
最后只有素数i的cnt[i]!=0
时间复杂度分析

处理cnt[i] O(n)
for素数o(n/logn) * 快速幂O(logn)=O(n)
时间复杂度共O(n)
*/
//此文https://www.cnblogs.com/sdfzsyq/p/10067304.html代码写得不错.
//样例通过,提交AC.2019-9-15 17:54
#include <stdio.h>
#include <string.h>
#define LL long long
#define maxn 1000010
int vis[maxn*2],prime[maxn*2/10],n,mod,cnt[maxn*2];
void linear_shaker(int x){//线性筛
    int i,j;
    memset(vis,0,sizeof(vis));
    for(i=2;i<=x;i++){
        if(!vis[i])vis[i]=i,prime[++prime[0]]=i;//质数
        for(j=1;i*prime[j]<=x;j++){
            vis[i*prime[j]]=prime[j];
            if(i%prime[j]==0)break;
        }
    }
}
LL quick_pow(LL a,LL b){//快速幂
    LL ans=1;
    while(b){
        if(b&1)ans=ans*a%mod;
        a=a*a%mod;
        b/=2;
    }
    return ans;
}
int main(){
    int i,j;
    LL ans=1;
    scanf("%d%d",&n,&mod);
    linear_shaker(2*n);
    for(i=2;i<=n;i++)cnt[i]=-1;//分母 n!
    for(i=n+2;i<=2*n;i++)cnt[i]=1;//分子(2*n)!/(n+1)!=(2*n)*(2*n-1)......(n+2)
    cnt[n+1]=0;
    for(i=2*n;i>1;i--){//逆序处理,从最大合数开始
        if(vis[i]==i)ans=ans*quick_pow(i,cnt[i])%mod;
        else cnt[vis[i]]+=cnt[i],cnt[i/vis[i]]+=cnt[i];//将i分解为vis[i]*(i/vis[i]),建议读者跟一下这个代码,这样方便看懂
    }
    printf("%lld\n",ans);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值