2019牛客第三场(D/F)

17 篇文章 0 订阅
11 篇文章 0 订阅

D.Big Integer

题意:A\left ( n \right )=\frac{10^{n}-1}{9}  当1<=p,n,m<=1e9时 求有多少对\left ( i,j \right )满足 A(i^{j})mod\ p=0

做法:

考虑   

            \frac{10^{n}-1}{9}mod\ p\equiv 0

 则         (10^{n}-1) mod \ p\equiv 0

  则        10^{n}\equiv 1\left ( mod \ p \right )

Case1(p不为2,5,3)

然后由欧拉定理当p和10的时候有

            10^{p-1}\equiv 1\left ( mod\ p\right )

则我们知道在i为0和p-1时  10^{i}mod\ p\equiv 1  所以在0~p-1中间有没有可能有一个i=d 满足这个式子

由 同余方程性质可知 a\equiv b(mod\ p)\Rightarrow a^{n}\equiv b^{n}(mod\ p)

若存在这样的d 则满足 (10^{d})^{\frac{p-1}{d}}\equiv 1(mod\ p)  可知 d必然是(p-1) 的因子,我们只要枚举(p-1) 的所有因子找到最小的d 则是最小

的循环节 ,则可以成为答案的i^{j} 必然是 d 的倍数

接下来我们发现n,m都是1e9的范围那先枚举i再枚举j去找答案显然不可取 

这里有一个技巧1:

首先固定j

将d因式分解   d=a1^{p1}*a2^{p2}...*ak^{pk}     然后令     g=a1^{\left \lceil \frac{p1}{j} \right \rceil}*a2^{\left \lceil \frac{p2}{j} \right \rceil}...*ak^{\left \lceil \frac{pk}{j} \right \rceil}     ,   mx=\left \{p1,p2,p3...pk \right \}

当j增大时, g也随之变化,当g在1~mx 里变化时 求出对应的g 然后 ans+=(n/g)  更新答案;

当 j>mx 时 g的值固定 然后ans+=(m-mx)*(n/g)   

Case2(p=3)

显然我们只要让\frac{10^{n}-1}{9}是3的倍数 ,而 \frac{10^{n}-1}{9} 得出来的是长度为n的11111... 序列, 由数学知识可知,数位之和为3的倍数则该数是3的倍数,则只要i为3的倍数则n=i^{j}必然是3的倍数 所以 答案为 \frac{n}{3}*m

Case3(p=5或p=2)

易知\frac{10^{n}-1}{9}不可能为2或5的倍数所以ans=0;

技巧2:

枚举一个数的所有因子的时候从i=2到i*i<=n 进行枚举 若i为因子,则必然n/i 也是因子,可以在\sqrt{n}的复杂度里枚举完

#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(n^{3}) 的做法 

以每个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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值