您的位置:首页 > 其它

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

2017-06-12 21:57 330 查看
前言:菜鸡博主打了一波计蒜之道,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的最大公约数能写成几个质数之积,

                         
4000
利用容斥原理不难发现,假设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 long
using 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:

                             
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: