8月20日考试T3 题解(数论+动态规划)
此题太毒瘤了,以至于我单独为它写一篇题解。
-------------------
题目大意:任何自然数都可以用$2$的整数幂之和来表示。现在给定$T$组数据,每组一个$n$,求$n$用$2$的整数幂之和表示的方案数。$n\leq 10^{18}$
首先科普一波伯努利数:
设自然数幂和$S_{k}(n)=\sum\limits_{i=0}^{n-1} i^k$
将其用关于$n$的$m+1$次多项式表示,在其中定义伯努利数$B_i$为:
$S_k(n)=\frac{1}{k+1}\sum\limits_{i=0}^k C_{k+1}^i B_d n^{k+1-d}$
将$n=1$带入得:
$B_0=1\ (m=0),\sum\limits_{i=0}^k C_{k+1}^iB_i\ (m>0)$
于是可以$O(k^2)$求出$B_k$。
其正确性可以用生成函数证明。贴连接:https://www.cnblogs.com/JiaZP/p/13491011.html
首先一个普遍的做法应该能想到:设$f_n$表示$n$的方案数,那么通过打表有$f_n=\sum\limits_{j=0}^{\lceil \frac{n}{2} \rceil }f_j$。我们考虑转化一下这个式子:
$f_n=\sum\limits_{j=0}^{\lceil \frac{n}{2} \rceil }f_j$
$=\sum\limits_{j=0}^{\lfloor \frac{n}{2} \rfloor}\sum\limits_{i=0}^{\lfloor \frac{j}{2} \rfloor} f_i$
$=\sum\limits_{i=0}^{\lfloor \frac{n}{4} \rfloor} f_i\sum\limits_{j=2i}^{\lfloor \frac{n}{2} \rfloor}1$
$=\sum\limits_{i=0}^{\lfloor \frac{n}{4} \rfloor} f_i(S_0(\lfloor \frac{n}{2} \rfloor+1)-S_0(2i))$
$=\sum\limits_{i=0}^{\lfloor \frac{n}{4} \rfloor} f_iS_0(\lfloor \frac{n}{2} \rfloor+1)-2\sum\limits_{i=0}^{\lfloor \frac{n}{4} \rfloor} f_i\times i$
发现前半部分很好解决,难的地方在于后面这个形如$\sum\limits_{i=0}^n f_i \times i^k$的式子。我们尝试解这个式子:
设$F(n,k)=\sum\limits_{i=0}^{n-1}i^k$
$\sum\limits_{i=0}^n f_i \times i^k$
$=\sum\limits_{i=0}^n i^k\sum\limits_{j=0}^{\lfloor \frac{n}{2} \rfloor}f_j$
$=\sum\limits_{j=0}^{\lfloor \frac{n}{2} \rfloor}f_j\sum\limits_{i=2j}^n i^k$
$=\sum\limits_{j=0}^{\lfloor \frac{n}{2} \rfloor}f_j(S_k(n+1)-S_k(2j))$
$=\sum\limits_{j=0}^{\lfloor \frac{n}{2} \rfloor}f_jS_k(n+1)-\sum\limits_{j=0}^{\lfloor \frac{n}{2} \rfloor}f_j\sum\limits_{d=1}^{k+1}\frac{C_{k+1}^d}{k+1}B_{k+1-d}(2j)^d$
$=\sum\limits_{j=0}^{\lfloor \frac{n}{2} \rfloor}f_jS_k(n+1)-\sum\limits_{d=1}^{k+1}\frac{C_{k+1}^d}{k+1}B_{k+1-d}2^d\sum\limits_{j=0}^{\lfloor \frac{n}{2} \rfloor} f_j \times j^d$
$=F(\lfloor \frac{n}{2} \rfloor,0)S_k(n+1)-\sum\limits_{d=1}^{k+1}\frac{C_{k+1}^d}{k+1}B_{k+1-d}2^dF(\lfloor \frac{n}{2} \rfloor ,d)$
发现左边是子问题,右边$k$最多是$\log n$。状态数$\log^2 n$,转移$\log n$,复杂度$O(T\log^3 n)$。记忆化搜索实现。
(跟爆long long战斗了两个小时的我QAQ,码风可能有点丑,见谅
最好手写哈希表,用map复杂度多一个log,会被卡常。也可以用unordered_map
代码:
#include<bits/stdc++.h> #define int long long typedef long long ll; using namespace std; const int maxn=100; const int mod=1e9+7; int B[70],C[70][70],f[155][70],inv[70]; int T,n,tot; inline ll read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline int qpow(int x,int y) { int res=1; while(y) { if (y&1) res=res*x%mod; x=x*x%mod; y>>=1; } return res%mod; } inline void init() { const int up=66; for (int i=0;i<=up;i++) C[i][0]=1,inv[i]=qpow(i,mod-2); for (int i=1;i<=up;i++) for (int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]%mod+C[i-1][j]%mod)%mod; B[0]=1; for (int i=1;i<=up;i++) { for (int j=0;j<i;j++) B[i]=(B[i]%mod+(C[i+1][j]%mod*B[j]%mod)%mod)%mod; B[i]=(mod-(inv[i+1]%mod*B[i]%mod)%mod+mod)%mod; } } const int P=13331; struct h { long long cnt,head[P+1000]; struct edge{int next,to,val;}edge[maxn*2]; inline void init(){memset(head,0,sizeof(head));cnt=0;} inline void add(int to,int val) { int x=to%P+1; edge[++cnt].next=head[x]; edge[cnt].to=to; edge[cnt].val=val; head[x]=cnt; } inline int find(int v) { int x=v%P+1; for (int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if (to==v) return edge[i].val; } return -1; } }Hash; inline int dp(ll n,int k) { int x=Hash.find(n); if (x==-1) Hash.add(n,++tot),x=tot; if (f[x][k]) return f[x][k]; if (n<=1) { if (n==0) return f[x][k]=(k==0?1:0); if (n==1) return f[x][k]=(k==0?2:1); } int res=0,tmp=(n+1)%mod; for (int d=1;d<=k+1;d++) { int tp=C[k+1][d]%mod; tp=(tp%mod*(inv[k+1]%mod))%mod; tp=(tp%mod*(B[k-d+1]%mod))%mod; tp=(tp%mod*tmp%mod)%mod; res=(res+tp)%mod; tmp=(n+1ll)%mod*tmp%mod; } res=(res%mod*dp(n>>1,0)%mod)%mod; for (int d=1;d<=k+1;d++) { int tmp=C[k+1][d]%mod; tmp=(tmp%mod*(inv[k+1]%mod))%mod; tmp=(tmp%mod*(B[k-d+1]%mod))%mod; tmp=(tmp%mod*((1ll<<d)%mod))%mod; tmp=(tmp%mod*(dp(n>>1,d)%mod))%mod; res=((res-tmp)%mod+mod)%mod; } return f[x][k]=res; } inline void work() { Hash.init(); tot=0; memset(f,0,sizeof(f)); printf("%lld\n",(dp(n/2,0)%mod+mod)%mod); } signed main() { T=read(); init(); while(T--) { n=read(); work(); } return 0; }