peiwenjun's blog 没有知识的荒原

ARC140 题解

A. Right String

题目描述

给定一个长为 \(n\) 的字符串 \(s\) ,字符集为全体小写字符。

你可以修改至多 \(k\) 个字符串,求 \(s\) 的循环节长度最小值。

数据范围

  • \(1\le n\le 2000,0\le k\le n\)

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{1024MB}\)

分析

枚举循环节长度 \(d\) ,判断是否可行。

将下标按\(\bmod d\) 分类,每一类保留出现字符最多的一种。

时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
int k,n,res;
char s[maxn];
int cnt[maxn][26];
bool check(int x)
{
    if(n%x) return false;
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++) cnt[i%x][s[i]-'a']++;
    int sum=0;
    for(int i=0;i<=x-1;i++)
    {
        int mx=0;
        for(int j=0;j<=25;j++) mx=max(mx,cnt[i][j]);
        sum+=mx;
    }
    return sum>=n-k;
}
int main()
{
    scanf("%d%d%s",&n,&k,s+1);
    for(int i=1;i<=n;i++)
        if(check(i))
        {
            res=i;
            break;
        }
    printf("%d",res);
    return 0;
}

B. Shorten ARC

题目描述

给定一个长为 \(n\) 字符串 \(s\) ,字符集为 \(\texttt{A,R,C}\)

你可以进行如下操作:

  • 在第奇数步将 \(\texttt{ARC}\) 替换成 \(\texttt R\)
  • 在第偶数步将 \(\texttt{ARC}\) 替换成 \(\texttt{AC}\)

求操作步数最大值。

数据范围

  • \(1\le n\le 2\cdot 10^5\)

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{1024MB}\)

分析

显然每个 \(\texttt R\) 对答案的贡献独立,记它的权值为两侧配对的 \(\texttt A\)\(\texttt C\) 的个数。

第奇数步会将某个 \(\texttt R\) 的权值减一,第偶数步会删去一个权值。

贪心,奇数步操作除 \(1\) 之外的最小权值,偶数步如果有 \(1\) 就删 \(1\) ,否则删除当前最小的权值,模拟即可。

时间复杂度 \(\mathcal O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int n,del,res;
char s[maxn];
priority_queue<int,vector<int>,greater<int>> q;
int main()
{
    scanf("%d%s",&n,s+1);
    for(int i=1;i<=n;i++)
    {
        if(s[i]!='R') continue;
        int j=1;
        while(s[i-j]=='A'&&s[i+j]=='C') j++;
        if((--j)!=0) q.push(j);
    }
    while(!q.empty())
    {
        int u=q.top();
        q.pop();
        if(u==1)
        {
            del++;
            continue;
        }
        if(res%2==0) res++,q.push(u-1);
        else
        {
            res++;
            if(del!=0) del--,q.push(u);
        }
    }
    printf("%d",res+del);
    return 0;
}

C. ABS Permutation (LIS ver.)

题目描述

定义一个排列 \(p\) 的快乐程度为 \(|p_i-p_{i+1}|\) 的最长严格上升子序列长度。

给定 \(p_1\) ,构造一个快乐程度最大的排列 \(p\)

数据范围

  • \(2\le n\le 2\cdot 10^5\)
  • \(1\le p_1\le n\)

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{1024MB}\)

分析

如果没有 \(p_1\) 的限制,显然上界为 \(n-1\)

对于偶数(以\(10\)为例):

5 6 4 7 3 8 2 9 1 10
6 5 7 4 8 3 9 2 10 1

对于奇数(以\(11\)为例):

6 5 7 4 8 3 9 2 10 1 11

所以如果 \(p_1\) 刚好是 \(1\sim n\) 的中位数,那么直接像上面这样构造即可。

如果不是,容易证明无法达到 \(n-1\) 的上界。

但可以做到 \(n-2\) ,将除掉 \(p_1\) 以外的数仿照上面的“螺旋”型排列即可。

时间复杂度 \(\mathcal O(n)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=200005;
int n,x;
int q[maxn];
void work(int n)
{
    int k=(n+1)/2;
    if(n%2==0) for(int i=1;i<=k;i++) q[2*i-1]=k-i+1,q[2*i]=k+i;
    else
    {
        q[1]=k;
        for(int i=1;i<=k;i++) q[2*i]=k-i,q[2*i+1]=k+i;
    }
}
int main()
{
    scanf("%d%d",&n,&x);
    if(n%2==0)
    {
        if(x==n/2)
        {
            work(n);
            for(int i=1;i<=n;i++) printf("%d ",q[i]);
            exit(0);
        }
        if(x==n/2+1)
        {
            work(n);
            for(int i=1;i<=n;i++) printf("%d ",n+1-q[i]);
            exit(0);
        }
        work(n-1);
        printf("%d ",x);
        for(int i=1;i<=n-1;i++) printf("%d ",q[i]+(q[i]>=x));
    }
    else
    {
        if(x==(n+1)/2)
        {
            work(n);
            for(int i=1;i<=n;i++) printf("%d ",q[i]);
            exit(0);
        }
        work(n-1);
        printf("%d ",x);
        for(int i=1;i<=n-1;i++) printf("%d ",q[i]+(q[i]>=x));
    }
    return 0;
}

D. One to One

题目描述

无向图 \(G\)\(n\) 条边(允许有重边和自环),第 \(i\) 条边连接 \((i,x_i)\) ,这张图的贡献是连通块个数。

给定部分 \(x_i\) ,求对剩下的 \(x_i\) 任意赋值,得到的所有 \(G\) 的贡献之和,对 \(998244353\) 取模。

数据范围

  • \(1\le n\le 2000\)
  • \(1\le x_i\le n\)\(x_i=-1\) ,后者表示 \(x_i\) 没有确定。

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{1024MB}\)

分析

给无向边定向,第 \(i\) 条边为 \(i\to x_i\)

由于每个点出度都为 \(1\) ,所以无向图连通块个数等于有向图连通块个数,进一步等于内向基环树的个数。

假设还有 \(cnt\) 条边没有确定,先把剩下的 \(n-cnt\) 条边连上,那么会形成一些连通块,并且每个连通块要么是树(\(x_{rt}=-1\)),要么是基环树。

对基环树计数还是不好做,但是注意到每个基环树恰有一个环,所以问题转化为对环计数。

一棵树向基环树连边不会产生贡献。

因为不会产生新的环。

每棵基环树的贡献是 \(n^{cnt}\)

总共 \(n^{cnt}\) 种赋值 \(x_i\) 的方式,而基环树不会再向外连边,自然也不会产生新的环。

至此基环树已经处理完了,只剩最后一种情况:树根向其他树连边。

由于树根连向树中的任意一个点本质相同,因此我们只需保留 \(sz\) 的信息(有 \(sz\) 种连边方式)。

假设有 \(m\) 棵树,第 \(i\) 棵树的大小为 \(a_i\)

考虑一个集合 \(S\)\(|S|=k\) ,如果最终方案里要把 \(a_{s_1},a_{s_2},\cdots,a_{s_k}\) 串成一棵基环树,我们来统计一下方案数。

首先可以把这 \(k\) 棵树的串联顺序任意排列,由于是串成环所以方案数为 \((k-1)!\)

这一棵树的根节点可以接到下一棵树的任何一个点上,方案数为 \(a_{nxt_i}\)

而其他树的根节点的 \(x\) 可以随意赋值,所以还要乘上 \(n^{m-k}\)

因此总方案数为 \((k-1)!n^{m-k}\prod_{i=1}^ka_{s_i}\)

问题转化为计算:

\[\sum_{S\subseteq\{1,2,\cdots m\}}(|S|-1)!n^{m-|S|}\prod_{k\in S}a_k\\ \]

动态规划, \(f_{i,j}\) 表示考虑前 \(i\) 个点,所有大小为 \(j\) 的集合 \(S\)\(n^{i-j}\prod\limits_{k\in S}a_k\) 之和。

转移是容易的,时间复杂度 \(\mathcal O(n^2)\)

如果扩展到 \(n\le 10^5\) 怎么做?

用生成函数的视角看待这个问题,令 \(f(x)=\prod_{i=1}^m(n+a_ix)\) ,则 \(\sum_{i=0}^m(i-1)![x^i]f(x)\) 即为所求。

暴力求 \(f(x)\) 仍是 \(\mathcal O(n^2)\) ,但分治 \(\texttt{NTT}\) 可以做到 \(\mathcal O(n\log^2n)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=2005,mod=998244353;
int m,n,cnt,res,tmp=1;
int f[maxn],x[maxn];
int a[maxn],sz[maxn],fac[maxn];
bool cir[maxn];
int dp[maxn][maxn];
int find(int x)
{
    if(f[x]==x) return x;
    return f[x]=find(f[x]);
}
void merge(int u,int v)
{
    u=find(u),v=find(v);
    if(u==v) cir[u]=true;
    else f[u]=v,sz[v]+=sz[u],cir[v]|=cir[u];
}
void add(int &x,long long y)
{
    x=(x+y)%mod;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) f[i]=i,sz[i]=1;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x[i]);
        if(x[i]!=-1) merge(i,x[i]);
        else cnt++;
    }
    for(int i=1;i<=cnt;i++) tmp=1ll*n*tmp%mod;
    for(int i=1;i<=n;i++)
    {
        if(find(i)!=i) continue;
        if(cir[i]) add(res,tmp);
        else a[++m]=sz[i];
    }
    fac[0]=dp[0][0]=1;
    for(int i=1;i<=m;i++)
        for(int j=0;j<=i;j++)
        {
            dp[i][j]=1ll*n*dp[i-1][j]%mod;
            if(j!=0) add(dp[i][j],1ll*dp[i-1][j-1]*a[i]);
        }
    for(int j=1;j<=m;j++) fac[j]=1ll*fac[j-1]*j%mod;
    for(int j=1;j<=m;j++) add(res,1ll*fac[j-1]*dp[m][j]);
    printf("%d",res);
    return 0;
}

E. Not Equal Rectangle

题目描述

给定 \(n,m\) ,构造一个 \(n\times m\) 的矩阵 \(a\) ,满足:

  • \(1\le a_{i,j}\le 25\)
  • \(\forall 1\le x_1<x_2\le n,1\le y_1<y_2\le m\),满足 \(a_{x_1,y_1},a_{x_1,y_2},a_{x_2,y_1},a_{x_2,y_2}\) 不全相等。

数据范围

  • \(2\le n,m\le 500\)

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{1024MB}\)

分享

题面误导人系列,如果把 \(25\) 改成 \(23\) 会好做很多。

分块,将 \(p^2\times p^2\) 的平面划分成 \(p\times p\) 个块,每个块的大小为 \(p\times p\)

给第 \((i,j)\) 个块的第 \((k,l)\) 个位置赋值 \((i\cdot j+k+l)\bmod p+1\) 即可。

严谨证明咕了。

#include<bits/stdc++.h>
using namespace std;
const int v=23,maxn=530;
int m,n;
int a[maxn][maxn];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<v;i++)
        for(int j=0;j<v;j++)
            for(int k=0;k<v;k++)
                for(int l=0;l<v;l++)
                    a[i*v+k][j*v+l]=(i*j+k+l)%v;
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
            printf("%d%c",a[i][j]+1,j==m-1?'\n':' ');
    return 0;
}

F - ABS Permutation (Count ver.)

题目描述

\(\forall k\in\{0,1\cdots,n-1\}\) ,求满足 \(\sum_{i=1}^{n-1}\big[|p_i-p_{i+1}|=m\big]=k\) 的排列 \(p\) 的个数。

数据范围

  • \(2\le n\le 2.5\cdot 10^5\)
  • \(1\le m\le n-1\)

时间限制 \(\texttt{8s}\) ,空间限制 \(\texttt{1024MB}\)

分析

恰好 \(=k\) 的方案数不好算,二项式反演转化为求钦定 \(k\) 个位置产生贡献的方案数 \(f_k\)

在值域上考虑这个问题,只有 \((i,i+m)\) 在原数组中相邻才会产生的贡献,因此我们可以先对 \(\bmod m\) 的每个剩余系分别算贡献,并且不同剩余系之间的贡献相互独立。

如果 \((i,i+m),(i+m,i+2m),\cdots\) 都能产生贡献,那就意味着 \(i,i+m,i+2m,\cdots\) 在数组中会排成一条链。贡献值为链长减一,并且如果链长 \(\ge 2\) ,这条链还可以翻转。

举个例子:假设 \(m=2\) ,则 \((1,3,5)\)\((5,3,1)\) 都能产生贡献。


带着 \(m\) 讲不清楚,先考虑 \(m=1\) 的情况。

先考虑一个朴素的 \(\texttt{dp}\)

\(dp_{i,j,0/1}\) 表示考虑数值(不是数组下标) \(1\sim i\) ,当前贡献为 \(j\)\((i-1,i)\) 是否产生贡献的方案数。

记录第三维状态是为了方便判断链长是否 \(\ge 2\) ,如果是则有两倍系数。

转移方程如下:

dp[i][j][0]=dp[i-1][j][0]+dp[i-1][j][1]
dp[i][j][1]=2*dp[i-1][j-1][0]+dp[i-1][j-1][1]

上面的 \(\texttt{dp}\) 考虑了每条链的内容,但把这些链放到数组中还需要考虑这些链之间的顺序。

贡献为 \(j\) 意味着有 \(n-j\) 条链,这些链可以随便排,方案数 \((n-j)!\)

综上所述, \(f_j=(n-j)!(f_{n,j,0}+f_{n,j,1})\)

至此,我们可以在 \(\mathcal O(n^2)\) 的时间内解决 \(m=1\) 的问题。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2005,mod=998244353;
int m,n;
int fac[maxn],inv[maxn];
int f[maxn],g[maxn],dp[maxn][maxn][2];
int qpow(int a,int k)
{
    int res=1;
    for(;k;a=1ll*a*a%mod,k>>=1) if(k&1) res=1ll*res*a%mod;
    return res;
}
void init(int n)
{
    fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
    inv[n]=qpow(fac[n],mod-2);
    for(int i=n;i>=1;i--) inv[i-1]=1ll*inv[i]*i%mod;
}
int c(int n,int m)
{
    return m>=0&&m<=n?1ll*fac[n]*inv[m]%mod*inv[n-m]%mod:0;
}
int main()
{
    scanf("%d%d",&n,&m),init(n);
    dp[1][0][0]=1;
    for(int i=2;i<=n;i++)
        for(int j=0;j<=i;j++)
        {
            dp[i][j][0]=(dp[i-1][j][0]+dp[i-1][j][1])%mod;
            if(j) dp[i][j][1]=(2ll*dp[i-1][j-1][0]+dp[i-1][j-1][1])%mod;
        }
    for(int j=0;j<n;j++) f[j]=1ll*fac[n-j]*(dp[n][j][0]+dp[n][j][1])%mod;
    for(int j=0;j<n;j++)
    {
        int res=0;
        for(int k=j;k<n;k++) res=(res+((k-j)&1?-1ll:1ll)*c(k,j)*f[k])%mod;
        printf("%d%c",(res+mod)%mod," \n"[j==n-1]);
    }
    return 0;
}

暂时不管后面的二项式反演, \(\texttt{dp}\) 转移怎么优化?

\(i\) 层只用到第 \(i-1\) 层的信息,矩阵乘法?

\(2n\times 2n\) 的转移矩阵,开玩笑呢?

但是,如果把 \(j\) 压到生成函数里面,会产生意想不到的效果:

\[F(i)=\begin{bmatrix} \sum_{j=0}^idp_{i,j,0}x^j&\sum_{j=0}^idp_{i,j,1}x^j\\ \end{bmatrix}\\ F(i)\times\begin{bmatrix} 1&2x\\ 1&x\\ \end{bmatrix}=F(i+1) \]

多项式矩阵快速幂,时间复杂度 \(T(n)=T(\frac n2)+\mathcal O(n\log n)\) ,解得 \(T(n)=\mathcal O(n\log n)\) ,只不过还有矩阵乘法的 \(8\) 倍常数。

优化二项式反演是个套路。

\[g_j=\sum_{i=j}^n(-1)^{i-j}\binom ijf_i=\frac1{j!}\sum_{i=j}^ni!f_i\cdot\frac{(-1)^{i-j}}{(i-j)!}\\ \]

\(\texttt{NTT}\) 计算差卷积即可。

至此,我们可以在 \(\mathcal O(n\log n)\) 的时间内解决 \(m=1\) 的问题。

#include<bits/stdc++.h>
#define poly vector<int>
using namespace std;
const int maxn=1<<19,mod=998244353;
int m,n;
int fac[maxn],inv[maxn];
int qpow(int a,int k)
{
    int res=1;
    for(;k;a=1ll*a*a%mod,k>>=1) if(k&1) res=1ll*res*a%mod;
    return res;
}
void init(int n)
{
    fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
    inv[n]=qpow(fac[n],mod-2);
    for(int i=n;i>=1;i--) inv[i-1]=1ll*inv[i]*i%mod;
}
int c(int n,int m)
{
    return m>=0&&m<=n?1ll*fac[n]*inv[m]%mod*inv[n-m]%mod:0;
}
namespace Poly
{
    int r[maxn],w[maxn];
    int add(int x,int y)
    {
        if((x+=y)>=mod) x-=mod;
        return x;
    }
    int dec(int x,int y)
    {
        if((x-=y)<0) x+=mod;
        return x;
    }
    int extend(int n)
    {
        return 1<<(__lg(n-1)+1);
    }
    void get_r(int n)
    {
        for(int i=0;i<n;i++) r[i]=(r[i>>1]>>1)|(i&1?n>>1:0);
    }
    void init(int n=maxn)
    {
        for(int k=2,m=1;k<=n;k<<=1,m<<=1)
        {
            w[m]=1;
            for(int i=m+1,x=qpow(3,(mod-1)/k);i<k;i++) w[i]=1ll*w[i-1]*x%mod;
        }
    }
    void print(poly a,int n)
    {
        a.resize(n);
        for(int i=0;i<n;i++) printf("%d%c",a[i]," \n"[i==n-1]);
    }
    void ntt(poly &a,int n,int op)
    {
        for(int i=0;i<n;i++) if(i<r[i]) swap(a[i],a[r[i]]);
        for(int k=2,m=1;k<=n;k<<=1,m<<=1)
            for(int i=0;i<n;i+=k)
                for(int j=i,*x=w+m;j<i+m;j++,x++)
                {
                    int v=1ll*a[j+m]**x%mod;
                    a[j+m]=dec(a[j],v),a[j]=add(a[j],v);
                }
        if(op==-1)
        {
            reverse(a.begin()+1,a.begin()+n);
            int v=qpow(n,mod-2);
            for(int i=0;i<n;i++) a[i]=1ll*a[i]*v%mod;
        }
    }
    poly operator+(poly a,poly b)
    {
        int n=max(a.size(),b.size());a.resize(n),b.resize(n);
        for(int i=0;i<n;i++) a[i]=add(a[i],b[i]);
        return a;
    }
    poly operator*(poly a,poly b)
    {
        if(a.empty()||b.empty()) return {};
        int n=a.size(),m=b.size(),len=extend(n+m-1);
        a.resize(len),b.resize(len),get_r(len),ntt(a,len,1),ntt(b,len,1);
        for(int i=0;i<len;i++) a[i]=1ll*a[i]*b[i]%mod;
        return ntt(a,len,-1),a.resize(n+m-1),a;
    }
}
using namespace Poly;
struct mat
{
    poly a,b,c,d;
};
mat operator*(mat x,mat y)
{
    return {x.a*y.a+x.b*y.c,x.a*y.b+x.b*y.d,x.c*y.a+x.d*y.c,x.c*y.b+x.d*y.d};
}
mat qpow(mat a,int k)
{
    mat res={{1},{},{},{}};
    for(;k;a=a*a,k>>=1) if(k&1) res=res*a;
    return res;
}
int main()
{
    scanf("%d%d",&n,&m),::init(n),Poly::init();
    mat a={{1},{0,2},{1},{0,1}},tmp=qpow(a,n-1);
    poly f=tmp.a+tmp.b,g(n+1);
    f.resize(n);
    for(int i=0;i<n;i++) f[i]=1ll*f[i]*fac[n-i]%mod*fac[i]%mod;
    for(int i=0;i<n;i++) g[n-i]=(i&1?mod-1ll:1)*inv[i]%mod;
    poly h=f*g;
    for(int i=0;i<n;i++) printf("%lld%c",1ll*h[n+i]*inv[i]%mod," \n"[i==n-1]);
    return 0;
}

问题并没有结束。如果 \(m\neq 1\) ,怎么办呢?

上面说过, \(\bmod m\) 的每个剩余系的贡献是独立的,又因为每个剩余系对应着一个多项式,所以直接把这 \(m\) 个多项式乘起来就行了。

n%m 个剩余系有 n/m+1 个元素, m-n%m 个剩余系有 n/m 个元素,多项式快速幂即可,时间复杂度 \(T(n)=T(\frac n2)+\mathcal O(n\log n)\) ,解得 \(T(n)=\mathcal O(n\log n)\)

完整时间复杂度 \(\mathcal O(n\log n)\)

#include<bits/stdc++.h>
#define poly vector<int>
using namespace std;
const int maxn=1<<19,mod=998244353;
int m,n;
int fac[maxn],inv[maxn];
int qpow(int a,int k)
{
    int res=1;
    for(;k;a=1ll*a*a%mod,k>>=1) if(k&1) res=1ll*res*a%mod;
    return res;
}
void init(int n)
{
    fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
    inv[n]=qpow(fac[n],mod-2);
    for(int i=n;i>=1;i--) inv[i-1]=1ll*inv[i]*i%mod;
}
int c(int n,int m)
{
    return m>=0&&m<=n?1ll*fac[n]*inv[m]%mod*inv[n-m]%mod:0;
}
namespace Poly
{
    int r[maxn],w[maxn];
    int add(int x,int y)
    {
        if((x+=y)>=mod) x-=mod;
        return x;
    }
    int dec(int x,int y)
    {
        if((x-=y)<0) x+=mod;
        return x;
    }
    int extend(int n)
    {
        return n!=1?1<<(__lg(n-1)+1):1;
    }
    void get_r(int n)
    {
        for(int i=0;i<n;i++) r[i]=(r[i>>1]>>1)|(i&1?n>>1:0);
    }
    void init(int n=maxn)
    {
        for(int k=2,m=1;k<=n;k<<=1,m<<=1)
        {
            w[m]=1;
            for(int i=m+1,x=qpow(3,(mod-1)/k);i<k;i++) w[i]=1ll*w[i-1]*x%mod;
        }
    }
    void print(poly a,int n)
    {
        a.resize(n);
        for(int i=0;i<n;i++) printf("%d%c",a[i]," \n"[i==n-1]);
    }
    void ntt(poly &a,int n,int op)
    {
        for(int i=0;i<n;i++) if(i<r[i]) swap(a[i],a[r[i]]);
        for(int k=2,m=1;k<=n;k<<=1,m<<=1)
            for(int i=0;i<n;i+=k)
                for(int j=i,*x=w+m;j<i+m;j++,x++)
                {
                    int v=1ll*a[j+m]**x%mod;
                    a[j+m]=dec(a[j],v),a[j]=add(a[j],v);
                }
        if(op==-1)
        {
            reverse(a.begin()+1,a.begin()+n);
            int v=qpow(n,mod-2);
            for(int i=0;i<n;i++) a[i]=1ll*a[i]*v%mod;
        }
    }
    poly operator+(poly a,poly b)
    {
        int n=max(a.size(),b.size());a.resize(n),b.resize(n);
        for(int i=0;i<n;i++) a[i]=add(a[i],b[i]);
        return a;
    }
    poly operator*(poly a,poly b)
    {
        if(a.empty()||b.empty()) return {};
        int n=a.size(),m=b.size(),len=extend(n+m-1);
        a.resize(len),b.resize(len),get_r(len),ntt(a,len,1),ntt(b,len,1);
        for(int i=0;i<len;i++) a[i]=1ll*a[i]*b[i]%mod;
        return ntt(a,len,-1),a.resize(n+m-1),a;
    }
}
using namespace Poly;
struct mat
{
    poly a,b,c,d;
};
mat operator*(mat x,mat y)
{
    return {x.a*y.a+x.b*y.c,x.a*y.b+x.b*y.d,x.c*y.a+x.d*y.c,x.c*y.b+x.d*y.d};
}
poly calc(int n)
{
    mat a={{1},{0,2},{1},{0,1}},res={{1},{},{},{}};
    for(int k=n-1;k;a=a*a,k>>=1) if(k&1) res=res*a;
    return res.a+res.b;
}
poly qpow(poly a,int k)
{
    poly res={1};
    for(;k;a=a*a,k>>=1) if(k&1) res=res*a;
    return res;
}
int main()
{
    scanf("%d%d",&n,&m),::init(n),Poly::init();
    poly f=qpow(calc(n/m+1),n%m)*qpow(calc(n/m),m-n%m),g(n+1);
    f.resize(n);
    for(int i=0;i<n;i++) f[i]=1ll*f[i]*fac[n-i]%mod*fac[i]%mod;
    for(int i=0;i<n;i++) g[n-i]=(i&1?mod-1ll:1)*inv[i]%mod;
    poly h=f*g;
    for(int i=0;i<n;i++) printf("%lld%c",1ll*h[n+i]*inv[i]%mod," \n"[i==n-1]);
    return 0;
}

总结

  • 场上 \(41\min\) 切了前\(3\)题,然后对着 \(\texttt D\)\(\texttt E\) 罚坐到比赛结束,感觉还是思维能力不足吧。
  • 这场的 \(\texttt F\) 题是真的毒瘤,动态规划 + 多项式矩阵快速幂 + 多项式快速幂 + 二项式反演差卷积,博主刚了一天才完全弄懂,另外 \(\texttt F\) 题题解长度大约占了本文一半。

posted on 2022-05-17 00:15  peiwenjun  阅读(17)  评论(0)    收藏  举报

导航