noip模擬36
阿新 • • 發佈:2021-08-11
A. Dove 打撲克
由於每次合併都會減少一堆,所以哪怕最終每一堆個數都不一樣,最多隻有 \(\sqrt{n}\) 個
所以可以得出結論不同大小的堆的個數最多 \(\sqrt{n}\) 個
那麼把這些存進數組裡,只要保證每次操作是根號的,就可以保證在 \(m\sqrt{n}\) 的複雜度完成
統計答案時,可以用雙指標維護,配合字尾和預處理,還可以保證根號複雜度
B. Cicada 與排序
對於每一個數處理其最終在每個位置的概率,再乘位置即可算出期望
設 \(g[i]\) 表示這個樹到 \(i\) 位置的概率
考慮模擬歸併排序的過程進行遞迴(只遞迴當前數該去的半個區間)
這樣需要維護上一層向這一層的 \(g\)
首先維護一個 \(f\) 輔助 \(dp\) 值,\(f[i][j]\) 表示排序合併左右區間的時候左邊的選到第 \(i\) 個數,此時右邊選到第 \(j\) 個數的概率,這個很好轉移,\(f[i][j]=f[i-1][j]+f[i][j-1]\) 即可
考慮 \(g\) 通過 \(f\) 進行轉移
對於 \(g[i+j]\),如果從 \(f[i][j]\) 轉移會有一個問題:不能保證當前時刻選的是左區間的點
所以應該改為 \(f[i-1][j]/2\)
這樣還有一個問題,如果右邊已經到了盡頭,那麼其實左邊向下一個移動的概率不再是 \(\frac{1}{2}\) 而變成了 \(1\)
那麼相當於 \(\sum f[k][j-1]/2\)
這樣總複雜度是 \(n^3\) 的
程式碼實現
#include<bits/stdc++.h> using namespace std; #define int long long const int maxn=505; const int mod=998244353; int n,f[maxn][maxn],g[maxn],h[maxn],a[maxn],b[maxn],posl,posr,pos[maxn],ans[maxn],inv2; int 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-48; ch=getchar(); } return x*f; } int po(int a,int b=mod-2){ int ans=1; while(b){ if(b&1)ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } void solve(int l,int r,int pos,int w){ if(l==r){ g[l]=1; return ; } for(int i=l;i<=r;i++)b[i]=a[i]; sort(b+l,b+r+1); b[l-1]=0; b[r+1]=0; int st=0,ed=0; for(int i=l;i<=r;i++){ if(b[i]!=b[i-1]&&b[i]==w)st=i; if(b[i]!=b[i+1]&&b[i]==w)ed=i; } // if(w==3)cout<<st<<" "<<ed<<endl; int mid=l+r>>1; if(pos<=mid){ solve(l,mid,pos,w); for(int i=l;i<=mid;i++)b[i]=a[i]; sort(b+l,b+mid+1); b[l-1]=b[mid+1]=0; for(int i=l;i<=mid;i++){ if(b[i]!=b[i-1]&&b[i]==w)posl=i; if(b[i]!=b[i+1]&&b[i]==w)posr=i; } int num=0; for(int i=mid+1;i<=r;i++)if(a[i]==w)num++; for(int i=0;i<=posr-posl;i++){ for(int j=0;j<=num-1;j++){ h[st+i+j]=(h[st+i+j]+g[posl+i]*f[i][j]%mod*inv2)%mod; } int sum=0; if(!num)sum=1; else{ for(int j=0;j<=i;j++)sum=(sum+f[j][num-1])%mod; sum=sum*inv2%mod; } h[st+i+num]=(h[st+i+num]+g[i+posl]*sum)%mod; } for(int i=l;i<=r;i++)g[i]=0; for(int i=st;i<=ed;i++){ g[i]=h[i],h[i]=0; // if(pos==1)cout<<l<<" "<<r<<" "<<i<<" "<<g[i]<<endl; } } else{ solve(mid+1,r,pos,w); for(int i=mid+1;i<=r;i++)b[i]=a[i]; sort(b+mid+1,b+r+1); b[mid]=b[r+1]=0; for(int i=mid+1;i<=r;i++){ if(b[i]!=b[i-1]&&b[i]==w)posl=i; if(b[i]!=b[i+1]&&b[i]==w)posr=i; } int num=0; for(int i=l;i<=mid;i++)if(a[i]==w)num++; for(int i=0;i<=posr-posl;i++){ for(int j=0;j<=num-1;j++){ h[st+i+j]=(h[st+i+j]+g[posl+i]*f[i][j]%mod*inv2)%mod; // if(pos==5)cout<<"hhh "<<st+i+j<<" "<<h[st+i+j]<<endl; } int sum=0; if(!num)sum=1; else{ for(int j=0;j<=i;j++)sum=(sum+f[j][num-1])%mod; sum=sum*inv2%mod; } h[st+i+num]=(h[st+i+num]+g[i+posl]*sum)%mod; // if(pos==5)cout<<"ppp "<<st+i+num<<" "<<h[st+i+num]<<" "<<i<<" "<<sum<<endl; } // if(w==4)cout<<"ggg "<<l<<" "<<r<<" "<<st<<" "<<ed<<" "<<h[st]<<endl; for(int i=l;i<=r;i++)g[i]=0; for(int i=st;i<=ed;i++){ g[i]=h[i],h[i]=0; // if(pos==5)cout<<"ggg "<<l<<" "<<r<<" "<<i<<" "<<g[i]<<endl; } } return ; } void pre(){ f[0][0]=1; for(int i=1;i<=n;i++)f[i][0]=f[i-1][0]*inv2%mod,f[0][i]=f[0][i-1]*inv2%mod; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ f[i][j]=(f[i-1][j]+f[i][j-1])*inv2%mod; // cout<<i<<" "<<j<<" "<<f[i][j]<<endl; } } return ; } signed main(){ // freopen("sort101.in","r",stdin); // freopen("my.out","w",stdout); n=read(); for(int i=1;i<=n;i++){ a[i]=read(); } inv2=po(2); pre(); for(int i=1;i<=n;i++){ memset(g,0,sizeof g); memset(h,0,sizeof h); solve(1,n,i,a[i]); int ans=0; for(int j=1;j<=n;j++){ // if(i==1)cout<<g[j]<<" "; ans=(ans+j*g[j])%mod; } cout<<ans<<" "; } return 0; }
C. Cicada 拿衣服
非常神奇的一道題
首先注意對於單個數 \(OR-AND=XOR\),但對於多個數不是這樣的
用到一個性質,序列裡字首與和或的和最多變化 \(logn\) 次(因為每一位最多一次,不可能往回變)
再觀察當右端點固定時,當左端點往左延伸時,\(max\) 單調不減,\(min\) 單調不增