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
#include
#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
#include
#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
#include
#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;
}