[考试反思]数学专题测试1:遗失
夹在一大排并列里没什么想法。。。
论我的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 }