[51nod]矩阵中不重复的元素

如果我们将每个数a分解质因式: a=i=1pkii ,那么任意一个数a都可以看作一个无穷维的向量 (k1,k2,...) ,其中第i维的系数表示从小到大第i个质数在a中的指数。这样的话 ab 就可以看作是向量的数乘,所以如果有 ab11=ab22 ,就必然有 a1,a2 共线。
所以我们可以统一处理所有共线的向量,这部分向量会有一个基向量a,即每一个向量都可以写成它的若干倍,即 gcd(k1,k2,...,)=1 。注意到0向量会是一个特例,不过还好,这道题的数据范围(a≥2)保证了0向量不在待求矩阵中;不过b≥2这个约束其实是没有意义,≥1也并无妨。。
所以就相当于是有一个乘法矩阵,第i行第j列是i*j,求 [l1,r1][l2,r2] 这个矩形中的不重复元素数量。 [l1,r1] 是a的指数范围,所以是 O(logan) 的。

V1
2n,m,a,b100
既然数据范围这么小,根据上面的分析,就可以直接把矩形中所有元素拎出来,排序/set去重 O(nmlog2n) ,或者直接用一个布尔数组判重 O(nm)
或者也可以不分析,直接按照题意取log/分解质因子/hash判重。
V2
2n,m,a,b5105
注意到随着a的增大, l1,r1 不升。
所以我们可以从小到大处理每个a,就相当于是需要支持插入、删除一个范围在 O(nlogn) 的元素,求当前有多少个不同的数。只需要记录下每个元素的出现次数,当一个元素的数量由0->1,就把当前的答案+1;当它由1->0,就把当前的答案-1。然后当 l1,r1 改变,只需要扫一下变化的这一行就可以了,时间复杂度 O(nlogn)

或者我们可以这样,每当 l1,r1 改变时,都用一个bool数组暴力统计一下答案,这样时间复杂度看起来似乎是 O(nlog2n) 的;但是其实 l1,r1 会很快地衰减,所以也不会花费很大的时间。

当然我们也可以容斥(为什么我一看见不重复就想容斥。。。),假如说在 [l1,r1] 中有序地选了 a1,a2,...,ak k个数,那么它们会贡献 (1)k1(l2lcm(a1,a2,...,ak)ak,r2lcm(a1,a2,...,ak)a1) ,所以直接按这个式子dfs,时间复杂度 O(ni=22login)3106
V3
2n,m51015,2a,b1015
注意到实际上时间复杂度远远不会及此,因为我们可以对它进行一些行之有效的剪枝。
当底数大于等于 max(n+a1+1,l) 时,必然会贡献m,而这部分是可以通过从中减去底数小于 max(n+a1+1,l) 而计算出来的,所以我们只需要枚举到 O(n) 级别。
一个很显然的剪枝是如果 l2lcm(a1,a2,...,ak)ak>r2lcm(a1,a2,...,ak)a1 ,那么答案就必然会是0.
另一个关键的剪枝是如果在还没有dfs到的部分存在一个数是当前的lcm的因子,那么会意味着它不会对lcm造成影响,选它和不选它会对应着互为相反数的答案,所以此时答案必然为0,便可以剪枝。
注意到答案的计算只与最小的数、最大的数和lcm有关,所以我们可以先枚举最小的数和最大的数,然后记下搜索的结果,这样在再次枚举的时候就不需要重复搜索了。
如果当前的 l1,r1 没变的话,我们也没必要再次搜索,直接用上次的答案即可。
如果当前的 l1,r1 已经等于 n+a1 l1,r1 的话,再进行一些冗余的判断就没有用处了,反而会带来很大的常数,可以直接迅速地处理完这一部分 (O(n13n12)) 。(这个剪枝让我快了1s)
在xjb剪了很久之后,终于a掉了!

ac后看到别人的代码,惊讶的发现大家似乎都写的爆搜?那他们怎么跑的那么快!
然后就又学习了一些别人代码的姿势:
算lcm的时候因为是一个很大的数与一个很小的数求gcd,所以其实是可以预处理gcd以做到 O(1) 的。(加上这个剪枝让我又快了1s)
容斥可以不记符号,而是把减法写在中间的计算里。
中间没有改变的状态是没有必要dfs下去的,这样只会使搜索树更大,让一个合法的状态消耗很多剪枝的判断。

代码:

#include<stdio.h>
#include<iostream>
using namespace std;
#include<algorithm>
#include<cmath>
#include<ctime>
#include<cstring>
#include<bitset>
typedef long long LL;
const int N=8e7+5;
bool vst[N];
int l1,r1;
LL l2,r2;
const int Mod=1e9+7;
const int Log=50+5;
int gcd[Log][Log];
int dfs(int x,LL lcm,LL l,LL r,int first,int end)
{
    if(l>r)return 0;
    for(int i=x;i<end;++i)
        if(lcm%i==0)
            return 0;

    int ans=(r-l+1)%Mod;
    LL nextlcm;
    for(int i=x;i<end;++i)
    {
        nextlcm=i/gcd[i][lcm%i]*lcm;
        (ans-=dfs(i+1,nextlcm,(l2+nextlcm/end-1)/(nextlcm/end),r2/(nextlcm/first),first,end))%=Mod;
    }
    return ans;
}
int f[Log][Log];
inline int query(int l,int r)
{
    if(!~f[l][r])
        if(l==r)f[l][r]=(r2-l2+1)%Mod;
        else
        {
            printf("f[%d,%d]=",l,r);

            int lcm=l*r/gcd[r][l];
            f[l][r]=(Mod-dfs(l+1,lcm,(l2+lcm/r-1)/(lcm/r),r2/(lcm/l),l,r))%Mod;

            printf("=%d\n",f[l][r]);
        }
    return f[l][r];
}
int main()
{
    freopen("51nod1026.in","r",stdin);
    LL l,r;
    cin>>r2>>r>>l>>l2;
    r+=l-1,r2+=l2-1;

    //printf("2:[%lld,%lld]\n",l2,r2);
    int sqroot=sqrt(r);
    LL cnt1=r-max((LL)sqroot,l-1);

    int now=0,ans=0;

    memset(f,-1,sizeof(f));

    for(int i=Log;i--;)gcd[i][0]=i;
    for(int i=1;i<Log;++i)
        for(int j=i;j;--j)
            gcd[i][j]=gcd[j][i%j];

    for(int i=2,prel,prer;i<=sqroot;++i)
        if(!vst[i])
        {
            prel=l1,prer=r1;

            l1=0;
            {
                LL j=i;
                int k=1;
                for(;j<=r/i;j*=i,++k)
                {
                    if(j<=sqroot)vst[j]=1;
                    else cnt1-=j>=l;
                    if(j>=l&&!l1)l1=k;
                }
                if(j>=l)
                {
                    --cnt1;
                    if(!l1)l1=k;
                }
                r1=k;
            }
            if(l1)
            {
                if(l1!=prel||r1!=prer)
                {
                    //printf("----%d:[%d,%d]----\n",i,l1,r1);

                    now=0;
                    for(int k=l1;k<=r1;++k)
                        for(int j=k;j<=r1;++j)
                            (now+=query(k,j))%=Mod;

                    //printf("%lld\n",now);
                    //printf("%.5f\n",(double)clock()/CLOCKS_PER_SEC);

                    if(sqroot>=l&&l1==1&&r1==2)
                    {

                        (ans+=now)%=Mod;
                        for(;i<=sqroot;++i)
                            if(!vst[i])
                                --cnt1,(ans+=now)%=Mod;
                        break;
                    }
                }
            }
            else now=0;
            (ans+=now)%=Mod;
        }
    //printf("%.5f\n",(double)clock()/CLOCKS_PER_SEC);

    cout<<((ans+cnt1%Mod*((r2-l2+1)%Mod))%Mod+Mod)%Mod<<endl;
}

总结:
①判重:可以排序 O(logn) ,但是其实如果元素值域允许的话,可以直接用bool判重。
②可以通过取log将指数上的数放下来,以减小数的大小。
③容斥时可以不记符号,把减法写在中间的计算里!
④没有改变的状态是没有必要dfs下去的,这样虽然会更好写,但它也会让搜索树多一个叉。

猜你喜欢

转载自blog.csdn.net/ta201314/article/details/53070808
今日推荐