8.29题解
T1
考试打表两小时,终于找出了正解的规律,结果zz低错,直接挂掉70分。。。。。。
其实说实话打表找规律很好用,最开始我是发现对于一个质数$x$,$x^k$的约数和就是$x$的约数和+$\sum\limits_{i=2}^{k}{x^i}$,当然我考试的时候zz,这实际上就是$\sum\limits_{i=0}^{k}{x^i}$,这个可以直接等比数列求和,继续找规律,我们可以发现约数和是个积性函数,设$x$的约数和为$f(x)$,$x=p_1^{c_1}{\times}p_2^{c_2}{\times}{\cdots}{\times}p_n^{c_n}$,那么$f(x)=f(p_1^{c_1}){\times}f(p_2^{c_2}){\times}{\cdots}{\times}f(p_n^{c_n})$,然后分解质因数,直接累计答案就可以了
细数我的zz操作
1.在${\%}$意义下,想要除一个数,就直接除了,忘记需要乘逆元了
2.直接把两个超大质因数乘起来了,忘记了我有一个大于${\sqrt{n}}$的质因数了,没取模,直接炸了
3.这个很让人无语,由于我没有发现质数的约数和,和后面拼起来是完整的等比数列,于是我的等比数列求和最小的时候是$b-1$项,他的数据里恰好有$b=0$的情况,然后我的快速幂就死了
1 #include<iostream> 2 #include<cstdio> 3 #include<vector> 4 #include<cmath> 5 #define int long long 6 #define maxn 1001000 7 #define mod 9901 8 using namespace std; 9 int a,b,cnt,ts=-1,ans=1,sum; 10 int ss[maxn],pd[maxn],jl[maxn],flag[maxn]; 11 vector <int> zys; 12 void shai(int x) 13 { 14 int s=sqrt(x); 15 for(int i=2;i<=s;++i) 16 { 17 if(!pd[i]) ss[++cnt]=i; 18 for(int j=1;j<=cnt&&i*ss[j]<=s;j++) 19 { 20 pd[i*ss[j]]=1; 21 if(i%ss[j]==0) break; 22 } 23 } 24 } 25 int KSM(int a,int b) 26 { 27 int ans=1; a=a%mod; 28 while(b>0) 29 { 30 if(b&1) ans=(ans*a)%mod; 31 b=b>>1; a=(a*a)%mod; 32 } 33 return ans%mod; 34 } 35 main() 36 { 37 scanf("%lld%lld",&a,&b); 38 if(b==0) {printf("1\n"); return 0;} 39 shai(a); 40 for(int i=1;i<=cnt;++i) 41 { 42 if(ss[i]>a) break; 43 if(!(a%ss[i])) zys.push_back(ss[i]); 44 while(!(a%ss[i])&&a>1) {jl[ss[i]]++; a/=ss[i];} 45 } 46 if(a!=1) {zys.push_back(a); ts=a;} 47 for(int i=0;i<zys.size();++i) 48 { 49 if(zys[i]!=ts) 50 { 51 sum=zys[i]+1; 52 int da=KSM(zys[i],b*jl[zys[i]]-1); da--; da=da*KSM(zys[i]-1,mod-2); 53 int ls=(((zys[i]%mod*zys[i])%mod)*da)%mod; 54 sum=(sum+ls)%mod; ans=(ans*sum)%mod; 55 } 56 else 57 { 58 sum=zys[i]+1; 59 int da=KSM(zys[i],b-1); da--; da=da*KSM(zys[i]-1,mod-2); 60 int ls=(((zys[i]%mod*zys[i])%mod)*da)%mod; 61 sum=(sum+ls)%mod; ans=(ans*sum)%mod; 62 } 63 } 64 printf("%lld\n",ans); 65 return 0; 66 }
T2
这题,我考场上什么都没发现。。。
首先如果1的个数小于n那一定无解,然后哦按照东坡的吃法我们会发现其实让东坡尽量吃最后面的答案一定不会变差,所以我们可以考虑倒序枚举,假设把1看作1,把0看作-1,东坡和乡亲肯定都不能闲着,无论如何,乡亲都不会闲着,所以只需要保证东坡一直有的吃,我们根据那个-1,1的转化,倒序扫后缀和,显然当后缀和小于-1的时候东坡就没得吃了,可以手玩,此时我们只需要找一个最近的1和东坡眼前的0交换位置就可以了,这么贪心一定是正确的,但是由于空间开不到,所以只能得到70分的好成绩
考虑用后缀和,我们可以把他变成以m为单位,扫每个组中的后缀和,再加上他后面的组的后缀和,就是实际的后缀和,而这个组中后缀和最小的地方就是0最多,需要被挪的,挪一个0,花费是1,-1-最小的后缀和就是需要挪的步数,取最大就可以了
接下来考虑每个m重复多次的问题,重复多次,这个最小值要么在第一次,要么在最后一次,处理一下就可以了
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #define maxn 20001000 5 #define maxx 10001000 6 #define maxs 1001000 7 using namespace std; 8 int n,m,t,js,tot,sum,num,ans; 9 int on[maxn],xl[maxn]; 10 char a[maxs]; 11 int main() 12 { 13 //freopen("meal4.in","r",stdin); 14 while(1) 15 { 16 scanf("%d%d",&n,&m); 17 if(!n&&!m) break; 18 memset(xl,0,sizeof(xl)); memset(on,0,sizeof(on)); 19 js=0; tot=0; sum=0; num=0; ans=0; 20 for(int i=1;i<=m;++i) 21 { 22 scanf("%s%d",a,&t); 23 int len=strlen(a); 24 for(int j=1;j<=t;++j) 25 for(int k=0;k<len;++k) 26 { 27 if(a[k]=='0') xl[++js]=0; 28 else {xl[++js]=1; tot++; on[tot]=js;} 29 } 30 } 31 if(tot<n) {printf("-1\n"); continue;} 32 for(int i=2*n;i>=1;--i) 33 { 34 if(xl[i]==1) {sum++; num++;} 35 else 36 { 37 sum--; 38 if(sum==-2) 39 { 40 int pos=on[tot-num]; 41 sum=0; xl[i]=1; xl[pos]=0; num++; 42 ans=max(ans,i-pos); 43 } 44 } 45 } 46 printf("%d\n",ans); 47 } 48 return 0; 49 }
1 //读入每一段的时候处理出来当前点在每一段中的后缀和,记录下这一段中的min和左端点的后缀和 2 //而对于不断重复的每一个m,整个m中的最小值要么出现在第一段,要么出现在最后一段 3 //第一段的话sum是几就是几在做贡献,最后一段的话做贡献的应该是sum+(t-1)sum<左> 4 //最后的答案就是-1-最小的sum,因为最少需要这么多的0被移动 5 #include<iostream> 6 #include<cstring> 7 #include<cstdio> 8 #define int long long 9 #define maxm 100100 10 #define maxs 1001000 11 #define inf 2000000000000000000 12 using namespace std; 13 int n,m,minnn,tot,sum,ans,hzh; 14 int minn[maxm],l[maxm],t[maxm]; 15 char a[maxs]; 16 main() 17 { 18 while(1) 19 { 20 scanf("%lld%lld",&n,&m); 21 if(!n&&!m) break; 22 sum=0; 23 for(int i=1;i<=m;++i) 24 { 25 scanf("%s%lld",a,&t[i]); 26 int len=strlen(a); tot=0; minnn=inf; int ls=0; 27 for(int k=len-1;k>=0;--k) 28 { 29 if(a[k]=='1') {tot++; ls++;} 30 else tot--; 31 minnn=min(minnn,tot); 32 } 33 sum+=ls*t[i]; l[i]=tot; 34 minn[i]=min(minnn,l[i]*(t[i]-1)+minnn); 35 } 36 if(sum<n) {printf("-1\n"); continue;} 37 ans=inf; hzh=0; 38 for(int i=m;i>=1;--i) 39 { 40 ans=min(ans,minn[i]+hzh); 41 hzh+=l[i]*t[i]; 42 } 43 printf("%lld\n",max(1ll*0,-1-ans)); 44 } 45 return 0; 46 }
T3
谁能想到暴力加上题目里给的程序就能水到50分,我反正没敢打那个。。。。
正解是DP,由于我们不可能枚举所有的状态,一种思路就是按位统计贡献,我们设$dp[i][j][k]$代表从大到小插入到了i,总积分为j,插入的这些数已经形成了k段的方案数,那么可以分为三种情况
1.插入这个数之后多了一段,那么后面再进的数一定都比这个数小,所以这个数的贡献是$2{\times}i$,对方案数的贡献是k+1,你原有的每个段左右都可以开一个新段,两个段之间的新段只能算一次,所以是k+1
2.插入这个数之后段数没变,那就是他有一边已经有一个数,这个数比他大,一边没有数,一会会再进一个比他小的,总贡献-i+i就是0,方案数的贡献就是$2{\times}k$
3.插入这个数之后段数少了一,也就是他左右两边都有数,那么对答案的贡献就是$-i{\times}2$,方案数是k-1
但很明显在我们上面的分类讨论中有一大类始终没有考虑到,就是如果这个数放在了左右端点上怎么办,显然在端点上对答案,对方案数的贡献都会改变,所以我们考虑在dp数组后面再加一维,分为0,1,2三种情况,表示现在已经考虑过的所有段里一共有几个端点,重新考虑上面三种情况
1.如果放在了端点上,对答案的贡献变为i
2.如果在端点上,对答案的贡献由0,变为-i
3.对答案的贡献由$-i{\times}2$变为-i
上述三种情况对方案数的贡献均为$2-$已有端点数,直接写转移方程转移就可以了
关于__float128卡精度那块,大佬们选择了用黑科技优化码量,DeepinC博客,我就直接复制粘贴了一遍,而且我跑的稍微有点慢,其实算是卡过去的,稍微不留神就会T
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 using namespace std; 5 int n,m,xs; 6 __float128 dpp[2][2100][55][3]; 7 void out1(int x,double ans) 8 { 9 if(x==0) {printf("%0.0lf\n",ans); return ;} 10 if(x==1) {printf("%0.1lf\n",ans); return ;} 11 if(x==2) {printf("%0.2lf\n",ans); return ;} 12 if(x==3) {printf("%0.3lf\n",ans); return ;} 13 if(x==4) {printf("%0.4lf\n",ans); return ;} 14 if(x==5) {printf("%0.5lf\n",ans); return ;} 15 if(x==6) {printf("%0.6lf\n",ans); return ;} 16 if(x==7) {printf("%0.7lf\n",ans); return ;} 17 if(x==8) {printf("%0.8lf\n",ans); return ;} 18 } 19 int floor(__float128 x) 20 { 21 22 for(int i=9;i>=0;--i) 23 if(x>=i) return i; 24 } 25 void out2(int x,__float128 ans) 26 { 27 int sta[55]; sta[0]=0; 28 for(int i=1;i<=x;++i) {ans*=10; sta[i]=floor(ans); ans-=floor(ans);} 29 ans*=10; 30 if(floor(ans)>=5) sta[x]++; 31 for(int i=x;i;--i) 32 if(sta[i]==10) {sta[i]=0; sta[i-1]++;} 33 printf("%d.",sta[0]); 34 for(int i=1;i<=x;++i) printf("%d",sta[i]); 35 puts(""); 36 } 37 void work1() 38 { 39 double ans=0; 40 double dp[2][7000][110][3]; dp[(n%2)^1][0][0][0]=1; 41 for(int i=n;i>=1;--i) 42 { 43 int dq=i%2; 44 memset(dp[dq],0,sizeof(dp[dq])); 45 for(int j=0;j<=6666;++j) 46 { 47 for(int k=0;k<=n;++k) 48 { 49 for(int l=0;l<=2;++l) 50 { 51 if(j-2*i>=0&&k-1>=0&&k-l>=0) 52 dp[dq][j][k][l]+=dp[dq^1][j-2*i][k-1][l]*(k-l); 53 if(j-i>=0&&k-1>=0&&l-1>=0) 54 dp[dq][j][k][l]+=dp[dq^1][j-i][k-1][l-1]*(3-l); 55 if(2*k-l>=0) dp[dq][j][k][l]+=dp[dq^1][j][k][l]*(2*k-l); 56 if(l-1>=0) dp[dq][j][k][l]+=dp[dq^1][j+i][k][l-1]*(3-l); 57 dp[dq][j][k][l]+=dp[dq^1][j+i*2][k+1][l]*k; 58 } 59 } 60 } 61 } 62 for(int j=m;j<=6666;++j) ans+=dp[1][j][1][2]; 63 for(int i=1;i<=n;++i) ans/=(double)i; 64 out1(xs,ans); 65 } 66 void work2() 67 { 68 __float128 ans=0; 69 dpp[(n%2)^1][0][0][0]=1; 70 for(int i=n;i>=1;--i) 71 { 72 int dq=i%2; 73 for(int j=0;j<=2000;++j) 74 for(int k=0;k<=i;++k) 75 for(int l=0;l<=2;++l) dpp[dq][j][k][l]=0; 76 for(int j=0;j<=2000;++j) 77 { 78 for(int k=0;k<=i;++k) 79 { 80 for(int l=0;l<=2;++l) 81 { 82 if(j-2*i>=0&&k-1>=0&&k-l>=0) 83 dpp[dq][j][k][l]+=dpp[dq^1][j-2*i][k-1][l]*(k-l); 84 if(j-i>=0&&k-1>=0&&l-1>=0) 85 dpp[dq][j][k][l]+=dpp[dq^1][j-i][k-1][l-1]*(3-l); 86 if(2*k-l>=0) dpp[dq][j][k][l]+=dpp[dq^1][j][k][l]*(2*k-l); 87 if(l-1>=0) dpp[dq][j][k][l]+=dpp[dq^1][j+i][k][l-1]*(3-l); 88 dpp[dq][j][k][l]+=dpp[dq^1][j+i*2][k+1][l]*k; 89 } 90 } 91 } 92 } 93 for(int j=m;j<=2000;++j) ans+=dpp[1][j][1][2]; 94 for(int i=1;i<=n;++i) ans/=(__float128)i; 95 out2(xs,ans); 96 } 97 int main() 98 { 99 scanf("%d%d%d",&n,&m,&xs); 100 if(xs<=8) work1(); 101 else work2(); 102 return 0; 103 }