3.4考试题解
两道多项式对于一个学习中并且没学明白选手是巨大的考验……发了题就看到滑稽的“雀巢咖啡模拟赛”?再往下翻翻一片绿了吧唧什么玩意,题目中有无关图片总是让人难受的。T1还算不错看出来了生成函数,但是只能到幂之和那个地步,之前只利用过数的无限级数求和不知道它还可以用于多项式。愚蠢地以为这大概可以快速幂,幻想能通过给$a0+1$来完成答案统计。模数不是平时常见的那两个,手写了一个暴力求原根发现跑不出来,只能乖乖利用原根的性质去求,得到了$5$。发现这个模数$=1917×2^n+1$,记得NTT模数要满足$-1$为$2^n$的倍数,但是忽然很怀疑这不是每个质数都满足?(考后问晖晖晖得知要求$2^n$大到一定级别)恰好这个$1917$里面有因子$27$,然后拆了$998244353$发现没有除$2$的幂以外的平方因子,于是以为这个模数不符合NTT要求需要任意模数NTT。这个模板没有写过,最后直接用$n^2$模拟多项式乘法再$log$快速幂,思路被限制在多项式上完全没有想起来还可以DP。后两题都是调第一题的间隙写的。T2发现答案$=Flag$中$1$的个数$+Data$与$Test$不匹配数,但是不知道不匹配数还可以用NTT来算,所以只拿到了$40$的部分分。T3开头论述了一些数论函数相关,以为题目中所说的函数也是数论函数,没有把$0$纳入定义域;同时我没有读出来这个计数的条件是恒成立而不是存在,一直没有调过样例。主要问题是多项式理论和实践都学得不够扎实,对多项式题目还是很没有入手点。
T1:设$c_k$为集合中$k$元素的个数,$a_k$为把$k$用集合中元素拆分的方案数。构造多项式$C=\sum{c_k*x^k}$,$G=\sum{a_k*x^k}$,则有$G=1+C+C^2+C^3+……=1/(1-C)$,所以对$1-C$多项式求逆即可。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 const int mod=1005060097; 7 const int sj=1000010; 8 ll pow(ll x,ll y) 9 { 10 ll ret=1; 11 while(y) 12 { 13 if(y&1) ret=ret*x%mod; 14 x=x*x%mod,y>>=1; 15 } 16 return ret; 17 } 18 inline int read() 19 { 20 int jg=0,jk=getchar()-'0'; 21 while(jk<0||jk>9) jk=getchar()-'0'; 22 while(jk>=0&&jk<=9) jg*=10,jg+=jk,jk=getchar()-'0'; 23 return jg; 24 } 25 int n,m,id[sj],n1,a1; 26 ll a[sj],b[sj],w[sj],inv,tmp[sj]; 27 void init(int x) 28 { 29 for(n1=1;n1<(x<<1);n1<<=1); 30 inv=pow(n1,mod-2); 31 for(int i=1;i<n1;i++) 32 { 33 if(i&1) id[i]=(id[i>>1]>>1)|(n1>>1); 34 else id[i]=id[i>>1]>>1; 35 } 36 w[0]=1,w[1]=pow(5,(mod-1)/n1); 37 for(int i=2;i<=n1;i++) w[i]=w[i-1]*w[1]%mod; 38 } 39 void ntt(ll *x,int o) 40 { 41 for(int i=0;i<n1;i++) 42 if(i<id[i]) 43 swap(x[i],x[id[i]]); 44 for(int k=2;k<=n1;k<<=1) 45 { 46 int t=k>>1,tt=n1/k; 47 for(int i=0;i<t;i++) 48 { 49 ll wn=(o==1)?w[i*tt]:w[n1-i*tt]; 50 for(int j=i;j<n1;j+=k) 51 { 52 ll tx=x[j],ty=x[j+t]*wn%mod; 53 x[j]=(tx+ty)%mod,x[j+t]=(tx-ty+mod)%mod; 54 } 55 } 56 } 57 if(o==-1) for(int i=0;i<n1;i++) x[i]=x[i]*inv%mod; 58 } 59 void get_inv(int dep,ll *x,ll *y) 60 { 61 if(dep==1){ y[0]=pow(x[0],mod-2);return; } 62 get_inv((dep+1)>>1,x,y); 63 init(dep); 64 for(int i=0;i<dep;i++) tmp[i]=x[i]; 65 for(int i=dep;i<n1;i++) tmp[i]=0; 66 ntt(tmp,1),ntt(y,1); 67 for(int i=0;i<n1;i++) 68 y[i]=y[i]*((2-y[i]*tmp[i]%mod+mod)%mod)%mod; 69 ntt(y,-1); 70 for(int i=dep;i<n1;i++) y[i]=0; 71 } 72 int main() 73 { 74 n=read(),m=read(); 75 for(int i=1;i<=m;i++) 76 { 77 a1=read(); 78 a[a1]--; 79 if(a[a1]<0) a[a1]+=mod; 80 } 81 a[0]=1; 82 get_inv(n+1,a,b); 83 printf("%lld",b[n]); 84 return 0; 85 }
T2:把题目中的式子分析一下就能发现这实际上是$x2$和$c$决定了平移多少位去匹配,答案$=Flag$中$1$的个数$+Data$与$Test$不匹配数。将其中一个串反转,另一个串复制到两倍长,我们可以把原来的按位匹配转换成卷积的形式。把$0$改为$-1$,如果两位匹配会贡献$1$,不匹配会贡献$-1$。匹配数$+$不匹配数$=n$,匹配数$-$不匹配数$=$卷积结果,可以分别算出匹配数和不匹配数。异或$x1$可以相当于交换匹配数和不匹配数,我们枚举$c$并按$x1$、$x2$的四种情况分别更新答案即可。改题的时候在内网上WA成Dog,最后发现$pow$不能作函数名……印象深刻的教训。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #define ll long long 7 using namespace std; 8 const int sj=2000010; 9 const int mod=1005060097; 10 int m,n,n1,id[sj],num; 11 ll a[sj],b[sj],inv,w[sj],tp,ans,a1,a2; 12 char s[sj]; 13 ll pow(ll x,ll y) 14 { 15 ll ret=1; 16 while(y) 17 { 18 if(y&1) ret=ret*x%mod; 19 x=x*x%mod,y>>=1; 20 } 21 return ret; 22 } 23 inline ll minn(ll x,ll y) 24 { 25 return x<y?x:y; 26 } 27 void init(int x) 28 { 29 for(n1=1;n1<(x<<1);n1<<=1); 30 inv=pow(n1,mod-2); 31 for(int i=1;i<n1;i++) 32 { 33 if(i&1) id[i]=(id[i>>1]>>1)|(n1>>1); 34 else id[i]=id[i>>1]>>1; 35 } 36 w[0]=1,w[1]=pow(5,(mod-1)/n1); 37 for(int i=2;i<=n1;i++) w[i]=w[i-1]*w[1]%mod; 38 } 39 void ntt(ll *x,int o) 40 { 41 for(int i=0;i<n1;i++) 42 if(i<id[i]) 43 swap(x[i],x[id[i]]); 44 for(int k=2;k<=n1;k<<=1) 45 { 46 int t=k>>1,tt=n1/k; 47 for(int i=0;i<t;i++) 48 { 49 ll wn=(o==1)?w[i*tt]:w[n1-i*tt]; 50 for(int j=i;j<n1;j+=k) 51 { 52 ll tx=x[j],ty=x[j+t]*wn%mod; 53 x[j]=(tx+ty)%mod,x[j+t]=(tx-ty+mod)%mod; 54 } 55 } 56 } 57 if(o==-1) for(int i=0;i<n1;i++) x[i]=x[i]*inv%mod; 58 } 59 int main() 60 { 61 scanf("%d%d",&m,&n); 62 scanf("%s",s); 63 for(int i=0;i<n;i++) 64 { 65 a[i]=s[i]-'0'; 66 if(!a[i]) a[i]=mod-1; 67 } 68 reverse(a,a+n); 69 scanf("%s",s); 70 for(int i=0;i<n;i++) 71 { 72 b[i]=b[i+n]=s[i]-'0'; 73 if(!b[i]) b[i]=b[i+n]=mod-1; 74 } 75 init(n*2); 76 ntt(a,1),ntt(b,1); 77 for(int i=0;i<n1;i++) b[i]=b[i]*a[i]%mod; 78 ntt(b,-1); 79 ans=0x7fffffff; 80 for(int i=0;i<(1<<m);i++) 81 { 82 num=0; 83 for(int j=0;j<m;j++) 84 if((i>>j)&1) 85 num++; 86 tp=b[i%n+n-1]; 87 a1=(n+tp)%mod/2,a2=(n-tp+mod)%mod/2; 88 ans=minn(ans,num+a2); 89 ans=minn(ans,num+a1+1); 90 tp=b[((-i)%n+n)%n+n-1]; 91 a1=(n+tp)%mod/2,a2=(n-tp+mod)%mod/2; 92 ans=minn(ans,num+a2+1); 93 ans=minn(ans,num+a1+2); 94 } 95 printf("%lld",ans); 96 return 0; 97 }
T3:题目要求$a(ax+b)+b=ax+b$在模$n$意义下恒成立,可以整理出$a(a-1)x+ab=0$,所以$a(a-1)$和$ab$在模$n$意义下必须都为$0$。枚举$i、a、b$可以有$20$分的$n^3$做法;如果注意到$ab$模$n$意义下为$0$满足条件的$b=k×gcd(a,n)$,$b$的个数为$n/(n/gcd(a,n))=gcd(a,n)$,这个暴力可以做到$n^2$。
上式中的条件可以转化为$n|a(a-1)$且$n|ab$,显然$a$与$a-1$是互质的,则它们分别包含n的一些质因子。设$a=k_1p$,$a-1=k_2q$,可知$pq=n$且$k_1p=k_2q+1$,所以$k_1p$在模$q$意义下与$1$同余。此时存在一个神奇的定理叫若两个数互质则一个数的若干倍遍历另一个数的剩余系,所以方程有唯一解。对于每一个小于$n$的$p$,若$gcd(p,n/p)=1$则$p$对答案贡献$gcd(p,n)=p$。$R(n)=\sum_{d|n,d<n,gcd(d,n/d)=1}d=\prod{(pi^{qi}+1)}-n$,后者利用了取括号原理。这样$R(n)+n$是一个积性函数,可以线性筛并且$O(n)$得到解,能拿到$50$分。
现在继续化式子。$$ans=\sum^n_{i=1}\sum_{d|i}[gcd(d,i/d)==1]d$$$$=\sum^n_{i=1}\sum_{d|i}d\sum_{D|d,D|(i/d)}\mu(D)$$$$=\sum^n_{d=1}d\sum^{\lfloor\frac{n}{d}\rfloor}_{t=1}\sum_{D|d,D|t}\mu(D)$$设$d=iD,t=jD$,则上式$$=\sum_{ijD^2<=n}\mu(D)*iD$$$$=\sum^{\sqrt{n}}_{D=1}\mu(D)*D\sum^{\lfloor\frac{n}{D^2}\rfloor}_{i=1}i\lfloor\frac{n}{iD^2}\rfloor$$发现这个$D$只有$\sqrt{n}$级别,暴力枚举$D$并对后面的部分用除法分块计算即可。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #define ll long long 6 using namespace std; 7 const int mod=1005060097; 8 const int sj=1000010; 9 int p[sj],cnt,miu[sj]; 10 ll n,ans,nt,pos,tp,sum,inv,a1,a2,sq; 11 bool ip[sj]; 12 void getprime() 13 { 14 miu[1]=1; 15 for(int i=2;i<=sq;i++) 16 { 17 if(!ip[i]) p[++cnt]=i,miu[i]=-1; 18 for(int j=1;j<=cnt&&i*p[j]<=sq;j++) 19 { 20 ip[i*p[j]]=1; 21 if(i%p[j]==0) break; 22 miu[i*p[j]]=-miu[i]; 23 } 24 } 25 } 26 int main() 27 { 28 scanf("%lld",&n); 29 sq=sqrt(n); 30 getprime(); 31 inv=mod+1>>1; 32 for(ll k=1;k<=sq;k++) 33 { 34 if(!miu[k]) continue; 35 nt=miu[k]*k,tp=n/(k*k); 36 sum=0; 37 for(ll i=1;i<=tp;i=pos+1) 38 { 39 pos=tp/(tp/i); 40 a1=tp/i,a2=(pos+i)%mod*((pos-i+1)%mod)%mod*inv%mod; 41 sum=(sum+a1*a2%mod)%mod; 42 } 43 ans=(ans+sum*nt%mod)%mod; 44 ans=(ans+mod)%mod; 45 } 46 a1=(n+1)%mod*(n%mod)%mod*inv%mod; 47 ans=(ans-a1+mod)%mod; 48 printf("%lld",ans); 49 return 0; 50 }