zoukankan      html  css  js  c++  java
  • CF140E New Year Garland (计数问题)

    用$m$种颜色的彩球装点$n$层的圣诞树。圣诞树的第$i$层恰由$a_{i}$个彩球串成一行,且同一层内的相邻彩球颜色不同,同时相邻两层所使用彩球的颜色集合不 同。求有多少种装点方案,答案对$p$取模。

    好神的计数问题,zwz Orz

    题解思路来自黄学长hzwer的博客

    先只考虑在一行内的彩球的方案数

    定义$g[i][j]$表示一共有$i$个球串成一行,一共用了$j$种颜色的方案数

    因为所有颜色都是等价的,我们可以利用最小表示法来简化计数,比如让颜色编号为$x+1$的球第一次出现的位置,在颜色编号为$x$的球之前。实际的方案数是$g[i][j]cdot j!$

    这样递推关系就简单多了

    加入一个新颜色的球,$g[i][j]+=g[i-1][j-1]$

    加入一个旧颜色的球,颜色不能和第$i-1$个球相同,$g[i][j]+=(j-1)g[i-1][j]$

    $g[i][j]=g[i-1][j-1]+(j-1)g[i-1][j]$

    在考虑行行之间的影响

    定义$f[i][j]$表示前$i$行,其中第$i$行选了$j$种颜色的方案数

    如果没有相邻两行集合不同这种限制

    $f[i][j]=C_{m}^{j}cdot g[a_{i}][j]cdot sum f[i-1][k]$

    如果加上限制,

    $f[i][j]=C_{m}^{j}cdot g[a_{i}][j]cdot j!sum f[i-1][k]-g[a_{i}][j]cdot j!cdot f[i-1][j]$

    $=A_{m}^{j}cdot g[a_{i}][j]sum f[i-1][k]-g[a_{i}][j]cdot j!cdot f[i-1][j]$

    利用前缀和优化可以$O(1)$转移

    $f[i][j]$的状态数也仅仅是$O(sum a_{i})$,用滚动数组记录

    $A_{m}^{j}$和$j!$可以通过预处理得到

    总结:由于本题的模数是非质数,用组合数计数会让问题变得复杂,且时间复杂度很难保证。如果本题保证模数为质数,可能会有很多其他做法,比如组合数+容斥等等,但应该都没有官方题解的思路简洁。出题者似乎引导我们走向突破口——消去组合数,突破口在于通过化简,把组合数化成排列数和阶乘,排列数和阶乘即使在模数为非质数的情况下,也能在$O(n)$时间预处理

     1 #include <cmath>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <algorithm>
     5 #define N1 5010
     6 #define M1 1000010
     7 #define dd double
     8 #define ll long long 
     9 using namespace std;
    10 
    11 int gint()
    12 {
    13     int ret=0,fh=1;char c=getchar();
    14     while(c<'0'||c>'9'){if(c=='-')fh=-1;c=getchar();}
    15     while(c>='0'&&c<='9'){ret=ret*10+c-'0';c=getchar();}
    16     return ret*fh;
    17 }
    18 int n,m,mx,P;
    19 
    20 int f[2][N1],g[N1][N1],am[N1],mul[N1],a[M1];
    21 
    22 int main()
    23 {
    24     int i,j,x;
    25     scanf("%d%d%d",&n,&m,&x);
    26     for(i=1;i<=n;i++) scanf("%d",&a[i]), mx=max(mx,a[i]);
    27     const int p=x;
    28     g[0][0]=g[1][1]=1;
    29     for(i=2;i<=mx;i++) for(j=1;j<=i;j++) g[i][j]=(1ll*(j-1)*g[i-1][j]%p+g[i-1][j-1])%p;
    30     mul[0]=mul[1]=1; am[0]=1;
    31     for(i=1;i<=min(m,mx);i++) am[i]=1ll*am[i-1]*(m-i+1)%p;
    32     for(i=2;i<=mx;i++) mul[i]=1ll*mul[i-1]*i%p;
    33     int now=1,pst=0,snow=0,spst=1;
    34     f[pst][0]=1;
    35     for(i=1;i<=n;i++)
    36     {
    37         snow=0;
    38         for(j=1;j<=min(m,a[i]);j++)
    39             f[now][j]=(1ll*am[j]*g[a[i]][j]%p*spst%p-1ll*f[pst][j]*mul[j]%p*g[a[i]][j]%p+p)%p, (snow+=f[now][j])%=p;
    40         memset(f[pst],0,(min(m,a[i-1])+1)<<2); 
    41         swap(now,pst); swap(snow,spst);
    42     }
    43     printf("%d
    ",spst);
    44     return 0;
    45 }
  • 相关阅读:
    NSRunLoop的利用
    快速排序算法
    WebViewJavascriptBridge的暂时理解
    非常喜欢的一期《晓松奇谈》
    字符串正则替换replace第二个参数是函数的问题
    Model模型和Module模块的区别
    jQuery的extend方法的深层拷贝
    正则表达式学习记录
    select2初始化默认值
    增进编程语言学习速度的小技巧
  • 原文地址:https://www.cnblogs.com/guapisolo/p/10356184.html
Copyright © 2011-2022 走看看