zoukankan      html  css  js  c++  java
  • AGC021F Trinity

    Description

    一个大小为 \(n\times m\) 的矩阵,每个格子可以为黑或白色,即共 \(2^{nm}\) 种形态,记 \(A_{1\sim n}\) 为每行第一个黑格子标号,如果没有就是 \(m+1\)\(B_{1\sim m}\) 为每列第一个格子标号,\(C_{1\sim m}\) 为每列最后一个格子标号,如果这列没有格子分别为 \(n+1/0\)

    求三元组 \((A,B,C)\) 的数量,\(n\le 8000,m\le 200\)

    Solution

    \(f_{j,i}\) 表示在 \(j\) 列上放过黑色格子,这些黑色格子所在行的并集大小为 \(i\)

    考虑新添加一列并放黑格子,这时候只能在最后一列上进行放置,让 \(f_{j,i}\) 转移到:

    • \(f_{j+1,i}\),转移系数就是这一列上黑格子方案,考虑可以放几个格子得到系数为 \(1+i+\binom{i}2\)

    • 枚举 \(k,f_{j+1,i+k}\),在原有 \(i\) 行基础上新 插入 \(k\) 行进来,这 \(k\) 行上的第 \(j+1\) 列都有黑色格子。这时由于原来的行上都有了黑色格子,那么这列上的黑色格子对其 \(A_i\) 并不造成影响

      考虑列上的格子对 \(B_{j+1},C_{j+1}\) 带来的影响,这等价于计算 \(B_{j+1}-1,C_{j+1}+1\) 的不同对数,这等价于选出 \(k+2\) 行并让最高/低两行作为作为白格子

      另一种做法是考察几个新增加行作为 \(B_{j+1}/C_{j+1}\),殊途同归,得数都是 \(\binom{i+k+2}{k+2}\)

    转移方程至此写作

    \[f_{j+1,i}=\left(\binom{i+1}2+1\right)f_{j,i}+\sum_{k<i}\binom{i+2}{k}f_{j,k} \]

    答案是 \(\sum\limits_{i=0}^n\binom ni f_{m,i}\)

    直接使用 \(\rm NTT\) 优化转移,时间复杂度 \(\Theta(nm\log n)\)

    故事到这里结束我就不更博了,所以设 \(f_{j,i}\)\(\rm EGF\)\(F_j(x)=\sum_{i\ge 0}\frac{f_{j,i}}{i!}x^i\) 丢进去继续化简(以下的 \(f_{j,i}\) 均表示原来的 \(\frac{f_{j,i}}{i!}\),与原来定义不同):

    \[\begin{aligned}&i!f_{j+1,i}=\left(\frac{i(i+1)}2+1\right)i!f_{j,i}+\sum_{k<i}\frac{(i+2)!}{(i+2-k)!k!}k!f_{j,k}\\ &f_{j+1,i}=\left(\frac{i(i+1)}2+1\right)f_{j,i}+\sum_{k\le i+2}\frac{(i+1)(i+2)}{(i+2-k)!}f_{j,k}-\frac{(i+1)(i+2)}2f_{j,i}-(i+1)(i+2)\left(f_{j,i+2}+f_{j,i+1}\right)\\ &f_{j+1,i}=\sum_{k\le i+2}\frac{(i+1)(i+2)}{(i+2-k)!}f_{j,k}-if_{j,i}-(i+1)(i+2)\left(f_{j,i+2}+f_{j,i+1}\right) \end{aligned}\]

    求和时枚举不到 \(i+2\) 就要减掉再变成 \(e^x\)\(\rm GF\) 运算细节最关键,需要时刻注意缺项的问题

    逐项考察,并使用导数进行系数配凑,第一项是:

    \[[x^i]F_{j+1}(x)=(i+2)(i+1)[x^{i+2}]eF_{j}(x)\rightarrow[x^i]F_{j+1}(x)=[x^i]\left(eF_{j}(x)\right)'' \]

    后面依次是 \(-xF'_{j}(x)-F''_{j}(x)-\left[xF''_{j}(x)+2F'_{j}(x)\right]\)

    前面的使用复合函数求导法则直接得到,整理得到下式,可能是微分方程:

    \[F_{j+1}(x)=e^{x}F_{j}(x)+(2e^x-x-2)F'_{j}(x)+(e^x-x-1)F''_j(x) \]

    解决这类问题的一个手段是 \(dp_{J,i,j}\) 表示 \(\sum_{i=0}^{m}\sum_{j=0}^m f_{J}x^je^{ix}\)

    根据方程进行转移,注意这里对 \((e^{jx})'=je^{jx}\),那么需要据此处理一阶/二阶导数的真正系数,和百鸽笼一题中没有导数的转移式子还不一样

    具体而言,由于导数是对整体进行的,那么以二阶导为例: \((x^je^{ix})''=i^2e^{ix}x^j+2ije^{ix}x^{j-1}+j(j-1)e^{ix}x^{j-2}\),所以系数需要附加

    最后统计答案时有个简单做法是不再逐 \([x^i]\) 求贡献再累加,而是再乘 \(e^{x}\) 将所有项都直接贡献到 \([x^n]\left(eF_m(x)\right)\)

    时间复杂度 \(\Theta(m^3+n)\),完全可以出到 \(m=300,n=10^7,mod=10^9+7\)

    实现的时候由于系数比较多容易漏,需要认真仔细一些

    这是让我学会函数系数在 \(\rm GF\) 下求导等计算的一题,简单记录纪念一下

    Code

    代码里面变量名称和叙述中有所不同,但是这不关键

    const int maxN=310;
    int f[2][maxN][maxN],N,M,cur,fac[8010],ifac[8010];
    struct node{int de,dx,dv;}; vector<node>vec[2];
    inline void push(int id,int be,int bx,int bv){
        for(auto t:vec[id]){
            ckadd(f[cur^1][be+t.de][bx+t.dx],mul(bv,t.dv));
        }
        return ;
    }
    signed main(){
        fac[0]=1;
        for(int i=1;i<=8000;++i) fac[i]=mul(fac[i-1],i);
        ifac[8000]=ksm(fac[8000],mod-2);
        Down(i,8000,1) ifac[i-1]=mul(ifac[i],i);
        N=read(); M=read(); 
        f[0][0][0]=1;
        vec[0].pb((node){1,0,2}); 
        vec[0].pb((node){0,1,mod-1}); 
        vec[0].pb((node){0,0,mod-2});
        
        vec[1].pb((node){1,0,1}); 
        vec[1].pb((node){0,1,mod-1}); 
        vec[1].pb((node){0,0,mod-1});
        //f[cur][i][j] -> e^{xi}x^j
        for(int m=1;m<=M;++m){
            for(int i=0;i<=m;++i){
                for(int j=0;j<=m;++j) if(f[cur][i][j]){
                    if(j>=1){
                        push(0,i,j-1,mul(f[cur][i][j],j));
                        push(1,i,j-1,mul(f[cur][i][j],2*i*j));
                    }
                    if(j>=2){
                        push(1,i,j-2,mul(f[cur][i][j],j*(j-1)));
                    }
                    ckadd(f[cur^1][i+1][j],f[cur][i][j]);
                    push(0,i,j,mul(f[cur][i][j],i));
                    push(1,i,j,mul(f[cur][i][j],i*i));
                }
            }
            rep(i,0,m) rep(j,0,m) f[cur][i][j]=0;
            cur^=1;
        }
        int ans=0;
        for(int i=0;i<=M;++i){
            for(int j=0;j<=N&&j<=M;++j) if(f[cur][i][j]){
                ckadd(ans,mul(f[cur][i][j],mul(ksm(i+1,N-j),ifac[N-j])));
            }
        }
        print(mul(ans,fac[N]));
        return 0;
    }
    

    玻璃碎片也能成为水晶雕塑,我摸索了这么久也最终会了 \(\rm GF\) 的计算方法,再次感谢 zero4338 的指导

  • 相关阅读:
    用servlet来实现验证码的功能
    Sqlite3 数据库
    xml解析
    Android .9文件
    AsyncTask
    Looper Handler
    URLConnection
    单例模式
    Httpclient访问网络
    json 解析
  • 原文地址:https://www.cnblogs.com/yspm/p/15773031.html
Copyright © 2011-2022 走看看