日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【2018北京集训(六)】Lcm

發(fā)布時間:2024/4/17 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【2018北京集训(六)】Lcm 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Portal --> 出錯啦qwq(好吧其實是沒有)

Description

  給定兩個正整數(shù)\(n,k\),選擇一些互不相同的正整數(shù),滿足這些數(shù)的最小公倍數(shù)恰好為\(n\),并且這些數(shù)的和為\(k\)的倍數(shù)

  求選擇的方案數(shù)對\(232792561\)取模

  數(shù)據(jù)范圍:多組數(shù)據(jù),組數(shù)\(T<=10,n<=10^{18},k<=20\),且\(n\)的所有質(zhì)因子不大于\(100\)

Solution

  這題。。好神仙啊qwq敲爆腦子都想不出來系列qwq

  注意到\(n<=10^{18}\)意味著\(n\)至多有\(15\)個不同的質(zhì)因子(前\(15\)個質(zhì)數(shù)乘一下就知道了),并且\(k\)的范圍也是比較小的

  然后還有一點就是這個模數(shù)比較有趣,它\(=lcm(1,2,...,20)+1\),也就是說它可以對長度在\(1..20\)的區(qū)間DFT

?  那不管別的我們可以先考慮一個最樸素的dp

  記\(f[i][j]\)表示當(dāng)前所選的數(shù)狀態(tài)為\(i\),和\(\%k=j\)的方案數(shù),其中這個“所選數(shù)的狀態(tài)”具體計算方式是:假設(shè)寫成分解質(zhì)因數(shù)的形式后\(n=\sum p_i^{mi_i}\),其中\(p_i\)\(n\)的每一個不同的質(zhì)因子,我們的狀態(tài)是一個二進制數(shù),并且二進制的每一位對應(yīng)一個\(n\)的質(zhì)因子,對于二進制的第\(i\)位(假設(shè)對應(yīng)\(p_i\)),如果說當(dāng)前所選的數(shù)中存在一個數(shù)\(x\)滿足\(p_i^{mi_i}|x\),那么這位為\(1\),否則為\(0\)

?  這樣一來,我們最后的答案就應(yīng)該是\(f[(1<<Cnt)-1][0]\),其中\(Cnt\)\(n\)的不同質(zhì)因子個數(shù)

  至于求解。。分解質(zhì)因數(shù)和預(yù)處理因數(shù)可以暴力求解,但是后面的dp直接暴力轉(zhuǎn)移的話穩(wěn)穩(wěn)的T啊(光枚舉子集就要\(3^{Cnt}\)了還要再乘個\(k\)),所以現(xiàn)在的問題是我們要怎么比較快速地轉(zhuǎn)移

?  觀察這個dp兩維的轉(zhuǎn)移,我們會發(fā)現(xiàn)第一維的轉(zhuǎn)移可以寫成一個集合或卷積的形式(或起來等于某個數(shù)就把值累計進去),而第二維的轉(zhuǎn)移則直接枚舉因數(shù)什么的大力轉(zhuǎn)移就好了

  然后這里有一個很有意思的想法:首先我們發(fā)現(xiàn)這兩維如果可以分開處理就會比較好一點,然后注意到這個模數(shù)很有趣,允許我們進行區(qū)間長度\(\in[1,20]\)的DFT,而DFT操作之后,dp的這兩維在某種意義上就是獨立的了,換句話來說,如果說我們先在同行內(nèi)轉(zhuǎn)移\(f\)數(shù)組,再對轉(zhuǎn)以后每行的不完全計算的\(f\)數(shù)組做一次DFT,這行的\(f\)的第二維就不會互相影響了,也就是說我們可以通過這種方式實現(xiàn)第一維和第二維轉(zhuǎn)移的分離

  這樣我們就可以先處理\(f[][i]\),直觀一點來說就是先在同行轉(zhuǎn)移\(f\)數(shù)組,具體一點就是枚舉\(n\)的因數(shù),然后考慮加進去的貢獻,也就是假設(shè)當(dāng)前枚舉到的因數(shù)是\(a[i]\)\(a[i]\)對應(yīng)的狀態(tài)是\(st\),那么我們可以對于所有的\(j\in [0,k)\)轉(zhuǎn)移:
\[ f[st][(j+a[i]\%k)]+=f'[st][j] \]
?  之所以在后面的\(f\)打了個\('\)是因為我們這里要累加進去的是用\(a[i]\)更新前的\(f[st]\)的版本

?  

?  這樣我們就得到了整合了同行數(shù)據(jù)之后的\(f\)數(shù)組(為了防止弄混在接下來的描述中我們還是將這個不完全轉(zhuǎn)移的\(f\)數(shù)組記為\(f1\)好了),然后對于每一行DFT一下,接下來就是考慮第一維的轉(zhuǎn)移了,更加直觀一點就是。。同列的\(f\)進行轉(zhuǎn)移

?  接下來為了讓描述變得更加簡潔,我們將第二維省去(因為反正轉(zhuǎn)移的時候都是同列轉(zhuǎn)移)

?  現(xiàn)在我們要做的事情就是挑出若干個\(f1[i]\),滿足\(i_1\ or\ i_2\ or\ ...\ i_m=st\)然后將這堆\(f1[i]\)的值累加到\(f[st]\)

  我們記\(F(S)=\sum\limits_{i\subseteq S}f[i]\),如果說我們知道了\(F(S)\)的取值,那么只要大力容斥一下就可以得出\(f\)數(shù)組了,具體一點的話就是:
\[ f[T]=\sum\limits_{S\subseteq T}(-1)^{|T|-|S|}F(S) \]
  我們將\(F(S)\)進行一下轉(zhuǎn)化,會發(fā)現(xiàn):
\[ \begin{aligned} F(S)&=\sum\limits_{j\subseteq S}f[j]\\ &=\prod\limits_{i\subseteq S}(1+f1[i]) \end{aligned} \]
  具體的話就是因為對于\(f\)來說,每一個\(f[j]\)都是由若干個\(f1[i]\)組成的,我們考慮將第二個等號后面的連乘的括號拆掉,展開之后會發(fā)現(xiàn)囊括了\(i\)的所有組合方式(為了方便理解可以自己用比較小的規(guī)模模擬一下),然后因為我們限制了\(i\subseteq S\)所以可以保證任意的組合方式都是滿足組合后\(\subseteq S\)

?  然后由于\(f1\)是已知的,所以我們現(xiàn)在就要想如何快速求\(F(S)\)

  顯然枚舉子集不現(xiàn)實

?  如果我們直接從小到大枚舉狀態(tài),每次將這個狀態(tài)對應(yīng)的\(f1\)值轉(zhuǎn)移的話,可能會出現(xiàn)重復(fù)計算的情況(比如說\(010\)可以轉(zhuǎn)移到\(011\)\(110\),而\(011\)\(110\)又都可以轉(zhuǎn)移到\(111\),這樣如果按照這種轉(zhuǎn)移方式的話,\(111\)這里\(010\)\(f1\)值就會被重復(fù)計算),所以這里考慮一種很玄學(xué)的按順序轉(zhuǎn)移方式:

?  我們考慮二進制一位一位轉(zhuǎn)移,從低位枚舉到高位,每枚舉一位就把所有的這位為\(0\)的狀態(tài)的值累加到將這位修改為\(1\)后的狀態(tài)里面去,具體一點的話就是:

?  我們以\(Cnt=3\)為例子,狀態(tài)轉(zhuǎn)移大概是這樣:

  其中橙黃色的箭頭表示轉(zhuǎn)移的方向,然后位數(shù)是從最右邊開始數(shù)的

?  這樣的話顯然所有的狀態(tài)都能轉(zhuǎn)移到它能轉(zhuǎn)移到的位置,現(xiàn)在我們來說明一下為什么這樣不會重復(fù)計算:首先可以確認(rèn)的一點是,這種重復(fù)計算的情況只會出現(xiàn)在轉(zhuǎn)移到一個被修改了兩個及以上位的狀態(tài)的時候,而我們這樣的枚舉方式每次只轉(zhuǎn)移到修改了一位的地方,并且是按照數(shù)位從低到高,狀態(tài)從小到大的順序轉(zhuǎn)移,比如說剛才提到的\(010\)\(111\)中被重復(fù)計算的情況,放在這個轉(zhuǎn)移方式中就應(yīng)該是:在轉(zhuǎn)移第一位的時候,\(010\)會先轉(zhuǎn)移到\(011\),然后在轉(zhuǎn)移第三位的時候再由累加了\(010\)\(011\)轉(zhuǎn)移到\(111\),每次都是從只修改一位的地方轉(zhuǎn)移過來,不會出現(xiàn)重復(fù)計算的情況(但其實上面的證明也不算。。特別嚴(yán)謹(jǐn)。。還是感性理解既視感qwq)

?  然后我們就可以在\(O(2^{Cnt})\)的時間內(nèi)求得\(F(S)\),接下來直接大力容斥一下就可以得出\(f[(1<<Cnt)-1][0]\)的值最后IDFT一下就可以得出答案啦

  

  代碼大概長這個樣子

#include<iostream> #include<cstdio> #include<cstring> #define ll long long using namespace std; const int MOD=232792561,G=71,N=110; const int p[26]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97}; ll mi[N],rec_mx[N],Tmp[N],have[N]; ll a[500010]; int f[(1<<15)+10][20]; ll n,K,T,Cnt,all; void add(int &x,int y){x=(1LL*x+y+MOD)%MOD;} bool in(int st,int x){return st>>(x-1)&1;} int St(int x){return 1<<x-1;} int ksm(int x,int y){int ret=1,base=x;for (;y;y>>=1,base=1LL*base*base%MOD)if (y&1) ret=1LL*ret*base%MOD;return ret; } namespace DFT{/*{{{*/int tmp[110],W[1010][2];int inv;void init(){int rt=ksm(G,(MOD-1)/K);W[0][0]=1;for (int i=1;i<=1000;++i)W[i][0]=1LL*W[i-1][0]*rt%MOD;for (int i=0;i<=1000;++i)W[i][1]=ksm(W[i][0],MOD-2);}void dft(int *a,int n,int op){for (int i=0;i<n;++i) tmp[i]=0;for (int i=0;i<n;++i) for (int j=0;j<n;++j)tmp[i]=(1LL*tmp[i]+1LL*a[j]*W[i*j][op==-1]%MOD)%MOD;if (op==-1){inv=ksm(n,MOD-2);for (int i=0;i<n;++i) tmp[i]=1LL*tmp[i]*inv%MOD;}for (int i=0;i<n;++i) a[i]=tmp[i];} }/*}}}*/ void Div(ll x){for (int i=1;i<=25;++i)if (x%p[i]==0){++Cnt; rec_mx[Cnt]=1; mi[Cnt]=0; have[Cnt]=p[i];while (x%p[i]==0) x/=p[i],++mi[Cnt],rec_mx[Cnt]*=p[i];} } void dfs(int now,ll prod){if (now>Cnt){a[++a[0]]=prod;return;}ll tmp=prod;for (int i=0;i<=mi[now];++i){dfs(now+1,tmp);tmp*=have[now];} } void prework(){a[0]=0; Cnt=0;Div(n);dfs(1,1); } void update(int st,int r){for (int i=0;i<K;++i) Tmp[i]=f[st][i];for (int i=0;i<K;++i) add(f[st][(i+r)%K],Tmp[i]); } void solve(){int st,op;memset(f,0,sizeof(f));all=(1<<Cnt)-1;for (int i=0;i<=all;++i) f[i][0]=1;//initfor (int i=1;i<=a[0];++i){st=0;for (int j=1;j<=Cnt;++j)if (a[i]%rec_mx[j]==0) st|=St(j);update(st,a[i]%K);}for (int i=0;i<=all;++i)DFT::dft(f[i],K,1);for (int i=1;i<=Cnt;++i)for (int j=0;j<=all;++j)if (in(j,i))for (int k=0;k<K;++k)f[j][k]=1LL*f[j][k]*f[j^St(i)][k]%MOD;for (int i=0;i<all;++i){op=1;for (int j=1;j<=Cnt;++j)if (!in(i,j)) op*=-1;for (int j=0;j<K;++j)add(f[all][j],op*f[i][j]);}DFT::dft(f[all],K,-1);printf("%d\n",f[all][0]); }int main(){ #ifndef ONLINE_JUDGEfreopen("a.in","r",stdin); #endifscanf("%d",&T);for (int o=1;o<=T;++o){scanf("%lld%lld",&n,&K);DFT::init();prework();solve();} }

轉(zhuǎn)載于:https://www.cnblogs.com/yoyoball/p/9362927.html

總結(jié)

以上是生活随笔為你收集整理的【2018北京集训(六)】Lcm的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。