操作 - 分治NTT

题目大意:
有n个操作和一个数字初始为0,每个操作形如有p的概率加a,1-p的概率乘b。现在随机打乱这个操作序列,问操作完数值的期望是多少。n≤105n\le10^5n105
题解:
每次就是让x变成kx+b。手玩一下发现答案就是:
1n!∑i=1nbi∑j=0n−1j!(n−j−1)![xj]∏t=1n(x+kt)x+ki\frac1{n!}\sum_{i=1}^nb_i\sum_{j=0}^{n-1}j!(n-j-1)![x^j]\frac{\prod_{t=1}^n(x+k_t)}{x+k_i}n!1i=1nbij=0n1j!(nj1)![xj]x+kit=1n(x+kt)
后面那一坨交换求和顺序可以直接分治NTT。

 
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define Rep(i,v) rep(i,0,(int)v.size()-1)
#define lint long long
#define p 998244353
#define ull unsigned lint
#define db long double
#define pb push_back
#define mp make_pair
#define fir first
#define sec second
#define gc getchar()
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
using namespace std;
typedef pair<int,int> pii;
typedef set<int>::iterator sit;
inline int inn()
{
    int x,ch;while((ch=gc)<'0'||ch>'9');
    x=ch^'0';while((ch=gc)>='0'&&ch<='9')
        x=(x<<1)+(x<<3)+(ch^'0');return x;
}
const int N=100010;int k[N],b[N];
inline int show(int *a,int n) { debug(n)sp<<", ";rep(i,0,n-1) cerr<<a[i]sp;cerr ln;return 0; }
inline int fast_pow(int x,int k,int ans=1) { for(;k;k>>=1,x=(lint)x*x%p) (k&1)?ans=(lint)ans*x%p:0;return ans; }
inline int *newInt(int n) { int *t=new int[n];memset(t,0,sizeof(int)*n);return t; }
inline int clr(int *a,int n) { return memset(a,0,sizeof(int)*n),0; }
inline int cpy(int *a,int *b,int n) { return memcpy(a,b,sizeof(int)*n),0; }
namespace NTT_space{
    const int N=131072+100,LOG=20;
    int *dwg[LOG],*dwgi[LOG];
    struct dwg_preluder{
        dwg_preluder()
        {
            for(int i=2,c=1;i+5<N;i<<=1,c++)
            {
                dwg[c]=newInt(i>>1),dwgi[c]=newInt(i>>1);
                int *d=dwg[c],*di=dwgi[c];d[0]=di[0]=1;
                int w=fast_pow(3,(p-1)/i),wn=fast_pow(w,p-2);
                rep(j,1,(i>>1)-1) d[j]=(lint)d[j-1]*w%p,di[j]=(lint)di[j-1]*wn%p;
            }
        }
    //  ~dwg_preluder() { for(int i=2,c=1;i+5<N;i<<=1,c++) delete dwg[c],delete dwgi[c]; }
    }__dwg_preluder;
    namespace NTT_SPACE{
        int r[N];
        inline int pre(int m)
        {
            int L=0,n=1;while(n<m) n<<=1,L++;
            rep(i,1,n-1) r[i]=(r[i>>1]>>1)|((i&1)<<(L-1));
            return n;
        }
        inline int NTT(int *a,int n,int s)
        {
            rep(i,1,n-1) if(i<r[i]) swap(a[i],a[r[i]]);
            for(int i=2,c=1;i<=n;i<<=1,c++)
            {
                int *d=(s>0?dwg[c]:dwgi[c]);
                for(int j=0,t=i>>1;j<n;j+=i) for(int k=0,x,y;k<t;k++)
                    x=a[j+k],y=(lint)d[k]*a[j+k+t]%p,
                    a[j+k]=(x+y>=p?x+y-p:x+y),a[j+k+t]=(x-y<0?x-y+p:x-y);
            }
            if(s<0) for(int i=0,v=fast_pow(n,p-2);i<n;i++) a[i]=(lint)a[i]*v%p;return 0;
        }
    }using NTT_SPACE::pre;using NTT_SPACE::NTT;
    namespace TMS_space{
        int A[N],B[N],as[N],bs[N];
        inline int trans(int *a,int *A,int m,int n,int s) { return cpy(A,a,m),clr(A+m,n-m),NTT(A,n,s); }
        inline int tms(int *a,int m1,int *b,int m2,int* &c,int m3=-1)
        {
            if(m3<0) m3=m1+m2-1;int n=pre(m1+m2-1);
            trans(a,A,m1,n,1),trans(b,B,m2,n,1);
            rep(i,0,n-1) A[i]=(lint)A[i]*B[i]%p;
            if(c==NULL) c=newInt(m3);
            return NTT(A,n,-1),cpy(c,A,m3),0;
        }
    }using TMS_space::tms;
    inline int pls(int *a,int n,int *b,int m,int* &c)
    {
        if(c==NULL) c=newInt(max(n,m));
        rep(i,0,max(n,m)-1) c[i]=(i<n?a[i]:0)+(i<m?b[i]:0),(c[i]>=p?c[i]-=p:0);
        return 0;
    }
}using NTT_space::tms;using NTT_space::pls;
int *ans[N],*pd[N],ansl[N],pdl[N],fac[N];
inline int solve(int l,int r)
{
    if(l==r) return ans[l]=newInt(ansl[l]=1),ans[l][0]=b[l],pd[l]=newInt(pdl[l]=2),pd[l][0]=k[l],pd[l][1]=1;
    int mid=(l+r)>>1,lc=l,rc=mid+1,*t1,*t2,t1l,t2l;solve(l,mid),solve(mid+1,r);
     
    t1=NULL,tms(ans[lc],ansl[lc],pd[rc],pdl[rc],t1,t1l=ansl[lc]+pdl[rc]-1),//delete ans[lc],
    t2=NULL,tms(pd[lc],pdl[lc],ans[rc],ansl[rc],t2,t2l=pdl[lc]+ansl[rc]-1),//delete ans[rc],
    ans[lc]=NULL,pls(t1,t1l,t2,t2l,ans[lc]),ansl[lc]=max(t1l,t2l);
     
    t1=NULL,tms(pd[lc],pdl[lc],pd[rc],pdl[rc],t1),//delete pd[lc],delete pd[rc],
    pd[lc]=t1,pdl[lc]+=pdl[rc]-1;return 0;
}
int main()
{
    int n=inn(),x,y,z,res=0;
    rep(i,1,n) x=inn(),y=inn(),z=inn(),k[i]=(x+(1ll-x)*z)%p,(k[i]<0?k[i]+=p:0),b[i]=(lint)x*y%p;
    solve(1,n);rep(i,fac[0]=1,n) fac[i]=(lint)i*fac[i-1]%p;
    rep(i,0,n-1) res=(res+(lint)fac[i]*fac[n-i-1]%p*ans[1][i])%p;
    res=(lint)res*fast_pow(fac[n],p-2)%p;return !printf("%d\n",res);
}
<think>我们正在讨论Dilithium算法中NTT(数论变换)的应用步骤。NTT在Dilithium中用于加速多项式乘法,这是签名和验证过程中的核心运算。根据引用[2]和[3],Dilithium使用NTT来高效计算环上的多项式乘法,其中环通常是$R_q = \mathbb{Z}_q[X]/(X^n+1)$。下面我将详细解释NTT在Dilithium中的具体应用步骤。 ### Dilithium中NTT运算的应用步骤 Dilithium的签名和验证流程中涉及大量多项式乘法,例如在计算$A \cdot y$(签名步骤2)和$A \cdot z - c \cdot t$(验证步骤)等。这些多项式乘法在普通域上计算复杂度为$O(n^2)$,而使用NTT可以降低到$O(n \log n)$。以下是NTT在Dilithium中的典型应用步骤: 1. **多项式转换为NTT域**: 首先,将多项式系数表示转换为NTT域(即频域)表示。Dilithium使用特定的参数设置,如$n=256$(多项式次数)和$q=8380417$(模数)。NTT变换基于模$q$的整数,并利用$q$的素因子性质(存在$2n$次单位根)。 - 对于多项式$a \in R_q$,其系数向量$(a_0, a_1, \dots, a_{n-1})$通过NTT变换为$\hat{a} = (\hat{a}_0, \hat{a}_1, \dots, \hat{a}_{n-1})$,其中: $$\hat{a}_i = \sum_{j=0}^{n-1} a_j \cdot \omega^{(2i+1)j} \mod q$$ 这里$\omega$是$2n$次单位根(满足$\omega^{2n} \equiv 1 \mod q$)[^2]。 - **注意**:由于多项式环是$R_q = \mathbb{Z}_q[X]/(X^n+1)$,Dilithium采用**正包裹卷积(positive wrapped convolution)**技术,可以直接使用$n$点NTT(而不是$2n$点)进行乘法[^2]。这显著减少了计算量。 2. **NTT域上的乘法**: 在NTT域中,两个多项式$a$和$b$的乘法变为逐系数(点乘)乘法: $$\hat{c}_i = \hat{a}_i \cdot \hat{b}_i \mod q, \quad i=0,1,\dots,n-1$$ 这避免了复杂的卷积运算,将多项式乘法从$O(n^2)$降至$O(n)$(加上NTT变换的开销)。 3. **逆NTT变换**: 完成乘法后,将结果转换回时域(系数表示): $$c = \text{INTT}(\hat{c})$$ 逆变换(INTT)公式为: $$c_j = n^{-1} \cdot \sum_{i=0}^{n-1} \hat{c}_i \cdot \omega^{-(2j+1)i} \mod q$$ 其中$n^{-1}$是$n$在模$q$下的逆元。 4. **蝴蝶操作优化**: NTT/INTT的实现通常使用蝴蝶操作(butterfly operation)进行分治加速,如引用[3]所述。蝴蝶操作NTT分解为多个阶段($\log_2 n$阶段),每阶段包含$n/2$次操作。例如,对于$n=4$,蝴蝶操作流程如下: - 阶段1:计算相邻系数的蝴蝶操作(乘$\omega^k$后加减)。 - 阶段2:计算跨步系数的蝴蝶操作。 蝴蝶操作示例(伪代码): ```python def ntt(a, omega): n = len(a) if n == 1: return a # 分治:偶数和奇数索引系数 a_even = a[0::2] a_odd = a[1::2] # 递归计算子NTT y_even = ntt(a_even, omega**2 % q) y_odd = ntt(a_odd, omega**2 % q) # 合并结果 y = [0] * n for k in range(n//2): t = (omega**k * y_odd[k]) % q y[k] = (y_even[k] + t) % q y[k + n//2] = (y_even[k] - t) % q return y ``` 实际实现使用迭代而非递归以提高效率[^3]。 5. **在签名流程中的具体应用**: 以签名步骤2($w = A \cdot y$)为例: - 矩阵$A$的每个元素(多项式)通常**预计算NTT形式**($\hat{A}_{ij}$)存储,避免重复变换。 - 随机向量$y$的每个分量$y_j$在计算时转换为NTT形式($\hat{y}_j$)。 - 计算$w_i = \sum_j A_{ij} \cdot y_j$时,对每个$i$: -NTT域计算逐系数乘:$\hat{w}_{i,k} = \sum_j \hat{A}_{ij,k} \cdot \hat{y}_{j,k} \mod q$(对每个$k$)。 - 对$\hat{w}_i$执行INTT得到$w_i$。 - 由于$w$是向量,需对每个分量独立计算。 6. **性能优势**: - 预计算:公钥矩阵$A$的NTT形式可预先存储,节省签名时变换时间。 - 批量处理:多个多项式乘法可并行进行NTT/INTT- 硬件加速:NTT的蝴蝶操作适合FPGA或ASIC实现。 ### 示例:签名步骤2的NTT优化伪代码 ```python import numpy as np # 假设A是公钥矩阵(m x l),每个元素是多项式(n维向量) # A_ntt: 预存储A的NTT形式(m x l x n) # y: 随机向量(l个多项式),需变换到NTT域 def compute_w(A_ntt, y): m, l, n = A_ntt.shape w_ntt = np.zeros((m, n), dtype=int) # 存储w_i的NTT结果(累加用) # 对y的每个分量进行NTT(实际中可预先变换) y_ntt = np.zeros((l, n), dtype=int) for j in range(l): y_ntt[j] = ntt_transform(y[j]) # 假设ntt_transform是NTT函数 # 矩阵乘法 w = A * y for i in range(m): for j in range(l): # 点乘:w_i += A_ij * y_j (在NTT域) for k in range(n): w_ntt[i][k] = (w_ntt[i][k] + A_ntt[i][j][k] * y_ntt[j][k]) % q # 将w_i转换回时域 w = [] for i in range(m): w_i = intt_transform(w_ntt[i]) # INTT变换 w.append(w_i) return w ``` ### 关键点总结 - **NTT作用**:将多项式乘法从$O(n^2)$降至$O(n \log n)$,是Dilithium高效的关键。 - **正包裹卷积**:利用环结构$X^n+1$,直接用$n$点NTT实现乘法[^2]。 - **蝴蝶操作**:分治策略的硬件友好实现[^3]。 - **预计算**:公钥矩阵$A$的NTT形式可预先存储,加速签名。 §§相关问题§§ 1. 为什么NTT能加速Dilithium中的多项式乘法?数学原理是什么? 2. Dilithium的NTT参数(如$n$和$q$)如何选择?为什么选择$q=8380417$? 3. 如何用硬件(如FPGA)实现Dilithium的NTT运算? 4. 与其他后量子签名方案(如Kyber)相比,Dilithium的NTT实现有何异同? 5. NTT在Dilithium签名验证流程中如何应用?</think>### Dilithium算法中NTT运算的应用步骤 Dilithium算法使用数论变换(NTT)加速核心多项式运算,这是实现高效签名的关键。以下是NTT在签名流程中的具体应用步骤(基于环 $R_q = \mathbb{Z}_q[X]/(X^n+1)$,其中 $n=256$,$q=8380417$): #### 1. **多项式预处理(转换为NTT域)** - 将多项式系数表示转换为NTT域表示: $$a(X) = \sum_{i=0}^{n-1} a_i X^i \xrightarrow{\text{NTT}} \hat{a} = (\hat{a}_0, \hat{a}_1, \dots, \hat{a}_{n-1})$$ - 转换公式(基于 $2n$ 次单位根 $\omega$): $$\hat{a}_i = \sum_{j=0}^{n-1} a_j \cdot \omega^{(2i+1)j} \mod q$$ - **优化**:公钥矩阵 $A$ 的元素通常**预计算为NTT形式**存储,避免重复变换[^2]。 #### 2. **NTT域的核心乘法** 在签名和验证中,多项式乘法(如 $A \cdot y$ 或 $c \cdot s_1$)通过以下步骤加速: - **步骤1**:若操作数未在NTT域,先进行NTT转换 - **步骤2**:执行**逐系数点乘**(而非普通卷积): $$\hat{c}_k = \hat{a}_k \cdot \hat{b}_k \mod q, \quad k=0,1,\dots,n-1$$ - **步骤3**:利用**正包裹卷积**性质($\text{mod } X^n+1$),直接使用 $n$ 点NTT替代 $2n$ 点运算[^2]。 #### 3. **逆NTT(INTT)转换** -NTT域结果转换回系数表示: $$c_j = n^{-1} \cdot \sum_{i=0}^{n-1} \hat{c}_i \cdot \omega^{-(2j+1)i} \mod q$$ 其中 $n^{-1}$ 是 $n$ 在模 $q$ 下的逆元。 - **关键作用**:生成最终结果如 $w = A \cdot y$ 或 $z = y + c \cdot s_1$ 的系数表示。 #### 4. **蝴蝶操作优化** - NTT/INTT 通过**蝴蝶操作**分阶段计算($\log_2 n$ 个阶段): ```python # 伪代码示例(基于引用[3]) def butterfly(a, b, root): # a, b: 当前多项式对 # root: 单位根幂次 t = b * root # 乘法 a_next = a + t # 加法路径 b_next = a - t # 减法路径 return a_next, b_next ``` - **并行性**:每阶段 $n/2$ 个蝴蝶操作可并行执行,大幅提升速度[^3]。 #### 5. **在签名流程中的具体应用 - **签名步骤2($w = A \cdot y$)**: ```python # 输入:预存NTT形式的A, 随机向量y y_ntt = NTT(y) # 步骤1: y转换到NTT域 w_hat = [] for i in range(m): # m为矩阵A的行数 # 点乘A的第i行与y (NTT域) w_i_hat = pointwise_mult(A_ntt[i], y_ntt) w_hat.append(INTT(w_i_hat)) # 步骤3: 逆NTT w = concatenate(w_hat) # 输出w ``` - **签名步骤4($z = y + c \cdot s_1$)**: ```python c_poly = expand_challenge(c) # 将挑战c扩展为多项式 c_ntt = NTT(c_poly) # c转换到NTT域 s1_ntt = NTT(s1) # 私钥s1预存为NTT形式 cs1_hat = pointwise_mult(c_ntt, s1_ntt) # NTT域点乘 cs1 = INTT(cs1_hat) # 逆NTT得c·s1 z = y + cs1 # 向量加法 ``` #### 性能优势与关键设计 1. **复杂度降低**:多项式乘法从 $O(n^2)$ 降至 $O(n \log n)$。 2. **模数选择**:$q=8380417$ 满足: - 存在 $2n$ 次单位根($\omega^{2n} \equiv 1 \mod q$) - 支持快速模约减($q = 2^{23} - 2^{13} + 1$) 3. **内存优化**:蝴蝶操作支持原位计算,减少内存占用[^3]。 > 示例:$n=256$ 时,NTT仅需 $256 \times \log_2(256) = 2048$ 次乘法,而普通卷积需 $256^2 = 65,536$ 次。 #### 与其他组件的交互 - **哈希与压缩**:NTT输出需配合 `UseHint` 函数(引用[1])进行范围检查和压缩: $$v_1 = \text{UseHint}_q(h, r, \alpha) \implies \|r - v_1 \cdot \alpha\|_\infty \leq \alpha + 1$$ - **安全约束**:NTT结果需满足 $\|z\|_\infty \leq \gamma_1$ 等范数限制,否则重签。 NTT使Dilithium在嵌入式设备上可达数千次签名/秒,成为NIST后量子标准的核心优势[^1][^2]。 --- ### 相关问题 1. **为什么Dilithium选择$q=8380417$作为模数?该选择如何优化NTT运算?** 2. **蝴蝶操作的具体硬件实现方案有哪些(如FPGA/ASIC)?** 3. **NTT在Dilithium验证流程中如何应用?与签名阶段有何差异?** 4. **与其他后量子方案(如Kyber)相比,Dilithium的NTT实现有何独特优化?** 5. **如何验证NTT实现中的数值稳定性?常见错误有哪些?**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值