Little Pony and Elements of Harmony(CF 453 D)
1 題目相關
1.1傳送門
傳送門
1.2 題目大意
給你一個mmm,現在有一個數組fff,已知這2m2^m2m個數在000時刻的值f[0][i](i∈[0,2m))f[0][i](i\in[0,2^m))f[0][i](i∈[0,2m)),已知另一個數組b[i](i∈[0,m])b[i](i\in[0,m])b[i](i∈[0,m])
記cnt(x)cnt(x)cnt(x)為xxx在二進制下1的數量,⊕\oplus⊕為異或運算
有如下遞推式f[i][j]=∑k=02m?1b[cnt[j⊕k]]?f[i?1][k]mod  pf[i][j]=\sum_{k=0}^{2^m-1}b[cnt[j\oplus k]]*f[i-1][k] \mod pf[i][j]=k=0∑2m?1?b[cnt[j⊕k]]?f[i?1][k]modp
求f[t][i](i∈[0,2m))f[t][i](i\in[0,2^m))f[t][i](i∈[0,2m))
1.3 數據范圍
原題數據范圍:
1≤m≤20,1≤t≤1018,2≤p≤1091\le m\le20,1\le t\le 10^{18},2\le p\le10^91≤m≤20,1≤t≤1018,2≤p≤109
1≤f[0][i],b[i]≤1091\le f[0][i],b[i]\le 10^91≤f[0][i],b[i]≤109
m,t,p,f,b∈Zm,t,p,f,b\in \mathbb{Z}m,t,p,f,b∈Z
時間限制6s6s6s
為了解釋方便,定義n=2mn=2^mn=2m
2 算法
2.1 暴力
拿到一道題,首先當然先考慮暴力怎么做,當然是很方便的,首先,我們可以直接考慮模擬,總復雜度Θ(n2tm)\Theta(n^2tm)Θ(n2tm)
2.2 稍有優化的暴力
容易發現,那個cnt[]cnt[]cnt[]是可以預處理的,所以復雜度變為Θ(n2t)\Theta(n^2t)Θ(n2t)
2.3 換個思路
你發現,ttt實在是太大了,實在為本題的一大瓶頸
考慮矩陣乘法,對于下一個時刻本質上是進行一次矩陣乘法,總復雜度Θ(n3logt)\Theta(n^3logt)Θ(n3logt)
2.4 你會FWT
前面的那么多算法的復雜度并不優越,一般的出題人對于前面的算法的給分一般都只有一檔,實在不是很可觀
但是,你會FWT,你看過我的博客,(不會FWT的請點擊快速沃爾什變換(FWT))
請仔細觀察如下式子
f[i][j]=∑k=02m?1b[cnt[j⊕k]]?f[i?1][k]mod  pf[i][j]=\sum_{k=0}^{2^m-1}b[cnt[j\oplus k]]*f[i-1][k] \mod pf[i][j]=k=0∑2m?1?b[cnt[j⊕k]]?f[i?1][k]modp
把它做一個轉換
f[i][j]=∑a⊕b=jb[cnt[a]]?f[i?1][b]mod  pf[i][j]=\sum_{a\oplus b=j}b[cnt[a]]*f[i-1][b] \mod pf[i][j]=a⊕b=j∑?b[cnt[a]]?f[i?1][b]modp
考慮構造一個數組g[i]=b[cnt[i]]g[i]=b[cnt[i]]g[i]=b[cnt[i]],下一個時刻就相當于對ggg做一次異或卷積,好吧,目前的復雜度是Θ(tnlogn)\Theta(tnlogn)Θ(tnlogn)
2.5 預處理
由于每次卷積都是卷同一個東西,所以預處理g1,g2,g3,g4???g^1,g^2,g^3,g^4···g1,g2,g3,g4???
即快速冪,復雜度Θ(nlognlogt)\Theta(nlognlogt)Θ(nlognlogt)
2.6 減少FWT、IFWT次數
我們回到2.4,發現復雜度瓶頸在FWTFWTFWT和IFWTIFWTIFWT,然而并不需要每次FWT()FWT()FWT()數組對應系數乘后IDFT回來,直接在里面乘就好了,復雜度Θ(nlogn+nt)\Theta(nlogn+nt)Θ(nlogn+nt)
2.7 快速冪優化
發現2.6里的乘系數每次都是乘同一個數,快速冪一下,復雜度Θ(nlogn+nlogt)\Theta(nlogn+nlogt)Θ(nlogn+nlogt)
2.8 Tip
發現這題有一個很坑的地方,就是p可能是2的倍數,而FWT的時候要除以n,然后就會掛
例如:n=2n=2n=2,p=6p=6p=6,現在有個數當前算出來值為888,直接除以222結果是444,先模ppp結果為88%6=28,再除以222為結果為111,就掛了
然后就需要將模數擴大nnn倍
n?pn*pn?p是101910^{19}1019兩個數直接乘會爆long long,慢速乘又會帶來Θ(logn)\Theta(logn)Θ(logn)的復雜度,不是很優越,所以需要一些奇技淫巧優化這個乘法
代碼
到這里這個算法已經可以通過此題了,貼出AC代碼
#include<cstdio> #include<cctype> #include<algorithm> #define rg register typedef long long LL; template <typename T> inline T max(const T a,const T b){return a>b?a:b;} template <typename T> inline T min(const T a,const T b){return a<b?a:b;} template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;} template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;} template <typename T> inline T abs(const T a){return a>0?a:-a;} template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;} template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);} template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;} template <typename T> inline T square(const T x){return x*x;}; template <typename T> inline void read(T&x) {char cu=getchar();x=0;bool fla=0;while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}while(isdigit(cu))x=x*10+cu-'0',cu=getchar();if(fla)x=-x; } template <typename T> inline void printe(const T x) {if(x>=10)printe(x/10);putchar(x%10+'0'); } template <typename T> inline void print(const T x) {if(x<0)putchar('-'),printe(-x);else printe(x); } LL mod=998244353;const int lenth=1048576; inline LL msc(LL a,LL b) {LL v=(a*b-(LL)((long double)a/mod*b+1e-8)*mod);return v<0?v+mod:v; } inline LL pow(LL a,LL b) {LL res=1;for(;b;a=msc(a,a),b>>=1)if(b&1)res=msc(a,res);return res; } LL n,m,t,a[lenth],b[lenth],c[21];int cnt[lenth]; inline void FWT(LL*A,const int fla) {for(rg int i=1;i<n;i<<=1)for(rg int j=0;j<n;j+=(i<<1))for(rg int k=0;k<i;k++){const LL x=A[j+k],y=A[j+k+i];A[j+k]=(x+y)%mod;A[j+k+i]=(x+mod-y)%mod;}if(fla==-1)for(rg int i=0;i<n;i++)A[i]/=n; } int main() {read(m),read(t),read(mod),n=1<<m,mod*=n;for(rg int i=0;i<n;i++)read(a[i]);for(rg int i=1;i<n;i++)cnt[i]=cnt[i^(i&-i)]+1;for(rg int i=0;i<=m;i++)read(c[i]);for(rg int i=0;i<n;i++)b[i]=c[cnt[i]];FWT(a,1),FWT(b,1);for(rg int i=0;i<n;i++)a[i]=msc(a[i],pow(b[i],t));FWT(a,-1);for(rg int i=0;i<n;i++)print(a[i]),putchar('\n');return 0; }3 優化
然而,這道題可以進行優化
3.1 加強版數據范圍(毒瘤)
1≤t≤2100001\le t\le 2^{10000}1≤t≤210000
3.2 思考
對于之前的那個算法,顯然是無法接受這個數據范圍的,所以我們現在需要換個思路
新定義:
定義兩個位置iii、jjj的距離dis(i,j)=cnt[i⊕j]dis(i,j)=cnt[i\oplus j]dis(i,j)=cnt[i⊕j]
對于一個位置xxx,每次能向它轉移的位置yyy有2m2^m2m個,所以暴力復雜度非常不優。但是你會發現,轉移的位置雖然多,但是系數的種類卻不多,對xxx位置產生貢獻的系數按與xxx位置的距離分只有m+1m+1m+1種,我們就可以觀察是否可以在這里進行突破
當然可以,不然我說這個干什么
容易發現,這m+1m+1m+1種系數就是輸入的那個b[]b[]b[],而在ttt時刻,仍然是有一個bt[]b_t[]bt?[]的(在之后,這個下標代表時刻),只是數值上發生了變化
那么如果我們能夠快速的求出ttt時刻的b數組,那么我們剩下要做的就是一遍FWT,復雜度只剩Θ(nlogn)\Theta(nlogn)Θ(nlogn)了
3.3 分析
要計算bt[]b_t[]bt?[],我們要從bt?1[]b_{t-1}[]bt?1?[]推過來
轉移顯然是Θ(m2)\Theta(m^2)Θ(m2)的,那么轉移的系數是什么呢?
考慮bt?1[j]b_{t-1}[j]bt?1?[j]對bt[i]b_t[i]bt?[i]的貢獻
- step1
把數值字母化,對于一組x,y,zx,y,zx,y,z滿足dis(x,y)=j,dis(x,z)=idis(x,y)=j,dis(x,z)=idis(x,y)=j,dis(x,z)=i
bt?1[j]b_{t-1}[j]bt?1?[j]相當于t?1t-1t?1時刻xxx對yyy的貢獻
bt[i]b_t[i]bt?[i]相當于ttt時刻xxx對zzz的貢獻 - step2
已知dis(x,y)=jdis(x,y)=jdis(x,y)=j即zzz中對disdisdis貢獻為111的位有jjj位,為000的有m?jm-jm?j位
我們枚舉yyy變到zzz的過程中,貢獻111變000的位數s1s_1s1?和貢獻000變111的位數s1s_1s1?
容易發現j?s1+s0=ij-s_1+s_0=ij?s1?+s0?=i,并且dis(y,z)=s0+s1dis(y,z)=s_0+s_1dis(y,z)=s0?+s1?,貢獻系數為b[s0+s1]?C(j,s1)?C(m?j,s0)b[s_0+s_1]*C(j,s_1)*C(m-j,s_0)b[s0?+s1?]?C(j,s1?)?C(m?j,s0?)
枚舉s0s_0s0?,那么s1=j?i+s0s_1=j-i+s_0s1?=j?i+s0?,把式子算出來即可
現在知道轉移的系數了,那么就可以用矩陣乘法優化,復雜度是Θ(m3logt)\Theta(m^3logt)Θ(m3logt)的,算法總復雜度為Θ(m3logt+nlogn)\Theta(m^3logt+nlogn)Θ(m3logt+nlogn)
3.4 代碼
貼出AC代碼
#include<cstdio> #include<cctype> #include<algorithm> #include<cstring> #define rg register typedef long long LL; template <typename T> inline T max(const T a,const T b){return a>b?a:b;} template <typename T> inline T min(const T a,const T b){return a<b?a:b;} template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;} template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;} template <typename T> inline T abs(const T a){return a>0?a:-a;} template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;} template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);} template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;} template <typename T> inline T square(const T x){return x*x;}; template <typename T> inline void read(T&x) {char cu=getchar();x=0;bool fla=0;while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}while(isdigit(cu))x=x*10+cu-'0',cu=getchar();if(fla)x=-x; } template <typename T> inline void printe(const T x) {if(x>=10)printe(x/10);putchar(x%10+'0'); } template <typename T> inline void print(const T x) {if(x<0)putchar('-'),printe(-x);else printe(x); } LL mod=998244353;const int lenth=1048576; inline LL msc(LL a,LL b) {LL v=(a*b-(LL)((long double)a/mod*b+1e-8)*mod);return v<0?v+mod:v; } const int maxn=21; int size=21; struct Martix {LL a[maxn][maxn];Martix(){};Martix(const int x){if(x==0)memset(a,0,sizeof(a));else if(x==1){memset(a,0,sizeof(a));for(rg int i=0;i<size;i++)a[i][i]=1;}}Martix operator *(const Martix&b)const{Martix x=0;for(rg int i=0;i<size;i++)for(rg int j=0;j<size;j++)for(rg int k=0;k<size;k++)x.a[i][j]=(x.a[i][j]+msc(a[i][k],b.a[k][j]))%mod;return x;} }bz,final; template <typename T,typename sum>inline T pow(T x,const sum y) {T res=1;for(rg sum i=1;i<=y;i<<=1,x=x*x)if(i&y)res=res*x;return res; } LL n,m,t,a[lenth],b[lenth],c[21];int cnt[lenth]; inline void FWT(LL*A,const int fla) {for(rg int i=1;i<n;i<<=1)for(rg int j=0;j<n;j+=(i<<1))for(rg int k=0;k<i;k++){const LL x=A[j+k],y=A[j+k+i];A[j+k]=(x+y)%mod;A[j+k+i]=(x+mod-y)%mod;}if(fla==-1)for(rg int i=0;i<n;i++)A[i]/=n; } LL C[21][21]; int main() {read(m),read(t),read(mod),n=1<<m,mod*=n;for(rg int i=0;i<n;i++)read(a[i]);for(rg int i=1;i<n;i++)cnt[i]=cnt[i^(i&-i)]+1;for(rg int i=0;i<=m;i++)read(c[i]);final.a[0][0]=1;for(rg int i=0;i<=m;i++){C[i][0]=1;for(rg int j=1;j<i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;C[i][i]=1;}for(rg int j=0;j<=m;j++)for(rg int i=0;i<=m;i++){for(rg int s0=0;s0<=m;s0++){const int s1=j-i+s0;if(s0>m-j)continue;if(s1>j||s1<0)continue;bz.a[i][j]=(bz.a[i][j]+msc(msc(c[s0+s1],C[m-j][s0]),C[j][s1]))%mod;}}final=final*pow(bz,t);for(rg int i=0;i<n;i++)b[i]=final.a[0][cnt[i]];FWT(a,1),FWT(b,1);for(rg int i=0;i<n;i++)a[i]=msc(a[i],b[i]);FWT(a,-1);for(rg int i=0;i<n;i++)print(a[i]),putchar('\n');return 0; }4 總結
這是一道非常不錯的題,想要AC此題并不是很難,我寫這題的博客是因為它的優化非常巧妙,也沒有用到很難的知識點,值得深思
還有要把慢速乘背下來
總結
以上是生活随笔為你收集整理的Little Pony and Elements of Harmony(CF 453 D)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 快速沃尔什变换(FWT)
- 下一篇: 数论学习