CSP-S 模拟测试57题解
人生第一次A,B层一块考rank2,虽然说分差没几分,但还是值得纪念。
题解:
T1 天空龙:
大神题,因为我从不写快读也没有写考场注释的习惯,所以不会做,全hzoi就kx会做,kx真大神级人物。
T2 巨神兵:
大神题,一看数据范围这么小,我们考虑状压,最傻逼的暴力思路是压边,但是这显然不行。正解是压点,设$f[s]$为当前选定点集状态为$s$的方案数。
我们考虑转移,当前选定的点集肯定是可以通过边和没有连过来的点相连构成新的方案。所以转移所以我们考虑枚举补集的子集$k$,设$cnt$为s与k两个点集相连的边数,那么转移为$f[s|k]+=f[s]*2^{cnt}$?不,这样会算重,因为比如我们先加A点,再加B点,和先加B点,再加A点是一样的。所以考虑容斥,容斥系数为$(-1)^{size[k]+1}$,蒟蒻博主不会正着推,但是这个可以证明是对的。我们设$g[i]$为转移了$i$层的系数,那么显然$g[0]=1$,然后$g$的递推式为$g[i]=\sum_{j=1}^{i}{C_{j}^{i}*(-1)^{j+1}*g[i-j]}$,我们要证的是$g[i]=1$,首先$g[i-j]$可以消掉,因为你由3层转移到5层,和从0层转移到2层是一样的然后
$\sum_{j=1}^{i}{C_j^i*(-1)^{j+1}}$
$=(-1)*(\sum_{j=0}^{i}{C_j^i*(-1)^{j}}-C_j^0*(-1)^0)$
$=(-1)*((1-1)^i-1)=1$ 证毕。
然后转移用了很多预处理,主要是找两个点集相连的边,我们首先预处理每个点与之相连点的状态,将它与上当前枚举的补集就好,然后还要预处理的是,每个二进制数中1的个数和每个取出来的1,是第几位,然后dp就好了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define int long long 4 const int N=1900000,mod=1e9+7; 5 int first[N],nex[N],to[N],tot,rc[N],sta[N],ma[5242880],f[5242880],n,m,qpow[N],re[N]; 6 void add(int a,int b){ 7 to[++tot]=b,nex[tot]=first[a],first[a]=tot; 8 } 9 signed main(){ 10 scanf("%lld%lld",&n,&m); 11 for(int i=1;i<=m;++i){ 12 int x,y; 13 scanf("%lld%lld",&x,&y); 14 add(x,y); 15 } 16 for(int i=0;i<=(1<<n);++i){ 17 ma[i]=ma[i>>1]+(i&1); 18 } 19 // for(int i=1;i<=1<<n;++i) cout<<"ma["<<i<<"]=="<<ma[i]<<" ";cout<<endl; 20 f[0]=qpow[0]=1; 21 for(int i=1;i<=n*n;++i) qpow[i]=(qpow[i-1]<<1)%mod; 22 for(int i=0;i<=n+1;++i) rc[i]=(i&1)?-1:1; 23 // for(int i=0;i<=n+1;++i) cout<<rc[i]<<" ";cout<<endl; 24 for(int i=1;i<=n;++i){ 25 re[1<<i-1]=i; 26 for(int j=first[i];j;j=nex[j]){ 27 sta[i]|=1<<to[j]-1; 28 } 29 } 30 int Max=1<<n,cnt=0,maxn=Max-1; 31 for(register int s=0;s^Max;++s){register int kl=~s; 32 for(register int i=kl&maxn;i;i=(i-1)&kl){ 33 cnt=0; 34 for(register int j=s;j;j-=j&-j) cnt+=ma[sta[re[j&-j]]&i]; 35 // cout<<cnt<<" "; 36 // cout<<f[s]%mod*qpow[cnt]%mod<<" "; 37 (f[s|i]+=rc[ma[i]+1]*f[s]%mod*qpow[cnt]%mod)%=mod; 38 } 39 } 40 printf("%lld\n",(f[maxn]+mod)%mod); 41 }
T3 太阳神:
正难则反,lcm大于n的不好求,我们可以考虑求小于n的,即$n^2-\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}{[lcm(i,j)<=n]}$
然后再转化$n^2-\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}{[\frac{i\times j}{gcd(i,j)}<=n]}$,难点在于求后面的那部分,现在我们考虑枚举,$i,j$的最大公约数d,柿子就变成了$\sum\limits_{d=1}^{n}\sum\limits_{a=1}^{\frac{n}{d}}\sum\limits_{b=1}^{\frac{n}{d}}{[gcd(a,b)==1][a\times b\leq \frac{n}{d}]}$
我们考虑求$f_k=\sum\limits_{1\leq a,b\leq n}{[a\times b\leq k][gcd(a,b)==1]}$,我们设$S_k=\sum\limits_{1\leq a,b\leq n}{[a\times b\leq k]}$,这一个数论分块就可以求出,那么$f_k=S_k-\sum\limits_{t>1} f_{\frac{k}{t^2}}$,前面S函数是不考虑gcd为1的条件的,后面的t相当于是枚举a,b的因子,同时把a,b因子提出即可得到$\frac{k}{t^2}$,然后就是关于f函数的求法,把它差分一下得到g[k],那么g[k]的含义就是$\sum\limits [a\times b=k][gcd(a,b)=1]$的a,b对数
$g[k]=2^cnt$,cnt为k的质因子种数,因为每种质因子,只能全部给a或b,而不能一部分给a,一部分给b,因为那样的话$gcd(a,b)$就不是1,这样f线筛即可,当n较小时直接用线筛筛出来的,当n较大时用上面提到的方法迭代即可。最外层数论分块,时间复杂度O(玄学)$O(n^{\frac{2}{3}})$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define int long long 4 const int N=1e7+10,mod=1e9+7; 5 int v[N],prime[N],num,f[N],n; 6 void init(){ 7 f[1]=1; 8 for(int i=2;i<=10000000;++i){ 9 if(!v[i]){ 10 prime[++num]=i; 11 f[i]=2; 12 v[i]=1; 13 } 14 for(int j=1;j<=num&&i*prime[j]<=10000000;++j){ 15 v[i*prime[j]]=1; 16 if(i%prime[j]==0){ 17 f[i*prime[j]]=f[i]; 18 break; 19 } 20 f[i*prime[j]]=f[i]*f[prime[j]]%mod; 21 // cout<<f[i]<<" "<<f[prime[j]]<<" "<<f[i*prime[j]]<<endl; 22 } 23 } 24 } 25 int calS(int x){ 26 int ans=0; 27 for(int l=1,r;l<=x;l=r+1){ 28 r=x/(x/l); 29 (ans+=(r-l+1)*(x/l)%mod)%=mod; 30 } 31 return ans; 32 } 33 34 int calF(int x){//cout<<x<<" "<<endl; 35 if(x<=10000000) return f[x]; 36 int ans=0; 37 ans=calS(x)%mod; 38 for(int i=2;i*i<=x;++i){ 39 (((ans-=calF(x/(i*i))+mod)%=mod)+=mod)%=mod; 40 } 41 return ans; 42 } 43 44 signed main(){ 45 scanf("%lld",&n); 46 init(); 47 //for(int i=1;i<=20;++i) cout<<"f["<<i<<"]=="<<f[i]<<" ";cout<<endl; 48 for(int i=1;i<=10000000;++i) (f[i]+=f[i-1])%=mod; 49 int ans=(n%mod*(n%mod))%mod; 50 for(int i=1,r;i<=n;i=r+1){ 51 r=n/(n/i); 52 // cout<<ans<<" "; 53 // cout<<i<<" "<<r<<endl; 54 (((ans-=calF(n/i)%mod*(r-i+1)%mod+mod)%=mod)+=mod)%=mod; 55 // cout<<calF(n/i)%mod*(r-i+1)%mod+mod<<endl; 56 } 57 printf("%lld",(ans+mod)%mod); 58 }