[考试反思]数学专题测试3:发现

唔。。。咕了好久啊。。。但是对我而言联赛后能得一次rk1简直难于上青天,所以还是要写博客纪念一下的。

其实这场考试也没什么好说的,又不是凭什么真正的知识储备取得的成绩。。。

只不过是大神们不稀罕用打表之类无聊的手段,刚好被我抓住了一次机会而已。

赶巧T1和T3的区分度都不大,况且我T3还炸了,只不过T2侥幸打表找到了规律恰好T2没有规律就根本不可做,所以拉开了分差。。。

这场考试简直就像是上天安排的对菜鸡的一次安慰一样。

就算你啥都不会,只要运气够好就可以rk1

其实也是挺巧,因为之前刚颓过《color有色图》的题解知道了分配去重的技巧所以拿到了第二档部分分。

因为多数人都认为现在做那种大神题是在浪费题,所以只有少数人颓了题解2333于是我又赚了

T2打了一个小时表。。。我估计其他人应该是不会把如此大量的时间花在这上面吧。。。但没办法啊我啥都不会除了打表还能怎么办啊?

于是就把大神们用来思考的时间拿来打表。。。在大量的猜测之下就随便写了一个,抱着稳住n=7的10分其余随意的心态就写了

我感觉最近一段时间的rp应该都差不多透支完了

T3做的不好。首先读题不认真,题目给的是点值没细看当成系数做了,调了一个小时样例。。。

不会拉格朗日插值于是少了一档暴力分,而且式子非常冗余可以化简导致消元之外的复杂度比别人多个n。

其实高斯消元就足以拿到30+5分,但是因为式子太麻烦常数过大被卡成15+5分。

如果把这些侥幸的因素去掉,就是rk9了啊。。。

但是不得不说:

打表是真的爽!

乱搞的确是有用的技巧!

(虽说我到目前为止打表的成功率也就30%左右,但是那种题目只输入1个参数的题还是可以试一试的)

 

T1:young

题目大意:n点图,点权$[0,2^m)$内随机,边权为两点异或。求所有情况下最小生成树权和。$mod \ 258280327=2\times 3^{17}+1$

对于$30\%$的数据$n \times m \le 16$。对于$50\%$的数据$m \le 4$。对于$100\%$的数据$n \le 50,m \le 8$。

这题部分分可以讲一下。

$30\%$搜索。

$50\%$,点权相同的点可以合并,点编号与答案无关,于是将点按权去重排序,后者严格大于前者再搜索,整数加法分配,状态数不多。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define mod 258280327
 4 int C[55][55],f[55],n,m,ans,v[55],N=1,fa[55];
 5 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;}
 6 struct Ed{int a,b,v;friend bool operator<(Ed x,Ed y){return x.v<y.v;}}E[1234];
 7 int find(int p){return fa[p]==p?p:fa[p]=find(fa[p]);}
 8 void sch(int al){
 9     if(al==N+1){
10         int ec=0,c=0; for(int i=1;i<=N;++i)fa[i]=i;
11         for(int i=1;i<=N;++i)for(int j=i+1;j<=N;++j)E[++ec]=(Ed){i,j,v[i]^v[j]};
12         sort(E+1,E+1+ec);
13         for(int i=1,A=0;A<n&&i<=ec;++i)if(find(E[i].a)!=find(E[i].b))fa[fa[E[i].a]]=fa[E[i].b],c+=E[i].v,A++;
14         ans=(ans+1ll*f[N]*c)%mod;
15         return;
16     }for(int j=v[al-1]+1;j<1<<m;++j)v[al]=j,sch(al+1);
17 }
18 int main(){
19     cin>>n>>m;v[0]=-1;
20     for(int i=0;i<=50;++i)C[i][0]=f[i]=1;
21     for(int i=1;i<=50;++i)for(int j=1;j<=n;++j)f[i]=f[i]*1ll*i%mod;
22     for(int i=1;i<=50;++i)for(int j=1;j<=i;++j)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
23     for(int i=1;i<=50;++i)for(int j=1;j<i;++j)f[i]=(f[i]-1ll*f[j]*C[i][j]%mod+mod)%mod;
24     for(;N<=n;++N)sch(1);
25     cout<<1ll*ans*qp(qp(2,n*m),mod-2)%mod<<endl;
26 }
View Code

$100\%$的话就是大神$dp$了。设$f(n,m)$表示$n$点$2^m$权图的最小生成树权和。

$g(s,t,m)$表示所有情况下大小为$s,t$的两个点集,权范围为$2^m$时所有情况中使两个点集联通的最小代价之和。

$p(s,t,m,k)$表示两个点集之间连边边权大于等于$k$的方案数。注意是方案数而不是权。

存在转移$g(s,t,m)=\sum\limits_{i=1}^{2^m-1} p(s,t,m,i)$。这比较显然。

考虑$f$的转移。对于当前的$n$的点的第$m$位,我们将它按照这一位上是$0$还是$1$分成两组。

设这一位是$0$的点集为$s$。其余为$t$。我们枚举$s$。

如果$s=0$或$t=0$那么无论如何连边都不会产生这一位为$1$的代价。否则想让它们联通就一定有$2^{m-1}$的代价。不论更低位是几。

所以只要$s,t$同时存在那么就会产生$2^{m-1} \times (2^{m-1})^{n}=2^{(m-1)(n+1)}$。乘号后面那个的含义就是所有点随便选。

这样的话这一位的贡献我们考虑完了,我们分别去考虑$s$内部,$t$内部与$s,t$之间的连边。

前两者这一位没有贡献就是$f(s,m-1)\times 2^{(m-1)(n-i)}+f(t,m-1) \times 2^{(m-1)s}$。后者去掉当前位的贡献就是$g(s,t,m-1)$。

前面乘2的幂是表示,因为$s,t$两个点集是独立计算的,而你要计算所有情况,所以对于每种可能的$t$集都会让所有的$s$集贡献答案。

所以总的转移式就是:

$f(n,m)=\sum\limits_{s=0}^{n} C_n^s(2^{(m-1)(n-i)}f(s,m-1)+ 2^{(m-1)s}f(t,m-1) + g(s,t,m-1) + [s \neq 0\&t \neq 0]\times 2^{(m-1)(n+1)} )$

接下来就只剩下了$p$的转移。大致意思差不多,但是是方案数。

分别讨论$s$与$t$集合中这一位是$0$和$1$的点数,再依据$k$这一位是$0/1$分类讨论。

因为你会连最小的边,所以当$(s0,t0)$或$(s1,t1)$同时存在时你这一位一定是$0$。

所以如果$k$这一位上是$1$那么一定代表只存在$(s0,t1)$或$(s1,t0)$。递归处理,答案就是$2 \times p(s,t,m-1,k\ xor \ (1<<m-1))$

如果$k$这一位上是$0$,那么就直接枚举$s0,t0$的大小,两边互不影响方案数相乘,再乘上选点的组合数。

转移到此为止,但是还要考虑递归边界的问题。

对于$f$。当点数或权为$0$时答案当然是$0$。$g$也是同理。

但是$p$不是。$p$是方案数。如果$m=0$那么一定代表这$k=0$,因为高位的$1$都被消去了,所以贡献$1$的方案。

而考虑如果有一个点集为$0$了。那么无论如何连边都不能使之联通,代价为$\infty \geq k$。所以另一个点集可以任选。

递归边界卡紧。再加一个剪枝就是,因为$s,t$点集的顺序是无关的,保证$s\le t$。再加一发记忆化,搜就行了。

理论复杂度$O(n^4m2^m)$。然而实际运行效率很优秀。不行就把表打出来也可以。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define mod 258280327
 4 unordered_map<int,int>f[55],g[55][55],p[55][55][9];
 5 int C[55][55],n,m,ans,N=1,pw[405];
 6 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;}
 7 int mo(int x){return x>=mod?x-mod:x;}
 8 int P(int S,int T,int m,int k){
 9     if(S>T)S^=T^=S^=T; if(!m)return !k; if(!S)return pw[T*m];
10     if(p[S][T][m].find(k)!=p[S][T][m].end())return p[S][T][m][k];
11     int ans=0;
12     if(k&1<<m-1)return p[S][T][m][k]=mo(P(S,T,m-1,k^1<<m-1)<<1);
13     for(int s=0;s<=S;++s)for(int t=0;t<=T;++t)if((s+T-t)*(t+S-s))ans=(ans+1ll*P(s,t,m-1,k)*P(S-s,T-t,m-1,k)%mod*C[S][s]%mod*C[T][t])%mod;
14     return p[S][T][m][k]=mo(ans+pw[(S+T)*(m-1)+1]);
15 }
16 int G(int S,int T,int m){
17     if(S>T)S^=T^=S^=T; if(!m||!S)return 0;
18     if(g[S][T].find(m)!=g[S][T].end())return g[S][T][m];
19     int ans=0;
20     for(int k=1;k<1<<m;++k)ans=mo(ans+P(S,T,m,k));
21     return g[S][T][m]=ans;
22 }
23 int F(int n,int m){
24     if(!m||n<=1)return 0;
25     if(f[n].find(m)!=f[n].end())return f[n][m];
26     int ans=0;
27     for(int i=0;i<=n;++i)ans=(ans+(1ll*pw[(n-i)*(m-1)]*F(i,m-1)+1ll*pw[i*(m-1)]*F(n-i,m-1)+G(i,n-i,m-1)+(i&&i!=n?pw[(n+1)*(m-1)]:0))%mod*C[n][i])%mod;
28     return f[n][m]=ans;
29 }
30 int main(){
31     for(int i=0;i<=50;++i)C[i][0]=1;pw[0]=1;
32     for(int i=1;i<=50;++i)for(int j=1;j<=i;++j)C[i][j]=mo(C[i-1][j-1]+C[i-1][j]);
33     for(int i=1;i<=400;++i)pw[i]=mo(pw[i-1]<<1);
34     cin>>n>>m;cout<<1ll*qp(pw[n*m],mod-2)*F(n,m)%mod<<endl;
35 }
View Code

 

T2:simple

题目大意:对于所有可含前导0的n位数$X$若其满足任意$1 \le k < n,X \times 10^k \  mod \ 10^n >X$,则产生$n^2$。贡献。求所有$k\le n$位数的贡献。

对于$10\%$的数据$n=7$。对于$100\%$的数据$n=10^{10},mod=258280327=2\times 3^{17}+1$。

对于这种只读入一个参数的题目可以考虑打表。

正解是考虑那个式子的含义,其实是十进制下的左移,对于有循环节的数显然不成立,否则对于$n$位数它的最大循环位移合法其余均不合法。

所以答案是$\sum\limits_{i=1}^{n} i^2 \sum\limits_{d|i} \mu(\frac{i}{d}) \frac{10^i}{d}$

化简,再套个等差成等比,发现要求的就是$i \times \mu(i)$的前缀和。卷个$id$就可以杜教筛。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define S 20000005
 4 #define mod 258280327 
 5 #define ll long long
 6 const int I9=229582513,I81=1ll*I9*I9%mod;
 7 unordered_map<int,int>Mu;
 8 int p[S],M,pc,mu[S];char np[S];ll n;
 9 int MU(ll x){int a=1;
10     if(x<M)return mu[x]; if(Mu.find(x)!=Mu.end())return Mu[x];
11     for(ll i=2,N,l;N=x/i,i<=x;i=l+1)l=x/N,a=(a-(l-i+1ll)%mod*(i+l)%mod*129140164%mod*MU(N)%mod+mod)%mod;
12     return Mu[x]=a;
13 }
14 int qp(ll b,ll t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;}
15 ll cal(ll n){ll P=qp(10,n+1);return ((n*P-10)%mod*I9%mod-(P-100)*I81%mod+mod)%mod;}
16 int main(){//freopen("1.in","r",stdin);
17     cin>>n;M=pow(n,0.72);mu[1]=1;
18     for(int i=2;i<M;++i){
19         if(!np[i])mu[p[++pc]=i]=-i;
20         for(int j=1,x;j<=pc&&(x=p[j]*i)<M;++j)
21             if(i%p[j])np[x]=1,mu[x]=-mu[i]*p[j];
22             else {np[x]=1;break;}
23     }int a=0;
24     for(int i=2;i<M;++i)mu[i]=(mu[i-1]+mu[i]+0ll+mod)%mod;
25     for(ll i=1,N,l,cl=0,cn;N=n/i,i<=n;i=l+1)l=n/N,cn=cal(l),a=(a+1ll*(cn-cl+mod)%mod*MU(N))%mod,cl=cn;
26     cout<<a<<endl;
27 }
View Code

 

T3:小H爱染色(Loj2504)

题目大意:编号$[0,n)$的球连续两次可重地从中挑取$m$个染色。求所有方案下$F(A)$的期望。$A$表示被染色球中编号最小的。

给出对于$0\le i \le m$的$F(i)$值。你可以认为$F$是个不高于$m$次的多项式。

对于$100\%$的数据,$n \le mod=998244353,m \le 10^6$

据说严重卡常。看了式子挺暴力的。上网直接搜题解。用空回来填坑。

大体分两种做法.

第一种可以看这个博客,我没有写。

第二种做法比较神奇,看着题解弄了半天,拿出来说一说。

 

对于$F(x)=x$。我们换一个角度来思考问题,转化一下问题含义:设$A$表示某一种选法得到的最小黑球。

问题可以转化为:求期望有多少个白球其左侧没有黑球。也即期望有多少球编号小于$A$

我们分开讨论每个球的贡献。枚举最终黑色球的数量$k(m \le k \le 2m)$。

那么算上正在做贡献的白球,一共有$k+1$个。选法是$C_n^{k+1}$种。这样选出来,最靠左的就是白的,剩下的就是黑的,与每种方案对应上了。

但是还没有完。关于你是怎么选出这$k$个的,分两轮选。

第一轮选的肯定在这$k$个里面乱选的,方案数是$C_k^m$。

第二轮一定会选没被选过的$k-m$个,剩下$m-(m-k)$个在选过的里乱选乱选,方案数是$C_{m}^{2m-k}$

所以选出若干球的方案数$H_k=C_k^m \times C_{m}^{2m-k}$

这个部分分的答案就是$\sum\limits_{k=m}^{2m} H_k \times C_n^{k+1}$

 

对于$F(x)=x^c$。还是转化题意,这次是:可重复的选$c$次球编号都小于$A$的方案数的期望。

可重复不是很好处理,我们设$q$表示最后选择$c$次后被选过的球一共多少种。设方案数为$G_q$(就是第二类斯特林数乘阶乘)

根据含义列式$q^c=\sum\limits_{i=0}^{q} C_q^i G_i$。二项式反演一波得到$G_q=\sum\limits_{i=0}^{q} (-1)^{q-i} C_q^i i^c$

设$T_i$表示你选择的球和你染黑的球的总数为$i$的情况下的贡献。$T_i=\sum\limits_{j=0}^{i} G_j H_{i-j}$

$G,T$都是经典的卷积形式,直接$NTT$就可以了。

 

对于所有数据。其实就是上面这个的复合,只要把$G$公式中$i^c$替换成$F(i)$就好了。

所以就可以不用插值直接做了。时间复杂度$O(mlogm)$。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define S 3000003
 4 #define mod 998244353
 5 int n,m,fac[S],invv[S],inv[S],Cn[S],rev[S],N,f[S],a[S],G[S],H[S],T[S],ans;
 6 int C(int n,int m){return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;}
 7 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;}
 8 void NTT(int*a,int opt=1){
 9     for(int i=0;i<N;++i)if(rev[i]>i)swap(a[i],a[rev[i]]);
10     for(int i=1;i<N;i<<=1)for(int j=0,b=qp(3,(mod-1)/2/i*opt+mod-1);j<N;j+=i<<1)
11         for(int k=j,w=1,x,y;k<j+i;++k,w=1ll*w*b%mod)x=a[k],y=1ll*a[i+k]*w%mod,a[k]=(x+y)%mod,a[i+k]=(mod+x-y)%mod;
12     if(opt==-1)for(int iv=qp(N,mod-2),i=0;i<N;++i)a[i]=1ll*a[i]*iv%mod;
13 }
14 int main(){
15     fac[0]=invv[1]=inv[0]=fac[1]=inv[1]=Cn[0]=N=1;
16     for(int i=2;i<S;++i)fac[i]=1ll*fac[i-1]*i%mod,invv[i]=(mod-mod/i+0ll)*invv[mod%i]%mod,inv[i]=1ll*inv[i-1]*invv[i]%mod;
17     scanf("%d%d",&n,&m);
18     for(int i=1;i<S;++i)Cn[i]=Cn[i-1]*(n-i+1ll)%mod*invv[i]%mod;
19     for(int i=0;i<=m;++i)scanf("%d",&f[i]),f[i]=1ll*f[i]*inv[i]%mod,a[i]=i&1?mod-inv[i]:inv[i],H[i]=1ll*C(i+m,i)*C(m,i)%mod;
20     while(N<=m<<1)N<<=1; for(int i=0;i<N;++i)rev[i]=rev[i>>1]>>1|(i&1?N>>1:0);
21     NTT(f);NTT(a);
22     for(int i=0;i<N;++i)G[i]=1ll*a[i]*f[i]%mod;
23     NTT(G,-1);
24     for(int i=0;i<=m;++i)G[i]=1ll*G[i]*fac[i]%mod;
25     for(int i=m+1;i<N;++i)G[i]=0;
26     NTT(G);NTT(H);
27     for(int i=0;i<N;++i)T[i]=1ll*G[i]*H[i]%mod;
28     NTT(T,-1);
29     for(int i=0;i<=m<<1;++i)ans=(ans+1ll*Cn[i+m]*T[i])%mod; cout<<ans<<endl;
30 }
View Code

 

posted @ 2020-01-09 09:01  DeepinC  阅读(344)  评论(2编辑  收藏  举报