1007
题意:有一个 维空间,第 维的范围为 。一个人在 ,要走到 。他的移动方式有 种,第 种用 表示,代表他在第 维上前进 个单位长。(当然他不能走出去)另外还给定了 个位置不能走。数据范围是 。注意PDF的数据范围错了,应该是 。
看到这个问题,我首先想到了一个类似的CF题。那个题是有一个巨大的棋盘,可以向上向右走,也是给定了 个位置不能走,问从左下角到右上角的方案数。这个题的解法是容斥:用 表示到达第 个不能走的位置,此前不经过别的这种位置的方案数。那么 。 表示第 个不能走的点, 表示从 走到 的方案数。把终点视作最后一个不能走的位置,跑这个 DP,就可以 过这道题。这个题的题号是 CF559C。
回到 1007,我们发现这个做法可以照搬下来,那么就剩下求 。题目给了总点数 ,稍微扩大一下,我们来求 到各个点的方案数。
如果熟悉多项式方面的事情,应该可以想到用生成函数处理。定义
那么 中项 的系数就表示移动 次到达 的方法数。我们的目标函数就是 ,它的各项系数是我们要求的答案。
做一个 维的多项式求逆多少是有点逆天的,所以换一种描述方式。对所有点按照字典序重编号,那么每个 其实都可以描述成一个下标的加法,只是有些位置因为越界了不能加。在多项式中对于越界,常常会选择“直接丢掉”的方式。但是如果只有这一个数的刻画也没法丢。
我对于这道题的思考到这里就进行不下去了。(所以我会认为前面都很简单doge
再来回顾一下刚才的定义:点 对应数 。(记 )这时直接对多项式求逆会出问题,也就是多项式乘法出了问题。
这时题解给出了一个相当妙的操作:增加一个变元 ,它在 项的次数是 。在做多项式乘法时,如果 乘以 能够贡献给 ,就要求 。观察到 (高斯函数的常用性质),所以 。那么我们只要做 的多项式乘法,保留合法的项就可以了。
具体到实现细节上,假设要对这样的两个多项式 卷积,设 , 类似。然后对各个 DFT 转成点值。要在 这一维上处理这些点值,因为 很小,我们直接对 这个变元暴力卷积,也就是 (点值)累加到 。再把每个 IDFT。最后只保留 作为卷积的结果。
这样我们就可以对这个多项式求逆了!这样我们就彻底解决这个题了!
讲题后UPD:增加一个数学意义上的严谨解释。
我们重载了多项式乘法: 与 的乘积是 。这里对 函数的所有运算都在 意义下。
为此定义
再定义
则
从而
我们通过 DFT 求出 在各个原根位置的点值,要求 在各个原根位置的点值,那太容易了,暴力就行。
卷完之后只保留需要的项的系数就行。
#include<bits/stdc++.h> using namespace std; const int K=22,N=1e6+5,P=998244353,_G=3,invG=(P+1)/3; int T,k,b[K],facb[K],n,m,lm[N]; int Y[N],ways[N],tr[N]; int qpow(int a,int b=P-2){int c=1;for(;b;b>>=1,a=1ll*a*a%P)if(b&1)c=1ll*c*a%P;return c;} void tpre(int n){ for(int i=0;i<n;i++) tr[i]=(tr[i>>1]>>1)|((i&1)?n>>1:0); } void NTT(int *f,int flag,int n){ for(int i=0;i<n;i++)if(i<tr[i])swap(f[i],f[tr[i]]); for(int p=2;p<=n;p<<=1){ int len=p>>1; int tG=qpow(flag==1?_G:invG,(P-1)/p); for(int k=0;k<n;k+=p){ int buf=1; for(int l=k;l<k+len;l++){ int tmp=1ll*buf*f[len+l]%P; f[len+l]=(f[l]-tmp+P)%P;f[l]=(f[l]+tmp)%P; buf=1ll*buf*tG%P; } } } } int F[K][N],G[K][N],H[N],res[K]; void mul(int*A,int*B,int n,int type,int *ans){ int len=1;for(;len<=n*1.5;len<<=1);tpre(len); for(int i=0;i<k;i++) for(int j=0;j<len;j++){F[i][j]=0;if(type)G[i][j]=0;} for(int i=0;i<n;i++){F[lm[i]][i]=A[i];if(type)G[lm[i]][i]=B[i];} for(int i=0;i<k;i++){NTT(F[i],1,len);if(type)NTT(G[i],1,len);} for(int i=0;i<len;i++){ for(int j=0;j<k;j++)res[j]=0; for(int j=0;j<k;j++) for(int l=0;l<k;l++){ int c=(j+l)%k; res[c]=(res[c]+1ll*F[j][i]*G[l][i]%P)%P; } for(int j=0;j<k;j++)F[j][i]=res[j]; } for(int i=0;i<k;i++)NTT(F[i],-1,len); int invn=qpow(len); for(int i=0;i<n;i++)ans[i]=1ll*F[lm[i]][i]*invn%P; } void inv(int*a,int n,int*ans){ if(n==1){ans[0]=qpow(a[0]);return;} inv(a,(n+1)>>1,ans);mul(a,ans,n,1,H);mul(H,ans,n,0,H); for(int i=0;i<n;i++)ans[i]=(2ll*ans[i]-H[i]+P)%P; } int p[1005],dp[1005]; bool cmp(int u,int v){ for(int i=1;i<=k;i++) if(u/facb[i-1]%b[i]>v/facb[i-1]%b[i])return false; return true; } int main(){ facb[0]=1; scanf("%d",&T); while(T--){ scanf("%d",&k); for(int i=1;i<=k;i++) scanf("%d",b+i),++b[i],facb[i]=facb[i-1]*b[i]; scanf("%d%d",&n,&m); while(n--){ int tmp=0; for(int i=1,x;i<=k;i++) scanf("%d",&x),tmp+=x*facb[i-1]; --Y[tmp]; } Y[0]=1-P; for(int i=0;i<facb[k];i++)Y[i]=(Y[i]+P)%P; memset(lm,0,sizeof(lm)); for(int i=0;i<facb[k];i++){ for(int j=1;j<k;j++)lm[i]+=i/facb[j]; lm[i]%=k; } memset(ways,0,sizeof(ways)); inv(Y,facb[k],ways); memset(Y,0,sizeof(Y)); memset(p,0,sizeof(p)); for(int i=1;i<=m;i++) for(int j=1,x;j<=k;j++) scanf("%d",&x),p[i]+=x*facb[j-1]; sort(p+1,p+m+1); p[++m]=facb[k]-1; memset(dp,0,sizeof(dp)); for(int i=1;i<=m;i++){ dp[i]=ways[p[i]]; for(int j=1;j<i;j++)if(cmp(p[j],p[i])) dp[i]=(dp[i]-1ll*ways[p[i]-p[j]]*dp[j]%P+P)%P; } printf("%d\n",dp[m]); } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具