[计蒜之道 复赛2017] 题解 (只有 A,E 两题)

来源:互联网 发布:网络平台开发预算 编辑:程序博客网 时间:2024/05/21 09:52

前言:菜鸡博主打了一波计蒜之道,A了B,D,F3个水题后并不能取得什么进展(沉迷A题卡常无法自拔)

            因为罚时爆炸,rk200+滚粗,%%%rk34的红太阳ZZT(听说ZZT如果不是漏掉了E的特判,就切5题rk20-了),

            听说C题是毛爷爷FFT,一脸懵逼.JPG,于是决定把A和E给补了。


A题:考虑进行数位dp,求L-R满足条件的数字个数,就是等价于求F(R)-F(L-1).

          F(x)的求法:

          这个做法还是很符合一般数位dp的套路的,

          将x表示为p进制后,从高位向低位考虑,

          当处理到第i位时,统计第i位之前所有位(就是更高位)都与x相同,

                                           而第i位取值小于x在第i位的取值  并符合题意的数字的个数,

          写成式子即为:F(x)+=dp[j][i-1],其中,j是满足与(i-1)位的数字互质并小于x在第i位取值的数,

          这样,dp数组预处理一下,最后再判断一下x本身是否符合题意就可以求出F(x)的值了。

          dp[i][j]表示后面还有i位没有填,最高位的数字是j的方案数,

          dp转移方程是:dp[i][j]=sigma(dp[i-1][k]),其中k是与j互质并且小于p的所有数字,边界情况是dp[0][j]=1.

          但是,如果直接进行转移,dp的预处理是O(p^2logpR)的,因为有50组数据都有10^3<p<=10^5,暴力求dp显然会TLE.

          考虑dp求解的优化,在求第i维dp数组的值的时候,这p个值都是只与第(i-1)维的值有关的,

          于是考虑通过莫比乌斯反演优化这样有关互质的dp过程,

          dp[i][j]=sigma((gcd(j,k)==1),dp[i-1][k]),

          则有dp[i][j]=sigma(d|j,miu[d]*sigma(d|k,dp[i-1][k])),

          通过预处理出前缀和,可以在O(nlog2plogpR)=O(nlog2R)时间内求出最终的答案,从而可以通过此题。

          正确性:如果k对j没有产生贡献,则显然j,k的最大公约数能写成几个质数之积,

                          利用容斥原理不难发现,假设j,k的最大公约数由r种质数组成,

                          则其对答案最终的贡献就是1-C(r,1)+C(r,2)-C(r,3)+....C(r,r).

                          可以发现这个数字最终等于0,

                          而k对j有贡献时,只有在d=1时计算了一次,贡献为1,也是正确的,

                          由此可以优化这样的dp求解过程。

Code:

#include <bits/stdc++.h>#define ll long longusing namespace std;ll dp[65][100005];ll s[100005];ll p;int prime[100001],cnt=0,miu[100001];bool taig[100001];void getmiu(){int i,j;miu[1]=1;for (i=2;i<=100000;i++){if (taig[i]==0){cnt++;prime[cnt]=i;miu[i]=-1;}for (j=1;j<=cnt&&i*prime[j]<=100000;j++){taig[i*prime[j]]=1;if (i%prime[j]!=0){miu[i*prime[j]]=miu[i]*(-1);}else{miu[i*prime[j]]=0;break;}}}return;}ll gcd(ll a,ll b){if (a==0||b==0) {return 0;}if (a%b==0) return b;return gcd(b,a%b);}inline void cal(int w){int i,j,k;for (i=1;i<p;i++){dp[0][i]=1;}for (k=1;k<=w;k++){for (i=1;i<p;i++){s[i]=0;dp[k][i]=0;{for (j=i;j<p;j+=i){s[i]+=dp[k-1][j];}}}for (i=1;i<p;i++){if (miu[i]!=0){s[i]*=miu[i];for (j=i;j<p;j+=i){dp[k][j]+=s[i];}}}}return;}ll ans=0;int tag=1;int getans(ll num,int w){int i,j;if (num<p){cal(w);for (i=1;i<num;i++){ans+=dp[w][i];}for (i=1;i<p;i++){for (j=0;j<w;j++){ans+=dp[j][i];}}return num;}int lar=getans(num/p,w+1);int m=num%p;if (tag){for (i=1;i<m;i++){if (gcd(i,lar)==1){ans+=dp[w][i];}}}if (gcd(m,lar)!=1) {tag=0;}return m;}int getans2(ll num,int w){int i,j;if (num<p){for (i=1;i<num;i++){ans+=dp[w][i];}for (i=1;i<p;i++){for (j=0;j<w;j++){ans+=dp[j][i];}}return num;}int lar=getans2(num/p,w+1);int m=num%p;if (tag){for (i=1;i<m;i++){if (gcd(i,lar)==1){ans+=dp[w][i];}}}if (gcd(m,lar)!=1) {tag=0;}return m;}int main (){getmiu();int test;ll l,r;int i;scanf ("%d",&test);while (test--){scanf ("%lld%lld%lld",&l,&r,&p);ans=0;tag=1;getans(r,0);if (tag) {ans++;}ll tp=ans;if (l>1){ans=0;tag=1;getans2(l-1,0);if (tag) {ans++;}}else{ans=0;}printf ("%lld\n",tp-ans);}return 0;}


E.先通过组合数的方法,得到本题的公式:

    令n=(x+y)/2,

     则ans=sigma(i=0...n,C(x-i,i)*C(x-2*i,n-i)).

     但是这样爆算的话,时间复杂度会爆炸,

     所以考虑利用模数很小(p=100003)的性质去解决,

     Lucas定理带入后,上面和式的每一项可以写成C((x-i)%p,i%p)*C((x-2*i)%p,(n-i)%p)*C((x-i)/p,i/p)*C((x-2*i)/p,(n-i)/p).

     将上面4个式子分别标号为A(i),B(i),C(i),D(i).

     A(i+p)=C((x-i-p)%p,(i+p)%p)=C((x-i)%p,i%p)=A(i)

     B(i+p)=C((x-2*i-2*p)%p,(n-i-p)%p)=C((x-2*i)%p,(n-i)%p)=B(i)

     这样第i,i+p,i+2p....项就可以进行合并了,而C(i)*D(i)的部分可以这样迭代下去继续算,

     预处理一下阶乘和阶乘的逆元就可以O(plogpX)解决此题。

Code:


                             

阅读全文
1 0
原创粉丝点击