[2018HN省隊集訓D1T3] Or
阿新 • • 發佈:2019-02-28
長度 turn const 完全 src return == for alt
統計答案的時候枚舉總共出現的二進制位個數:
\[ \text{Ans}=\sum_{i=1}^k{k\choose i}dp_{n,i} \]
狀態數是 \(O(nk)\) 的, 樸素轉移 \(O(k)\), 總復雜度 \(O(nk^2)\). 是第 \(i\) 項的新二進制位個數, \(j\) 是前 \(i\) 項已經出現過的二進制位個數, \(j-k\) 是前 \(i-1\) 項已經出現的二進制位個數. 顯然這些前 \(i-1\) 項中出現過的二進制位在第 \(i\) 項中是任選的, 於是我們需要乘上這玩意.
的數列裏面是完全不包含前面 \(i-m\) 中的二進制位的方案的. 這些位由於在前面已經出現過, 所以在後面長度為 \(m\) 的數列裏是任選的. 一共有 \(m(j-k)\) 位.
[2018HN省隊集訓D1T3] Or
題意
給定 \(n\) 和 \(k\), 求長度為 \(n\) 的滿足下列條件的數列的數量模 \(998244353\) 的值:
- 所有值在 \([1,2^k)\) 中
- 前綴或的值嚴格遞增
\(n,k\le 3\times 10^4\)
題解
這題有點意思
首先肯定每一項都得有新出現的二進制位, 於是可以想到一個超簡單的 \(O(nk^2)\) 的DP, 設 \(dp_{i,j}\) 為長度為 \(i\) 且已經出現了 \(j\) 個二進制位的數列的個數. 然後考慮枚舉數列第 \(i\) 項的新二進制位個數, 那麽轉移顯然:
\[
dp_{i,j}=\sum_{k=1}^j {j\choose k} dp_{i-1,j-k}2^{j-k}
\]
統計答案的時候枚舉總共出現的二進制位個數:
\[ \text{Ans}=\sum_{i=1}^k{k\choose i}dp_{n,i} \]
狀態數是 \(O(nk)\) 的, 樸素轉移 \(O(k)\), 總復雜度 \(O(nk^2)\).
機智的我們一眼看出後面的轉移式子就是個二項卷積, 隨手比個階乘打個NTT上去就變成 \(O(nk\log k)\) 了.
然而這還不夠.
我們發現一次轉移相當於一次卷積和一次點積, 我們肯定想這玩意能不能快速冪一發.
然後我們非常sad地發現由於裏面那個 \(2^{j-k}\) 搞事情所以不能裸快速冪.
考慮這個 \(2^{j-k}\) 是拿來幹啥的. \(k\)
那麽如果轉移不是 \(1\) 位而是 \(m\) 位呢?
這次應該是這樣的轉移式:
\[
dp_{i,j}=\sum_{k=1}^j{j\choose k} dp_{i-m,j-k}dp_{m,k}2^{(j-k)m}
\]
我們相當於在一個長度為 \(i-m\) 的序列後面接了長度為 \(m\) 的序列並用組合數讓他們的二進制位互不幹擾. 但是後面接的長度為 \(m\)
下標相同的並在一起就又是個二項卷積了, 倍增就好了. 復雜度 \(O(k \log k \log n)\).
參考代碼
#include <bits/stdc++.h>
const int G=3;
const int DFT=1;
const int IDFT=-1;
const int MAXN=1e5+10;
const int MOD=998244353;
const int PHI=MOD-1;
int n;
int k;
int dp[MAXN];
int pw[MAXN];
int tr[MAXN];
int tx[MAXN];
int rev[MAXN];
int fact[MAXN];
int C(int,int);
int Pow(int,int,int);
void NTT(int*,int,int);
int main(){
scanf("%d%d",&n,&k);
pw[0]=1;
fact[0]=1;
for(int i=1;i<=k;i++){
pw[i]=(pw[i-1]<<1)%MOD;
fact[i]=1ll*fact[i-1]*i%MOD;
tr[i]=Pow(fact[i],MOD-2,MOD);
}
int bln=1,bct=0;
while(bln<=k*2){
bln<<=1;
++bct;
}
for(int i=0;i<bln;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(bct-1));
NTT(tr,bln,DFT);
dp[0]=1;
int cur=1;
while(n>0){
if(n&1){
for(int i=k+1;i<bln;i++)
dp[i]=0;
for(int i=0;i<=k;i++)
dp[i]=1ll*dp[i]*Pow(pw[i],cur,MOD)%MOD;
NTT(dp,bln,DFT);
for(int i=0;i<bln;i++)
dp[i]=1ll*dp[i]*tr[i]%MOD;
NTT(dp,bln,IDFT);
}
NTT(tr,bln,IDFT);
for(int i=k+1;i<bln;i++)
tx[i]=0;
for(int i=0;i<=k;i++)
tx[i]=1ll*tr[i]*Pow(pw[i],cur,MOD)%MOD;
NTT(tx,bln,DFT);
NTT(tr,bln,DFT);
for(int i=0;i<bln;i++)
tr[i]=1ll*tr[i]*tx[i]%MOD;
NTT(tr,bln,IDFT);
for(int i=k+1;i<bln;i++)
tr[i]=0;
NTT(tr,bln,DFT);
n>>=1;
cur<<=1;
}
int ans=0;
for(int i=0;i<=k;i++)
(ans+=1ll*dp[i]*fact[i]%MOD*C(k,i)%MOD)%=MOD;
printf("%d\n",ans);
return 0;
}
int C(int n,int m){
return n<0||m<0||n<m?0:1ll*fact[n]*Pow(fact[m],MOD-2,MOD)%MOD*Pow(fact[n-m],MOD-2,MOD)%MOD;
}
void NTT(int* a,int len,int opt){
for(int i=0;i<len;i++)
if(rev[i]>i)
std::swap(a[i],a[rev[i]]);
for(int i=1;i<len;i<<=1){
int step=i<<1;
int wn=Pow(G,(opt*PHI/step+PHI)%PHI,MOD);
for(int j=0;j<len;j+=step){
int w=1;
for(int k=0;k<i;k++,w=1ll*w*wn%MOD){
int x=a[j+k];
int y=1ll*a[j+k+i]*w%MOD;
a[j+k]=(x+y)%MOD;
a[j+k+i]=(x-y+MOD)%MOD;
}
}
}
if(opt==IDFT){
int inv=Pow(len,MOD-2,MOD);
for(int i=0;i<len;i++)
a[i]=1ll*a[i]*inv%MOD;
}
}
inline int Pow(int a,int n,int p){
int ans=1;
while(n>0){
if(n&1)
ans=1ll*a*ans%p;
a=1ll*a*a%p;
n>>=1;
}
return ans;
}
[2018HN省隊集訓D1T3] Or