D.Big Integer
题意:
当1<=p,n,m<=1e9时 求有多少对
满足 ![A(i^{j})mod\ p=0](https://i-blog.csdnimg.cn/blog_migrate/c10390c3bab5cddb649c2e6af2a42daa.gif)
做法:
考虑
则
则
Case1(p不为2,5,3)
然后由欧拉定理当p和10的时候有
则我们知道在i为0和p-1时 所以在0~p-1中间有没有可能有一个i=d 满足这个式子
由 同余方程性质可知
若存在这样的d 则满足 可知 d必然是(p-1) 的因子,我们只要枚举(p-1) 的所有因子找到最小的d 则是最小
的循环节 ,则可以成为答案的 必然是 d 的倍数
接下来我们发现n,m都是1e9的范围那先枚举i再枚举j去找答案显然不可取
这里有一个技巧1:
首先固定j
将d因式分解 然后令
,
当j增大时, g也随之变化,当g在1~mx 里变化时 求出对应的g 然后 ans+=(n/g) 更新答案;
当 j>mx 时 g的值固定 然后ans+=(m-mx)*(n/g)
Case2(p=3)
显然我们只要让是3的倍数 ,而
得出来的是长度为n的11111... 序列, 由数学知识可知,数位之和为3的倍数则该数是3的倍数,则只要i为3的倍数则
必然是3的倍数 所以 答案为
Case3(p=5或p=2)
易知不可能为2或5的倍数所以ans=0;
技巧2:
枚举一个数的所有因子的时候从i=2到i*i<=n 进行枚举 若i为因子,则必然n/i 也是因子,可以在的复杂度里枚举完
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int t;
map<ll,ll>sum;
ll p,n,m;
ll ans=0;
ll prime[500006];
ll Pow(ll a, ll x)
{
ll res=1;
while(x)
{
if(x&1)res=res*a%p;
a=a*a%p;
x>>=1;
}
return res;
}
ll pow(ll a, ll x)
{
ll res=1;
while(x)
{
if(x&1)res=res*a;
a=a*a;
x>>=1;
}
return res;
}
void solve()
{
ll d;
ll mi=p-1;
for(d=1;d*d<=(p-1);d++)
{
if( (p-1)%d==0 )
{
if(Pow(10,d)%p==1)
{
mi=min(mi,d);
}
if(Pow(10,(p-1)/d)%p==1)
{
mi=min(mi,(p-1)/d);
}
}
}
int cnt=0;
memset(prime,0,sizeof(prime));
sum.clear();
for(ll i=2;i*i<=mi;i++)
{
if(mi%i==0)
{
prime[++cnt]=i;
while(mi%i==0)
{
sum[i]++;
mi/=i;
}
}
}
if(mi>1)
{
prime[++cnt]=mi;
sum[mi]++;
}
ll g=1;
ll mx=-1;
for(int i=1;i<=cnt;i++)
{
mx=max(mx,sum[prime[i]]);
}
for(int j=1;j<=((m>mx)?mx:m );j++)
{
g=1;
for(int i=1;i<=cnt;i++)
{
if(sum[prime[i]]%j)
{
g*=pow(prime[i],sum[prime[i]]/j+1);
}
else g*=pow(prime[i],sum[prime[i]]/j);
}
ans+=n/g;
}
if(m>mx)ans+=(n/g)*(m-mx);
}
int main()
{
cin>>t;
while(t--)
{
ans=0;
cin>>p>>n>>m;
if(p==2||p==5)
{
cout<<0<<endl;
}
else if(p==3)
{
cout<<n/3*m<<endl;
}
else
{
solve();
cout<<ans<<endl;
}
}
return 0;
}
F.Planting Trees
题意:给你一个N*N的矩阵,找一个最大的矩阵满足里面的最大的值-最小的值<=m
满足O(
) 的做法
以每个i为行的起点, j为枚举行,l初始化为1,r 为枚举的右边界, 每次取a[j][r] 更新mi[r] 和 mx[r] 相当于 在一个一维的数组上维护一个最小值的单调队列,和一个维护最大值的单调队列,每次在单调队列队尾 加入mi[r] 和 mx[r] , 然后判断最大值的单调队列的队头减去最小值的单调队列的队头 是否大于M 否则就让左边界l右移,然后如果两个单调队列队头下标小于l 则让 队列下标 l1,l2 右移,删除不可能的元素。如果满足条件则用更新 (l-r+1)*(j-i+1) 更新ans
#include<bits/stdc++.h>
using namespace std;
int T,n,m;
int a[505][505];
int l1,l2,r1,r2,l;
int q1[505],q2[505];
int mi[505],mx[505];
int ans=-1;
int main()
{
cin>>T;
while(T--)
{
ans=-1;
cin>>n>>m;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
for(int i=1;i<=n;i++)
{
//memset(q1,0,sizeof(q1));
//memset(q2,0,sizeof(q2));
memset(mx,0,sizeof(mx));
memset(mi,0x3f,sizeof(mi));
for(int j=i;j<=n;j++)
{
l1=l2=1;
r1=r2=0;
l=1; // l为实际左边界,r为实际的右边界
for(int r=1;r<=n;r++)
{
mi[r]=min(mi[r],a[j][r]);
mx[r]=max(mx[r],a[j][r]);
while(l1<=r1&&mi[r]<=mi[q1[r1]])r1--;
while(l2<=r2&&mx[r]>=mx[q2[r2]])r2--;
q1[++r1]=r;
q2[++r2]=r;
while(l<=r&&mx[q2[l2]]-mi[q1[l1]]>m) // l直到缩小到 在l到r的范围内 最大值-最小值<=m
{
l++;
if(q1[l1]<l)l1++;
if(q2[l2]<l)l2++;
}
ans=max(ans,(r-l+1)*(j-i+1) );
}
}
}
cout<<ans<<endl;
}
return 0;
}