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 }

obelisk

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 }

ra

原文地址:https://www.cnblogs.com/leom10/p/11623873.html

时间: 2024-10-09 06:24:00

CSP-S 模拟测试57题解的相关文章

[考试反思]1002csp-s模拟测试57:平庸

一天两场,感觉要完. 不粘排行榜,太壮观了. #1:190 #2:180 #4:160 #35:150 #37:140 #39:120 #kx:20呃... 最后一个是考试结束后了. 又是CE盖40分.其实离#2不远... 调试语句没删干净,开O2编译出一条编译错误,没看,以为是那条关于scanf的warning. 然而并不是. 一定要仔细检查编译信息!!!交代码前一定要编译!!! 还有其实T1险些出锅,看错了题. 考试结束前7分钟重新看了一遍发现不对劲,+100pts. 看题!!! 要检查!!

[CSP-S模拟测试59]题解

以后题解还是单独放吧. A.Divisors 根号筛求所有数的因子,扫一遍去重统计即可. #include<cstdio> #include<iostream> #include<cstring> #include<vector> #include<map> using namespace std; const int N=205; int a[N],m,n; map<int,int> bu; vector<int> re

csp-s模拟测试57(10.2)「天空龙」&#183;「巨神兵」&#183;「太阳神」

题目是古埃及神话??? A. 天空龙 傻逼模拟,看来没有滑天下之大稽QAQ,也没有打错快读(大雾...) B. 巨神兵 难度爆增,一脸懵比..... 60分状压: 因为是求有向图,关于有向图好像拓扑用的很多,考虑到每个图的拓扑序是一定的 那么我们可以借此转移,设f[i][j]为当前点的状态为i,出度为零的点的度数为j 向下一层转移时枚举下一层的点集,那么点集S中每个点一定要和j连边,可以和i中除j以外的点连边 然后对于每个点cnt1,表示除j以外与i的连边,cnt2表示与j的连边,该点的贡献为2

CSP-S模拟测试69 题解

一如既往的垃圾,又回到了那个场场垫底的自己,明明考场上都想到正解了,但是就是拿不到分,可能是互奶把rp用光了吧以后一定加强训练代码能力. T1: 考场上一直yy矩阵快速幂,虽然自己矩阵快速幂一点都不会还是硬着头皮yy,发现不可做之后并没有及时转化思路,但其实自己预处理的数组就是正解. 切记:不仅矩阵快速幂是log的,普通快速幂也是2333 然后这题其实很水啊,我们设$dp[i][j]$为前$i$列放$j$个棋子的方案数,然后枚举最后一列放多少个棋子就好了. 转移方程为$dp[i][j]=\sum

[CSP-S模拟测试96]题解

以后不能再借没改完题的理由不写题解了…… A.求和 求$\sum \sum i+j-1$ 柿子就不化了吧……这年头pj都不考这么弱智的公式化简了…… 坑点1:模数不定,可能没有2的逆元,那么只要先把乘数里的2去掉就好了. 坑点2:1e18炸long long $\rightarrow$ 慢速乘即可 #include<cstdio> #include<iostream> #include<cstring> #include<vector> using name

[CSP-S模拟测试97]题解

A.小盆友的游戏 感觉题解解释的很牵强啊……还是打表找规律比较靠谱 对于每个人,它构造了一个期望函数$f(x)$,设它的跟班个数为$cnt[x]$,那么令$f(x)=2^{cnt[x]}-1$(??鬼知道为什么要等于这个) 然后再定义当前局面的期望函数为每个人期望函数之和. 然后你会发现每次猜拳后局面期望函数变化量都是1 那么期望步数其实就是终止局面期望函数值-初始局面期望函数值 $ans=2^{n-1}-1-\sum (2^{cnt[x]}-1)$ #include<bits/stdc++.h

[CSP-S模拟测试53]题解

A.u 只涉及到区间修改可以考虑差分,然而如果每一行都差分复杂度还是过高.我们发现差分标记也是连续的(一行横着的一行斜着的),所以可以维护两个 差分的差分,扫两遍统计即可. #include<cstdio> #include<iostream> #include<cstring> using namespace std; typedef long long ll; const int N=2005; int read() { int x=0,f=1;char ch=ge

csps模拟测试57

T1 天空龙 大神题,考察多方面知识,例如:快读 附上考试代码,以供后人学习 1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 inline int read() 7 { 8 int x=0,f=1;char c=getchar(); 9 while(c<'0'||c>'9') {if

模拟测试57

T1: 贴心送分题. 对于每种颜色,如果多了,就会有多的数量除二的贡献,反之会有少的数量的需求. 最后判断贡献和需求哪个大即可. 时间复杂度$O(1)$. T2: 边数太多,考虑将状态记录在点上. 每一种可行方案是一个dag,可以按照拓扑序列分层. 状态记录当前选中的集合,和最后一层的点的集合,然后枚举状态更新即可. 更新时算出当前集合向拓展集合连的边数,每个点至少要有一条入边. 考虑优化,省去第二维状态. 枚举补集的子集,算出当前集合向拓展集合连的边数,每条边都可以连或不连. 但是这样会算重,