UOJ #86 mx的组合数 (数位DP+NTT+原根优化)

题目传送门

matthew99神犇的题解讲得非常清楚明白,跪烂Orzzzzzzzzzzzzz

总结一下,本题有很多重要的突破口

1.Lucas定理

看到n,m特别大但模数特别小时,容易想到$lucas$定理

$C_{n}^{m}=C_{n/p}^{m/p}\cdot C_{n\;mod\;p}^{m\;mod\;p}\;(mod\;p)$

但普通的$lucas$显然不适用于多次计算,我们可以把$lucas$定理展开

我们把$n$和$m$都看成两个$p$进制数$a$和$b$

$C_{n}^{m}=\pi C_{a_{i}}^{b_{i}}\;(mod\;p)$

这个展开显然成立

2.数位$DP$

想到了上一条进制转化,很容易就联想到数位$DP$

定义$f[i][j]$表示枚举到第$i$位,余数是$j$的方案数

转移十分经典,设现在要填上的数是$x$,$0$表示没达到上界,$1$达到上界,设$z=C_{x}^{b_{i+1}}$

$f0[i][j\cdot z\;mod\;p]+=f0[i][j]$

$x<a_{i+1}$时,$f0[i][j\cdot z\;mod\;p]+=f1[i][j]$

$x=a_{i+1}$时,$f1[i][j\cdot z\;mod\;p]+=f1[i][j]$

总复杂度$O(p^2log_{p}n)$

3.原根优化与多项式乘法

上面的$p^{2}$转移咋这么无脑呢,是不是有啥优化啊?是的

由于$p$是一个质数,它一定存在一个原根$g$

这就要涉及到离散对数了。其实这是一个映射

$g^{ind(x)}\equiv x\;(mod\;p)$

$ind(x)\;(ind(x)\in[0,p-1))$与$x\;(x\in [1,p))$是一组一一映射

那么$j\cdot z\;mod\;p$

$=g^{ind(j)+ind(z)\;(mod\;p-1)}\;mod\;p$

我们利用映射关系,把底数化成指数

这样转移变成了卷积的形式,用多项式乘法每次$O(plogp)$计算

计算出结果后,再逆映射回来得到实际的答案

而离散对数的映射并不能处理余数等于$0$的情况,我们每次$O(p)$单独讨论即可

总时间$O(plogplog_{p}n)$

  1 #include <bits/stdc++.h>
  2 #define N1 (1<<16)+10
  3 #define dd double
  4 #define ld long double
  5 #define ll long long
  6 #define uint unsigned int
  7 #define i128 __int128
  8 using namespace std;
  9
 10 const int inf=0x3f3f3f3f;
 11 i128 gi128()
 12 {
 13     i128 ret=0;int 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 ll qpow(ll x,ll y,const int &p)
 19 {
 20     ll ans=1;
 21     for(;y;x=x*x%p,y>>=1) if(y&1) ans=ans*x%p;
 22     return ans;
 23 }
 24 const ll p=998244353;
 25 namespace NTT{
 26 ll a[N1],b[N1],c[N1],Wn[N1],_Wn[N1];
 27 int r[N1];
 28 ll qpow(ll x,ll y)
 29 {
 30     ll ans=1;
 31     for(;y;x=x*x%p,y>>=1) if(y&1) ans=ans*x%p;
 32     return ans;
 33 }
 34 void NTT(ll *s,int len,int type)
 35 {
 36     int i,j,k; ll wn,w,t;
 37     for(i=0;i<len;i++) if(i<r[i]) swap(s[i],s[r[i]]);
 38     for(k=2;k<=len;k<<=1)
 39     {
 40         wn=(type>0)?Wn[k]:_Wn[k];
 41         for(i=0;i<len;i+=k)
 42         {
 43             for(j=0,w=1;j<(k>>1);j++,w=w*wn%p)
 44             {
 45                 t=w*s[i+j+(k>>1)]%p;
 46                 s[i+j+(k>>1)]=(s[i+j]+p-t)%p;
 47                 s[i+j]=(s[i+j]+t)%p;
 48             }
 49         }
 50     }
 51 }
 52 void Pre(int len,int L)
 53 {
 54     int i;
 55     for(i=0;i<len;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(L-1));
 56     for(i=1;i<=len;i<<=1) Wn[i]=qpow(3,(p-1)/i), _Wn[i]=qpow(Wn[i],p-2);
 57 }
 58 void Main(int len)
 59 {
 60     int i,invl=qpow(len,p-2);
 61     NTT(a,len,1); NTT(b,len,1);
 62     for(i=0;i<len;i++) c[i]=a[i]*b[i]%p;
 63     NTT(c,len,-1);
 64     for(i=0;i<len;i++) c[i]=c[i]*invl%p;
 65     memset(a,0,sizeof(a)); memset(b,0,sizeof(b));
 66 }
 67 };
 68
 69 int T,m,G;
 70 i128 n,l,r;
 71 int a[N1],b[N1],tmp[N1];
 72 ll f0[2][N1],f1[2][N1],mul[N1],_mul[N1],ans[N1];
 73
 74 inline ll C(int N,int M)
 75 {
 76     if(M>N) return 0;
 77     return mul[N]*_mul[M]%m*_mul[N-M]%m;
 78 }
 79
 80 int pr[N1],use[N1],ind[N1],_ind[N1],son[N1];
 81 void get_ind()
 82 {
 83     int i,j,ns=0,flag,x,cnt=0,sz=0;
 84     for(i=2;i<=m-1;i++)
 85     {
 86         if(!use[i]) pr[++cnt]=i;
 87         for(j=1;j<=cnt&&i*pr[j]<=m-1;j++)
 88         {
 89             use[i*pr[j]]=1;
 90             if(i%pr[j]==0) break;
 91         }
 92     }
 93     for(j=1;j<=cnt;j++) if((m-1)%pr[j]==0) son[++sz]=(m-1)/pr[j];
 94     for(i=2;i<=m-2;i++)
 95     {
 96         flag=1;
 97         for(j=1;j<=sz;j++) if(qpow(i,son[j],m)==1){ flag=0; break;}
 98         if(flag){ G=i; break; }
 99     }
100     ind[1]=0; _ind[0]=1; ind[0]=m-1;
101     for(i=1,x=1;i<=m-2;i++) x=x*G%m, ind[x]=i, _ind[i]=x;
102 }
103 //using NTT::a; using NTT::b; using NTT::c;
104 void solve(i128 w,int type)
105 {
106     int i,j,k,nw=0,nn=0,len,L,now=0,pst=1;
107     if(w<n){ ans[0]=((w+1)*type%p+ans[0]+p)%p; return; }
108     while(w>0) nw++,tmp[nw]=w%m,w/=m;
109     for(i=1;i<=nw;i++) a[nw-i+1]=tmp[i];
110     i128 N=n;
111     while(N>0) nn++,tmp[nn]=N%m,N/=m;
112     for(i=1;i<=nn;i++) b[nw-i+1]=tmp[i];
113
114     for(len=1,L=0;len<m+m-1;len<<=1,L++);
115     NTT::Pre(len,L);
116
117     memset(f0,0,sizeof(f0)); memset(f1,0,sizeof(f1));
118     for(j=0;j<a[1];j++) f0[1][C(j,b[1])]++;
119     f1[1][C(a[1],b[1])]++;
120     for(i=1;i<nw;i++)
121     {
122         memset(f0[now],0,sizeof(f0[now])); memset(f1[now],0,sizeof(f1[now]));
123
124         for(j=1;j<m;j++) NTT::a[ind[j]]=f0[pst][j];
125         for(j=0;j<m;j++) NTT::b[ind[C(j,b[i+1])]]++;
126         for(j=1;j<m;j++) (f0[now][0]+=f0[pst][j]*NTT::b[m-1])%=p;
127         (f0[now][0]+=f0[pst][0]*m)%=p; NTT::b[m-1]=0;
128         NTT::Main(len);
129         for(j=0;j<len;j++) (f0[now][_ind[j%(m-1)]]+=NTT::c[j])%=p;
130
131         for(j=1;j<m;j++) NTT::a[ind[j]]=f1[pst][j];
132         for(j=0;j<a[i+1];j++) NTT::b[ind[C(j,b[i+1])]]++;
133         for(j=1;j<m;j++) (f0[now][0]+=f1[pst][j]*NTT::b[m-1])%=p;
134         (f0[now][0]+=f1[pst][0]*a[i+1])%=p; NTT::b[m-1]=0;
135         NTT::Main(len);
136         for(j=0;j<len;j++) (f0[now][_ind[j%(m-1)]]+=NTT::c[j])%=p;
137
138         for(k=0;k<m;k++)
139         (f1[now][k*C(a[i+1],b[i+1])%m]+=f1[pst][k])%=p;
140
141         swap(now,pst);
142     }
143     for(i=0;i<m;i++) (ans[i]+=(f0[pst][i]+f1[pst][i])*type%p+p)%=p;
144 }
145
146 int main()
147 {
148     int i,j,x,cnt=0;
149     scanf("%d",&m); n=gi128(); l=gi128(); r=gi128(); l--;
150     mul[0]=mul[1]=_mul[0]=_mul[1]=1;
151     for(i=2;i<m;i++) mul[i]=mul[i-1]*i%m, _mul[i]=qpow(mul[i],m-2,m);
152     get_ind();
153     solve(r,1);
154     solve(l,-1);
155     for(i=0;i<m;i++) printf("%lld\n",ans[i]);
156     return 0;
157 }  

原文地址:https://www.cnblogs.com/guapisolo/p/10356707.html

时间: 2024-10-26 10:17:53

UOJ #86 mx的组合数 (数位DP+NTT+原根优化)的相关文章

UOJ #2. 【NOI2014】起床困难综合症 数位DP

第二次做NOI的题... .预处理+数位DP #2. [NOI2014]起床困难综合症 id=2" style="">统计提交情况 id=2#tab-statement" style="color:rgb(85,85,85); text-decoration:none; position:relative; display:block; padding:10px 15px; margin-right:2px; line-height:1.428571

BZOJ 2425 [HAOI2010]计数:数位dp + 组合数

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2425 题意: 给你一个数字n,长度不超过50. 你可以将这个数字: (1)去掉若干个0 (2)打乱后重新排列 问你可以产生多少个小于n的数字. 题解: 题目中的第一个操作其实是没有用的. 去掉若干个0之后再重新排列(不允许前导0),和不去0直接重新排列(允许前导0),其实是等价的. 所以按照数位dp的方法从高到低按位统计. 如n = 2345时,分别统计前缀为0~1, 20~22, 23

[HAOI2010] 计数 - 数位dp,组合数

你有一组非零数字(不一定唯一),你可以在其中插入任意个0,这样就可以产生无限个数.比如说给定{1,2},那么可以生成数字12,21,102,120,201,210,1002,1020,等等. 现在给定一个数,问在这个数之前有多少个数.(注意这个数不会有前导0). Solution 可重复康托展开 常用数位 dp 套路,枚举哪一位开始比原数小,前方唯一而后方算全排列 回避高精度的全排列数计算,考虑到阶乘最多大约算到 50, 开个因子计数桶,然后在桶上操作就可以了 #include <bits/st

[暑假集训--数位dp]LightOj1205 Palindromic Numbers

A palindromic number or numeral palindrome is a 'symmetrical' number like 16461 that remains the same when its digits are reversed. In this problem you will be given two integers i j, you have to find the number of palindromic numbers between i and j

hdu 5898 odd-even number 数位DP

odd-even number Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 716    Accepted Submission(s): 385 Problem Description For a number,if the length of continuous odd digits is even and the length

hihoCoder1033 交错和 数位DP

题目:交错和 链接:http://hihocoder.com/problemset/problem/1033# 题意:对于一个十进制整数x,令a0.a1.a2.....an是x从高位到低位的数位,定义f(x)=a0-a1+a2-a3+...an,给出L.R.K,x在L到R之间,求所有满足:f(x)=k的x的和.(0 ≤ l ≤ r ≤ 10^18, |k| ≤ 100) 思路: L与R太大,连预处理的可能性都没有,很明显的数位DP. 令dp[i][j]为精确的(有前导0)i 位,f(x)值为j

POJ3252 Round Numbers 组合数学||数位DP

这题目做了两个小时,调试的头都晕了... 题型是数位DP中很常见的,给一个区间[l,r]求区间[l,r]中的 符合题目要求的数的个数,本题求的 是 区间中 的数  它的二进制中0的个数大于等于1的个数的    这些数的个数,不好意思表达能力有点差...例如[1,4]答案是2, 因为 2的二进制是10,0的个数大于等于1的个数,4的二进制是100,0的个数大于1的个数,所以答案是两个 好了第一反应肯定是 设定一个函数 f(r) - f[l - 1]就是答案,那么显然这个函数f(r)的意思就是 1

[HDOJ4734]F(x)(数位dp)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4734 题意:给定了一个函数,求[0,B]内的数的个数,使得他们换算出来的结果小于F(A). 数位dp,dp(l,s)表示l位数的值,s为被F(A)减后还剩下的值.如果s<0说明此时的F(x)比F(A)大了,要剪枝掉.直到位数被枚举完全,判断s是否大于等于0.由于这个s最大为9*(∑i=0,9 2^i)≍5120,所以s这一维可以开5120. 1 #include <bits/stdc++.h>

2017&quot;百度之星&quot;程序设计大赛 - 复赛1005&amp;&amp;HDU 6148 Valley Numer【数位dp】

Valley Numer Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 311    Accepted Submission(s): 165 Problem Description 众所周知,度度熊非常喜欢数字. 它最近发明了一种新的数字:Valley Number,像山谷一样的数字. 当一个数字,从左到右依次看过去数字没有出现先递增接