[2018.5.22集训]棋盘-矩阵乘法-NTT-插值

Posted zltttt

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[2018.5.22集训]棋盘-矩阵乘法-NTT-插值相关的知识,希望对你有一定的参考价值。

题目大意

给出一个 $n*m$ 的棋盘,在上面放满白棋和黑棋,问使得仅由黑棋构成的四联通块的数量为 $k$ 的棋子放置方案的数量。
$ n \leq 3,m \leq 10^5,n*m \leq 10^5,k \leq n*m$,答案对 $998244353$ 取模。

题解

考虑朴素dp,设$f[i][j][s]$表示当前在第 $i$ 列,构成了 $j$ 个黑四联通块,当前行的涂色情况为 $s$ 的方案数。
$s$ 是一个状压之后的数,当 $n \leq 2$ 时 $s < 2^n$ ,而当$n=3$,由于特殊状态的存在,$s < 2^n +1$。
这里的特殊状态是指,形如下图的情况:

01111  01111
01000  00000
01111  01111

上面的两种 $1?0?1$ 状态是不同的,前一种上下的 $1$ 之间已经联通,而后一种没有,因此要分开讨论。

在 $n$ 的值不同时分别讨论,实现三个不同的dp即可。

然后你就有 $69$ 分了。

然而还是不够。
定义生成函数 $g(i,s)=\sum_{j}f[i][j][s]x^j$,那么答案即为 $\sum_{s}[x^k]g(m,s)$。
直接求这个生成函数就是暴力dp的做法。

考虑计算一些点值,并通过这些点值解出这个多项式。
对于单个点值的计算,显然可以矩阵快速幂计算出。
问题在于,如何将点值还原成一个多项式。

考虑人民的好朋友NTT的原理:
将原多项式使用DFT带入若干个单位根化成点值,进行完奇怪的操作后,再将利用了单位根计算出的点值用IDFT还原成多项式。
也就是说,如果能获得使用单位根计算出的点值,就能使用IDFT解出目标多项式。

那么就可以矩阵快速幂求出在单位根上的点值,并$IDFT$ 出原多项式即可~

代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;

typedef long long ll;
const int E=9;
const int N=100009;
const ll md=998244353;

int n,m,k,l;
ll w[N],a[N],rev[N];

struct matrix
{
    int lena,lenb;
    ll a[E][E];

    matrix(int _a=0,int b=0){memset(a,0,sizeof(a));lena=_a;lenb=b;}

    inline void e()
    {
        memset(a,0,sizeof(a));
        for(int i=0;i<lena;i++)
            a[i][i]=1;
    }

    matrix operator * (const matrix &o)const
    {
        matrix ret(lena,o.lenb);
        for(int i=0;i<lena;i++)
            for(int j=0;j<o.lenb;j++)
                for(int k=0;k<lenb;k++)
                    (ret.a[i][j]+=a[i][k]*o.a[k][j]%md)%=md;
        return ret;
    }
}base,mul;

inline matrix qpow(matrix a,int b)
{
    matrix ret(a.lena,a.lenb);ret.e();
    while(b)
    {
        if(b&1)ret=ret*a;
        a=a*a;b>>=1;
    }
    return ret;
}

inline ll qpow(ll a,ll b)
{
    ll ret=1;
    while(b)
    {
        if(b&1)ret=ret*a%md;
        a=a*a%md;b>>=1;
    }
    return ret;
}

inline void ntt(ll *a,int n)
{
    for(int i=0;i<n;i++)
        rev[i]=((rev[i>>1])>>1)|((i&1)*(n>>1));
    for(int i=0;i<n;i++)if(i<rev[i])swap(a[i],a[rev[i]]);
    for(int h=2;h<=n;h<<=1)
    {
        ll w=qpow(qpow(3,(md-1)/h),md-2);
        for(int j=0;j<n;j+=h)
        {
            ll wn=1ll,x,y;
            for(int k=j;k<j+(h>>1);k++)
            {
                x=a[k],y=wn*a[k+(h>>1)]%md;
                a[k]=(x+y)%md;a[k+(h>>1)]=(x-y+md)%md;
                wn=wn*w%md;
            }
        }
    }
    for(int inv=qpow(n,md-2),i=0;i<n;i++)
        a[i]=a[i]*(ll)inv%md;
}

inline ll m1(ll w)
{
    ll m1[2][2]=
    {
        {1,w},
        {1,1},
    };
    int sz=2;
    base=matrix(sz,sz);base.e();
    mul=matrix(sz,sz);
    for(int i=0;i<sz;i++)
        for(int j=0;j<sz;j++)
            mul.a[i][j]=m1[i][j];
    base=base*qpow(mul,m);
    ll ret=0;
    for(int i=0;i<sz;i++)
        ret+=base.a[0][i];
    return ret;
}

inline ll m2(ll w)
{
    ll m2[4][4]=
    {
        {1,w,w,w},
        {1,1,w,1},
        {1,w,1,1},
        {1,1,1,1},
    };
    int sz=4;
    base=matrix(sz,sz);base.e();
    mul=matrix(sz,sz);
    for(int i=0;i<sz;i++)
        for(int j=0;j<sz;j++)
            mul.a[i][j]=m2[i][j];
    base=base*qpow(mul,m);
    ll ret=0;
    for(int i=0;i<sz;i++)
        ret+=base.a[0][i];
    return ret;
}

inline ll m3(ll w)
{
    ll m3[9][9]=
    {
        {1,w,w,w,w,w,w*w%md,0,w},
        {1,1,w,w,1,w,w,0,1},
        {1,w,1,w,1,1,w*w%md,0,1},
        {1,w,w,1,w,1,w,0,1},
        {1,1,1,w,1,1,w,0,1},
        {1,w,1,1,1,1,w,0,1},
        {1,1,w,1,1,1,1,0,qpow(w,md-2)},
        {1,1,w,1,1,1,0,1,1},
        {1,1,1,1,1,1,0,1,1}
    };
    int sz=9;
    base=matrix(sz,sz);base.e();
    mul=matrix(sz,sz);
    for(int i=0;i<sz;i++)
        for(int j=0;j<sz;j++)
            mul.a[i][j]=m3[i][j];
    base=base*qpow(mul,m);
    ll ret=0;
    for(int i=0;i<sz;i++)
        ret+=base.a[0][i];
    return ret;
}

int main()
{
    scanf("%d%d%d",&n,&m,&k);
    if(k>(n*m+1)/2)return puts("0"),0;
    for(l=1;l<=((n*m+1)>>1);l<<=1);

    w[0]=1;w[1]=qpow(3,(md-1)/l);
    for(int i=2;i<l;i++)
        w[i]=w[i-1]*w[1]%md;

    for(int i=0;i<l;i++)
        a[i]=(n==1?m1:(n==2?m2:m3))(w[i]);
    ntt(a,l);

    printf("%lld\n",a[k]);
    return 0;
}

以上是关于[2018.5.22集训]棋盘-矩阵乘法-NTT-插值的主要内容,如果未能解决你的问题,请参考以下文章

JZOJ 6693. 2020.06.05省选模拟紫色彼岸樱推迟绽放 (自然数幂拆上升幂+组合意义矩阵乘法+NTT)

算法导论分治思想—大数乘法矩阵相乘残缺棋盘

bzoj4417[Shoi2013]超级跳马 矩阵乘法

Test 2018-07-23 二中集训

2019雅礼集训第一类斯特林数NTT&多项式permutation

BZOJ 4000: [TJOI2015]棋盘( 状压dp + 矩阵快速幂 )