

夾在一大排並列里沒什么想法。。。
論我的T1都發生了些什么?變量聲明寫錯行,exLucas理解不深刻。
這都是聯賽的東西了啊。。。板子題,一如既往的爆炸。
然而T2的50分暴力也是板子,沾了進度稍快一點的光有一點印象,然而我並不知道正確的形式,於是在考場上試了一個小時的參數。。。
然而T3一直執着的認為記搜剪枝會比狀壓快,然而出題人顯然沒有留這一檔的分數。。。
其實T3已經猜到了狀態定義,然而那個轉移是真的不可想。。。
這次的4個半小時過的莫名快。。。轉眼就沒時間了
本來還想給T1寫個對拍的,如果真的寫了就算exLucas沒調出來那個變量也能發現,好歹也是70分。。。
還是有點太放松了。。。T3浪費過多時間,T1打的太慢(或者說根本就沒想快起來?),而T2又因為結論沒背花太長時間。。。
態度還是要端正一些,平時做過的題還是應該記住一些。。。
覺得自己能有60+的題目要寫對拍。。。要比死剛在暴力上好一些
以及不要總以為出題人沒有給你設分你能水過去,時間足夠的話可以試一試時間不夠的話還是別在這種事情上浪費太多時間了。
出題人毒瘤的很,數據怎么能不出滿呢?
T1:解方程
題目大意:n個正整數和為m,對於n1個變量有上界限制,對於n2個變量有上界限制。求方案數。
答案對$10007$,$262203414=2 \times 3 \times 11 \times 397 \times 10007$或$437367875=5^3 \times 7^3 \times 101^2$取模。
$n,m \le 10^9$,$n1,n2 \le 8$
看到數據范圍那個8就知道是狀壓枚舉容斥,然后就剩下一個exLucas+CRT了。
exLucas就是提出特定質數來進行運算。然而剩余部分的系數的算法是一個遞歸的過程,不斷考慮是p的倍數和非p的倍數兩部分進行考慮。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,n1,n2,P,T,m,p[6],pc,k[6],pk[6],lim[9],ans[6],fac[6][11111],Tm[6][11111],K=1,Tms=0; 4 int pow(int b,int t,int mod,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;} 5 void exgcd(int a,int b,int&x,int&y){ 6 if(!b){x=1;y=0;return;} 7 exgcd(b,a%b,x,y);int r=x;x=y;y=r-a/b*x; 8 } 9 int inv(int x,int P){int a,b;exgcd(x,P,a,b);return (a%P+P)%P;} 10 int CRT(){ 11 int tp=pk[1],la=ans[1]; 12 for(int i=2;i<=pc;++i){ 13 int k1,k2;exgcd(tp,pk[i],k1,k2); 14 k1*=ans[i]-la;la=(1ll*tp*k1%(tp*pk[i])+la+tp*pk[i])%(tp*pk[i]);tp*=pk[i]; 15 }return la; 16 } 17 void cal(int x,int i,int o){ 18 if(x<p[i]){K=K*(o==1?fac[i][x]:inv(fac[i][x],pk[i]))%pk[i];return;} 19 cal(x/p[i],i,o);Tms+=x/p[i]*o; 20 if(o==1)K=K*fac[i][x%pk[i]]%pk[i]*pow(fac[i][pk[i]],x/pk[i],pk[i])%pk[i]; 21 else K=K*inv(fac[i][x%pk[i]]*pow(fac[i][pk[i]],x/pk[i],pk[i]),pk[i])%pk[i]; 22 } 23 main(){//freopen("1.in","r",stdin); 24 cin>>T>>P;//if(P==437367875)return 0; 25 for(int i=2;i*i<=P;++i)if(P%i==0){ 26 p[++pc]=i;pk[pc]=1; 27 while(P%i==0)k[pc]++,pk[pc]*=i,P/=i; 28 }if(P^1)p[++pc]=P,k[pc]=1,pk[pc]=P; 29 for(int i=1;i<=pc;++i)fac[i][0]=1; 30 for(int i=1;i<=pc;++i)for(int j=1;j<=pk[i];++j)fac[i][j]=fac[i][j-1]*(j%p[i]?j:1)%pk[i]; 31 while(T--){ 32 cin>>n>>n1>>n2>>m; 33 for(int i=1;i<=n1;++i)cin>>lim[i]; 34 for(int i=1,x;i<=n2;++i)cin>>x,m-=x-1; 35 for(int I=1;I<=pc;++I){ 36 ans[I]=0; 37 for(int s=0;s<1<<n1;++s){int rt=1,M=m-1; 38 for(int j=0;j<n1;++j)if(s&1<<j)rt*=-1,M-=lim[j+1]; 39 if(M<n-1)continue;Tms=0,K=1;cal(M,I,1);cal(n-1,I,-1);cal(M-n+1,I,-1); 40 ans[I]=(ans[I]+rt*K*1ll*pow(p[I],Tms,pk[I])%pk[I]+pk[I])%pk[I]; 41 } 42 }cout<<CRT()<<endl; 43 } 44 }
T2:宇宙序列
題目大意:給定長為$2^n$的數列$A$,異或卷積$i$次得到$A_i$,求$\sum\limits_{i=0}^{p} A_{2^i}[j]$。
$p \le 10^9,n \le 18,mod=10007$
因為卷積是有結合律的,所以容易想到倍增。
那么暴力FWT卷積就有50分了。然而我不會,於是在考場上瘋狂試參。這東西也許不難證明,但是是真的難以yy。
事實上$A_0=A_0-A_1,A_1=A_0+A_1$就是DFT過程。IDFT求逆就行了。
然后這題模數很小容易想到找循環節,事實上整個數列的循環節長度為$5002$然而這沒用。
復雜度$O(mod^2)$的做法就是找每個數的循環節,因為點值是可加的所以就直接加。在最開始DFT后利用循環節直接得到前綴和的點值再IDFT。
然而正解是倍增。先對原數列DFT得到點值,然后我們只需要得到前綴和數列的點值IDFT回去就行。因為點值可加所以前綴和數列的點值其實也就是對應數列對應項的點值和。
對於原點值為$x$的位置,我們要求的是$\sum\limits_{i=0}^{p} x^{2^i}$,設$f[x][i]=\sum\limits_{j=0}^{2^i-1} x^{2^j}$
倍增得到$f$數組:$f[x][i]=f[x][i-1]+f[x^{2^{2^{i-1}}}][i-1]$。然后就像快速冪一樣一邊更新base一邊更新答案就可以了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define mod 10007 4 int n,a[262145],b[262145],f[mod][30],p,j,ans; 5 int pow(int b,int t,int m=mod,int a=1){for(;t;t>>=1,b=b*b%m)if(t&1)a=a*b%m;return a;} 6 int main(){ 7 cin>>n>>p>>j;n=1<<n;p++; 8 for(int i=0;i<n;++i)cin>>a[i]; 9 for(int i=1;i<n;i<<=1)for(int j=0;j<n;j+=i<<1)for(int k=0,x,y;k<i;++k) 10 x=a[j+k],y=a[j+k+i],a[j+k]=(x-y+10007)%10007,a[j+k+i]=(x+y)%10007; 11 for(int i=0;i<mod;++i)f[i][0]=i; 12 for(int t=1;t<30;++t)for(int i=0;i<mod;++i)f[i][t]=(f[i][t-1]+f[pow(i,pow(2,1<<t-1,mod-1))][t-1])%mod; 13 for(int i=0;i<n;++i)for(int t=29;~t;--t)if(p&1<<t)b[i]=(b[i]+f[a[i]][t])%mod,a[i]=pow(a[i],pow(2,1<<t,mod-1)); 14 for(int i=1;i<n;i<<=1)for(int j=0;j<n;j+=i<<1)for(int k=0,x,y;k<i;++k) 15 x=b[j+k],y=b[j+k+i],b[j+k]=(x+y)%10007*5004%10007,b[j+k+i]=(y-x+10007)*5004%10007; 16 cout<<b[j]<<endl; 17 }
T3:exp
題目大意:n座環狀摩天輪坐着一些人,摩天輪順時針旋轉速度為1。又在隨機時刻陸續來了一些人,有空座就上沒空座就等。求坐滿時所有人期望等待時間之和。
$T \le 10,|S| \le 200$。字符串中'.'代表沒人'X'表示有人。
考場上看出來這是區間dp,想到區間內整段都是'X'只有一個是'.'。然后又感覺這個必須正着做得像《三華聚頂》一樣存概率。
倒着做的話只能確定最后一步貢獻是$\frac{n-1}{2}$。倒數第二步就沒法做了。。
考場上就想到這里。覺得很不可做,就去研究記搜了。
其實大體思路已經有了,但是這個dp轉移的確是不好想。
首先按照套路:斷環成鏈是常識。拆成2倍鏈。
設$f[i][j]$表示$[i,j]$這段區間中所有的位置都是'X'而位置$j$上是一個'.'。這樣的話只要在這里面挑任意一個位置都會做到$j$
而對答案期望的貢獻是$\frac{j-i}{2}$。由於要正推,所以要存每個狀態的概率,設為數組$p[i][j]$
考慮這樣的狀態會從什么狀態轉移而來?一定是中間原來有一個斷檔被來的一個人補上了。
即原來是(l)XXXXX(k)oXXXXXXX(r)o。為了方便看'.'變成了'o'。一定是有一個人坐在了[l,k]之間把位置k添上了。
而$[l,k]$和$[k+1,r]$這兩部分都是上面$f$定義中的那種區間。
所以我們就可以枚舉$k$,$f[i][j]$可以從$f[i][k]$和$f[k+1][j]$轉移而來,新的步數(不是期望值)就是$f[i][k]+f[k+1][r]+\frac{k-l+1}{2}$
然而到現在為止我們還沒有考慮概率的問題。
首先我們發現我們所枚舉的$k$的實際含義就是$k$在$[l,r-1]$這段區間內是最后一個由'.'變為'X'的,對於不同的$k$概率不相同,設為$g(l,r,k)$。
那么像上面考慮$f$的貢獻一樣枚舉$k$來計算$p$,根據含義可以列式:$p[l][r]=\sum\limits_{k=l}^{r-1} g(l,r,k)$
這樣就得到了$p$。現在給$f$加上概率,那么就是$f[l][r]=\frac{1}{p[l][r]} \sum\limits_{k=l}^{r-1} g(l,r,k)\times(f[l][k]+f[k+1][r]+\frac{k-l+1}{2})$
剩下的問題就是怎么求$g$了。首先設$cnt[a][b]$為$[a,b]$中有多少個'.'。那么我只是欽定了k是最后一個,剩下的可以隨便排序。左右內部順序隨意只考慮之間的關系。
這樣的話就是在所有位置中給左邊元素選位置,是$C_{cnt[l][r-1]-1}^{cnt[l][k-1]}$。
其次的問題就是你在左邊必須恰好選$cnt[l,k]$個而在右邊必須恰好選$cnt[k+1,r-1]$個,這樣的概率是$\frac{(k-l+1)^{cnt[l,k]} \times (r-k)^{cnt[j+1,r-1]}}{(r-l+1)^{cnt[l,r-1]}}$
最后,為什么在第一步沒有考慮左右內部的順序?因為內部還有內部的限制,$k$和$r$必須分別是兩邊的最后一個,這個概率是$p(l,k)\times p(k+1,r)$
三部分相乘即為$p(l,r,k)$。(這種東西怎么可能想得到啊)
然后$dp$或記憶化搜索都可以。還是推薦$dp$吧,記憶化搜索碼量偏長常數也較大。
一定要記住特判掉都是'X'的情況!
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 402 4 double f[S][S],p[S][S],C[S][S],pw[S][S],ans;int n,cnt[S][S];char s[S],al[S][S]; 5 double P(int,int);double F(int,int); 6 double g(int l,int r,int k){ 7 int a=cnt[l][k],b=cnt[k+1][r]; if(s[r]=='X'||s[k]=='X')return 0; 8 return pw[k-l+1][a]*pw[r-k][b-1]/pw[r-l+1][a+b-1]*C[a+b-2][a-1]*P(l,k)*P(k+1,r); 9 } 10 double P(int l,int r){ 11 if(al[l][r]&1)return p[l][r]; p[l][r]=0; 12 if(s[r]=='X')return 0; if(cnt[l][r]==1)return 1; 13 for(int i=l;i<r;++i)p[l][r]+=g(l,r,i); 14 al[l][r]|=1; return p[l][r]; 15 } 16 double F(int l,int r){ 17 if(al[l][r]&2)return f[l][r]; f[l][r]=0; 18 if(s[r]=='X'||P(l,r)<1e-12||cnt[l][r]==1)return 0; 19 for(int i=l;i<r;++i)f[l][r]+=g(l,r,i)*(F(l,i)+F(i+1,r)+(i-l)/2.0); 20 al[l][r]|=2; return f[l][r]/=P(l,r); 21 } 22 int main(){ 23 for(int i=0;i<S;++i)pw[i][0]=C[i][0]=1; 24 for(int i=1;i<S;++i)for(int j=1;j<S;++j)pw[i][j]=pw[i][j-1]*i,C[i][j]=C[i-1][j-1]+C[i-1][j]; 25 int t;cin>>t;while(t--){ 26 cin>>s+1;n=strlen(s+1);for(int i=1;i<=n;++i)s[n+i]=s[i]; 27 for(int i=1;i<=n<<1;++i)cnt[i][i]=s[i]=='.'; 28 for(int i=1;i<=n<<1;++i)for(int j=i+1;j<=n<<1;++j)cnt[i][j]=cnt[i][j-1]+(s[j]=='.'); 29 if(!cnt[1][n]){puts("0");continue;} 30 memset(al,0,sizeof al); ans=(n-1)/2.0; 31 for(int i=1;i<=n;++i)ans+=F(i,i+n-1)*P(i,i+n-1); 32 printf("%.8lf\n",ans); 33 } 34 }
