[bzoj3202]项链 解题报告

这题wa了无数遍。。

先说做法,显然最终答案只与n和不同珠子数量有关系。
所以我们先求不同珠子数量。
如果两个珠子排完序后相同,那么这两个珠子就相同。
考虑珠子里最大的数是i,第二数与i的gcd是d,那么第三个数就必须与d互质。所以答案就是

i=1ad|iϕ(id)ϕ(d)id
后面一坨显然是个积性函数,所以我们就可以直接筛出来了。但是考虑一下还是有点麻烦,所以简单起见我们把它化成这样:
i=1aϕ(i)j=1aiϕ(j)j
,这样我们就只需要筛 ϕ ,然后分块求就行了。
但是考虑一下上式,最大数+两个相同的与它互质的数这种情况会被考虑一遍,其余的都会被考虑两遍,所以再加上这种情况一起除以2就是答案了,即: ai=1ϕ(i)aij=1ϕ(j)j+ai=1ϕ(i)2

x=ai=1ϕ(i)aij=1ϕ(j)j+ai=1ϕ(i)2
我们考虑求项链的数量,先考虑如果项链不考虑旋转同构的方案数,这可以dp。设 Fi 表示长度为i的项链不考虑旋转同构的方案数,则 Fi=(x2)Fi1+(x1)Fi2 (考虑倒数第二位与正数第一位是否相同), F1=0,F2=x(x1) .这个就是一个比较常见的式子了,通常的做法是矩阵乘或者求通项。
矩乘的话就是乘

(x21x10)
,时间复杂度 O(23logP+2logP103 (设 P=109+7 ),考虑这题的数据范围还是有点虚的。
所以我们干脆把通项求出来,这种形如 Fi=aFi1+bFi2(ab0) 的通项大致有3种求法。
(差比列)
Fn+xFn1
=(a+x)(Fn1+xFn2)
=(a+x)Fn1+(a+x)xFn2
=(a+x)Fn1+bFn2

(a+x)x=b
,即
x2+axb=0
显然,若设方程解为 x1,x2 ,则 x1x20 , a=(x1+x2) ,将 x=x1 代入递推式,则有
Fn+x1Fn1
=(a+x1)(Fn1+x1Fn2)
=(x2)(Fn1+x1Fn2),
Fn+x1Fn1=(x2)n1(F1+x1F0)
,同除 (x2)n ,得
Fn(x2)n=x1x2Fn1(x2)n11x2(F1+x1F0)
,令 Gn=Fn(x2)n ,则 Gn=x1x2Gn11x2(F1+x1F0) ,
x1x21 ,即 x1x2 ,设
Gn+y
=x1x2(Gn1+y)
=x1x2Gn1+x1x2y
=x1x2Gn11x2(F1+x1F0)+y,
x1x2y=1x2(F1+x1F0)+y
y=F1+x1F0x2x1,
Gn+F1+x1F0x2x1=(x1x2)n(G0+F1+x1F0x2x1)
Gn=(x1x2)n(G0+F1+x1F0x2x1)F1+x1F0x2x1,
Fn=(F0+F1+x1F0x2x1)(x1)n+F1+x1F0x2x1(x2)n
=F1+x2F0x2x1(x1)n+F1+x1F0x2x1(x2)n
x1=x2 ,则 Gn=Gn11x2(F1+x1F0),
Gn=F1+x1F0x2n+G0,
Fn=(F1+x1F0x2n+G0)(x2)n

(母函数)
我并不是很会。。貌似需要很高端的数学知识。
(特征根法)
其实是上述做法的简化,考虑方程 x2=ax+b 的两根 x1,x2 ,是方程 x2+axb=0 的两根的相反数。注意到当 x1=x2 时,通项公式为 Fn=Axn1+Bxn2 ,当 x1x2 时,通项公式为 Fn=(An+B)xn1 .所以我们只需代入 F0,F1 解出A,B即可。

所以考虑题目 Fn=(x2)Fn1+(x1)Fn2 ,首项 F1=0,F2=x(x1) (注意 F0 并非首项),由以上推导易得 Fn=(x1)((x1)n1+(1)n) ,注意到实际上,在 x11 modp 的时候,这个式子依然是成立的。这样我们就能在 O(logn) 的时间内求出一个 Fn 了。

我们首先考虑一下关于循环节的问题。首先长度为n的项链的循环节长度必为n的约数。且若其有长为p的循环节和长为q的循环节,则其必然有长为(p,q)的循环节,因为若设p>q,该项链中第i个珠子为 xi ,则有 xi=xi+pq ,所以p-1也是该项链的循环节,所以(p,q)也是该项链的循环节。
那么显然,一条最短循环节为d的项链在不考虑循环的计算中会被计算d次,所以设最短循环节为d的项链有f(d)条,则 ans=d|n1df(d) ,而考虑我们刚才求的F(n),显然有 F(n)=d|nf(d) , f(n)=d|nF(d)μ(nd) , ans=d|n1di|dF(i)μ(di)=d|nF(d)1di|ndμi1i=d|nF(d)ϕ(nd)n

那么我们就可以在 O(sqrt(n)logn)5108 的时间复杂度内回答单次询问了,注意到实际上这个 O(sqrt(n)) 是很虚的,实际上应该只有 105 106 左右,所以实际上只会有 107 左右。

但是这道题就这么解决了么?不!注意到n非常非常大,有可能是p的倍数,所以如果我们要除n的话,就可能会面临没有逆元的问题。
那么这时候该怎么办呢?实际上有式子: ab mod apa=b mod p ,这个还是比较显然的。但是这个n非常大啊,我要是直接乘n的话不就爆了么?但是注意到我们其实可以把除n分解成除p再除 np ,注意到n中只会有一个p,而因为 np 是有逆元的,所以我们可以对这两部分分别处理。
但是这样的话就需要在快速幂里再加一个快速乘,时间复杂度不就又爆了么?但是实际上,考虑到我们需要快速乘的时候是 p|n 之时,这是n的约数只会有 O(2np)102 是非常少的。所以时间复杂度并不会增加,只是预处理更慢了而已。。

所以总的时间复杂度就是 O(alogp+T(nlogp+nplog2p))108 .
然而似乎是预处理的时候太慢了跑了22s。。因为预处理的时候需要long long+快速乘,慢的飞起。。而如果把 ϕ 换成 μ 的话就会快很多(只跑了4s)。。这也是这题一个奇怪的地方,因为一般情况下用 ϕ 都是会比用 μ 更优的。

然后膜拜xyz大爷只跑了600ms。。应该是有什么更神的做法吧。。(难道是用杜教筛筛了 μ …如果用杜教筛筛 μ +数据较水,时间复杂度就成了 O(T(a+logp+lognlogp+lognplog2p))7105
代码( ϕ ):

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long LL;
const LL Mod[2]={(LL)(1e9+7)*(int)(1e9+7),1e9+7},phi[2]={(LL)(1e9+7)*(int)(1e9+6),1e9+6};
LL mul(LL a,LL b,bool o){
    if(a<=2e9&&b<=2e9)return a*b%Mod[o];
    else{
        LL ans=0,sum=a%Mod[o];
        for(b=(b+Mod[o])%Mod[o];b;b>>=1,sum=(sum<<1)%Mod[o])
            if(b&1)
                ans=(ans+sum)%Mod[o];
        return ans;
    }
}
LL pow(LL a,LL x,bool o){
    LL ans=1,prod=a%Mod[o];
    //cout<<"Pow("<<a<<"^"<<x<<")=";
    for(x%=Mod[o]-1;x;x>>=1,prod=mul(prod,prod,o))
        if(x&1)
            ans=mul(ans,prod,o);
    //cout<<ans<<" "<<prod<<endl;
    return ans;
}
const int A=1e7+5;
LL sphi[2][A],sphii[2][A];
int prime[1000005];
LL dvs[50];
int ind[50];
LL x;
LL ans;
void dfs(int i,LL f,LL phi,bool o){
    if(i<0){
        //cout<<"dfs("<<i<<","<<f<<","<<phi<<","<<o<<")\n";
        ans=(ans+mul(mul(x-1,pow(x-1,f-1,o)+(f&1?-1:1),o),phi,o))%Mod[o];
        return;
    }
    dfs(i-1,f,phi,o);
    dfs(i-1,f/=dvs[i],phi*=(dvs[i]-1),o);
    for(int j=ind[i];--j;)dfs(i-1,f/=dvs[i],phi*=dvs[i],o);
}
int main(){
    for(int i=2;i<=10000000;++i){
        if(!sphi[0][i]){
            sphi[0][i]=i-1;
            prime[++prime[0]]=i;
        }
        for(int j=1;j<=prime[0]&&i*prime[j]<=10000000;++j)
            if(i%prime[j])sphi[0][i*prime[j]]=sphi[0][i]*(prime[j]-1);
            else sphi[0][i*prime[j]]=sphi[0][i]*prime[j];
    }
    sphi[0][1]=1;
    for(int i=1;i<=10000000;++i)
        for(int o=2;o--;){
            sphii[o][i]=(sphii[o][i-1]+sphi[0][i]*i)%Mod[o];
            sphi[o][i]=(sphi[o][i-1]+sphi[0][i])%Mod[o];
        }
    //cout<<sphi[0][10000000]<<" "<<sphii[0][10000000]<<endl;

    int t;
    cin>>t;
    while(t--){
        LL n;
        int a;
        cin>>n>>a;

        bool o=n%Mod[1];
        x=0;
        for(int i=1,j;i<=a;i=j+1){
            j=a/(a/i);
            //x=(x+mul(sphii[o][a/i],sphi[o][j]-sphi[o][i-1],o))%Mod[o];
            x=(x+sphii[o][a/i]*(sphi[o][j]-sphi[o][i-1]))%Mod[o];
            //printf("[%d,%d]=%I64d*%I64d=%I64d\n",i,j,sphii[o][a/i],sphi[o][j]-sphi[o][i-1],sphii[o][a/i]*(sphi[o][j]-sphi[o][i-1]));
        }
        x=mul((x+sphi[o][a])%Mod[o],pow(2,phi[o]-1,o),o);

        int dtot=0;
        for(int i=1;i<=prime[0]&&(LL)prime[i]*prime[i]<=n;++i)
            if(n%prime[i]==0){
                dvs[dtot]=prime[i];
                ind[dtot]=0;
                while(n%prime[i]==0)n/=prime[i],++ind[dtot];
                ++dtot;
            }
        if(n!=1){
            dvs[dtot]=n,ind[dtot]=1;
            ++dtot;
            n=1;
        }
        for(int i=dtot;i--;)
            for(int j=ind[i];j--;)
                n*=dvs[i];
        /*cout<<n<<"=";
        for(int i=dtot;i--;)cout<<dvs[i]<<"^"<<ind[i]<<"*";
        puts("");*/

        ans=0;
        dfs(dtot-1,n,1,o);
        if(!o)ans/=Mod[1],n/=Mod[1];
        ans=(ans*pow(n,Mod[1]-2,1)%Mod[1]+Mod[1])%Mod[1];
        cout<<ans<<endl;
    }
}

代码( μ ):

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long LL;
const LL Mod[2]={(LL)(1e9+7)*(int)(1e9+7),1e9+7},phi[2]={(LL)(1e9+7)*(int)(1e9+6),1e9+6};
LL mul(LL a,LL b,bool o){
    if(a<=2e9&&b<=2e9)return a*b%Mod[o];
    else{
        LL ans=0,sum=a%Mod[o];
        for(b=(b+Mod[o])%Mod[o];b;b>>=1,sum=(sum<<1)%Mod[o])
            if(b&1)
                ans=(ans+sum)%Mod[o];
        return ans;
    }
}
LL pow(LL a,LL x,bool o){
    LL ans=1,prod=a%Mod[o];
    //cout<<"Pow("<<a<<"^"<<x<<")=";
    for(x%=Mod[o]-1;x;x>>=1,prod=mul(prod,prod,o))
        if(x&1)
            ans=mul(ans,prod,o);
    //cout<<ans<<" "<<prod<<endl;
    return ans;
}
const int A=1e7+5;
int smu[A];
bool p[A];
int prime[1000005];
LL dvs[50];
int ind[50];
LL x;
LL ans;
void dfs(int i,LL f,LL phi,bool o){
    if(i<0){
        //cout<<"dfs("<<i<<","<<f<<","<<phi<<","<<o<<")\n";
        ans=(ans+mul(mul(x-1,pow(x-1,f-1,o)+(f&1?-1:1),o),phi,o))%Mod[o];
        return;
    }
    dfs(i-1,f,phi,o);
    dfs(i-1,f/=dvs[i],phi*=(dvs[i]-1),o);
    for(int j=ind[i];--j;)dfs(i-1,f/=dvs[i],phi*=dvs[i],o);
}
int main(){
    smu[1]=1;
    for(int i=2;i<=10000000;++i){
        if(!p[i]){
            prime[++prime[0]]=i;
            smu[i]=-1;
        }
        for(int j=1;j<=prime[0]&&i*prime[j]<=10000000;++j){
            p[i*prime[j]]=1;
            if(i%prime[j])smu[i*prime[j]]=-smu[i];
            else break;
        }
    }
    for(int i=2;i<=10000000;++i)smu[i]+=smu[i-1];

    int t;
    cin>>t;
    while(t--){
        LL n;
        int a;
        cin>>n>>a;

        bool o=n%Mod[1];
        LL f2=0,f3=0,prod;
        x=0;
        for(int i=1,j;i<=a;i=j+1){
            j=a/(a/i);
            prod=(LL)(a/i)*(a/i)%Mod[o];
            f2=(f2+mul(prod,smu[j]-smu[i-1],o))%Mod[o];
            f3=(f3+mul(mul(prod,a/i,o),smu[j]-smu[i-1],o))%Mod[o];
        }
        x=mul((f3+mul(f2,3,o)+2)%Mod[o],pow(6,phi[o]-1,o),o);
        //cout<<mul(6,pow(6,phi[o]-1,o),o)<<endl;
        //cout<<f3+mul(f2,3,o)+2<<"*"<<pow(6,phi[o]-1,o)<<"="<<mul((f3+mul(f2,3,o)+2)%Mod[o],pow(6,phi[o]-1),o)<<endl;
        //cout<<f2<<" "<<f3<<"->"<<x<<endl; 

        int dtot=0;
        for(int i=1;i<=prime[0]&&(LL)prime[i]*prime[i]<=n;++i)
            if(n%prime[i]==0){
                dvs[dtot]=prime[i];
                ind[dtot]=0;
                while(n%prime[i]==0)n/=prime[i],++ind[dtot];
                ++dtot;
            }
        if(n!=1){
            dvs[dtot]=n,ind[dtot]=1;
            ++dtot;
            n=1;
        }
        for(int i=dtot;i--;)
            for(int j=ind[i];j--;)
                n*=dvs[i];
        /*cout<<n<<"=";
        for(int i=dtot;i--;)cout<<dvs[i]<<"^"<<ind[i]<<"*";
        puts("");*/

        ans=0;
        dfs(dtot-1,n,1,o);
        if(!o)ans/=Mod[1],n/=Mod[1];
        ans=(ans*pow(n,Mod[1]-2,1)%Mod[1]+Mod[1])%Mod[1];
        cout<<ans<<endl;
    }
}

这题因为不会写暴力,wa了无数遍,这里写下每一遍wa的原因:
①没有考虑n可能为p的倍数。
②快速乘的时候要保证分解的那个数是正数。
③指数应该模 ϕ(p) ,而不是p。
④误以为珠子的翻转可以用旋转搞出来。。然而实际并不能。
⑤没有想清楚模 p2 的时候该怎么搞。。一直在改一点wa一遍改一点wa一遍。。在一遍遍wa中渐渐发现要全改。。
⑥在开存质因子的数组时用了int。。忘了开long long。

总结:
11ax=+n=0(ax)n
②一个字符串的所有循环节长度必然均为其最短循环节的倍数。
③在做模除法时,一定要考虑清楚是否会出现没有逆元的情况。
④除一个没有逆元的数时,可以对其没有逆元的部分和有逆元的部分分开处理。
⑤通常情况下用 ϕ 会比用 μ 更优,但是若预处理的时间复杂度成为主要矛盾,把 ϕ 换成 μ 通常会是更好的选择,因为筛 μ 比筛 ϕ 快很多。
⑥如果不知道怎么写暴力,就写两个标算对拍吧~
⑦快速乘的时候要保证分解的那个数是正数。
⑧指数应该模 ϕ(p) ,而不是p。
⑨写代码前一定要仔细想清楚+冷静。
⑩在开变量之前一定要检查变量的范围!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: bzoj作为一个计算机竞赛的在线评测系统,不仅可以提供大量的题目供程序员练习和学习,还可以帮助程序员提升算法和编程能力。为了更好地利用bzoj进行题目的学习和刷题,制定一个bzoj做题计划是非常有必要的。 首先,我们需要合理安排时间,每天留出一定的时间来做bzoj的题目。可以根据自己的时间安排,每天挑选适量的题目进行解答。可以先从难度较低的题目开始,逐渐提高难度,这样既能巩固基础知识,又能挑战自己的思维能力。 其次,要有一个计划和目标。可以规划一个每周或每月的题目数量目标,以及每个阶段要学习和掌握的算法知识点。可以根据bzoj的题目分类,如动态规划、图论、贪心算法等,结合自己的实际情况,有针对性地选择题目进行学习。 此外,要充分利用bzoj提供的资源。bzoj网站上有很多高质量的题解和优秀的解题代码,可以参考和学习。还有相关的讨论区,可以与其他程序员交流和讨论,共同进步。 最后,要坚持并保持思考。做题不是单纯为了刷数量,更重要的是学会思考和总结。遇到难题时,要有耐心,多思考,多尝试不同的解法。即使不能一次性解出来,也要学会思考和分析解题过程,以及可能出现的错误和优化。 总之,bzoj做题计划的关键在于合理安排时间、制定目标、利用资源、坚持思考。通过有计划的刷题,可以提高算法和编程能力,并培养解决问题的思维习惯,在计算机竞赛中取得更好的成绩。 ### 回答2: bzoj做题计划是指在bzoj这个在线测评系统上制定一套学习和刷题的计划,并且将计划记录在excel表格中。该计划主要包括以下几个方面的内容。 首先是学习目标的设定。通过分析自己的水平和知识缺口,可以设定一个合理的目标,比如每天解决一定数量的题目或者提高特定的算法掌握程度。 其次是题目选择的策略。在excel表格中可以记录下自己选择的题目编号、题目类型和难度等信息。可以根据题目的类型和难度来安排每天的刷题计划,确保自己可以逐步提高技巧和解题能力。 然后是学习进度的记录和管理。将每天的完成情况记录在excel表格中,可以清晰地看到自己的学习进度和任务完成情况。可以使用图表等功能来对学习进度进行可视化展示,更好地管理自己的学习计划。 同时,可以在excel表格的备注栏中记录下每道题目的解题思路、关键点和需要复习的知识点等信息。这样可以方便自己回顾和总结,巩固所学的知识。 最后,可以将excel表格与其他相关资料进行整合,比如算法教材、题目解析和学习笔记等。这样可以形成一个完整的学习档案,方便自己进行系统的学习和复习。 总之,bzoj做题计划excel的制定和记录可以帮助我们更加有条理和高效地进行学习和刷题。通过合理安排学习目标和题目选择策略,记录学习进度和思路,并整合其他学习资料,我们可以提高自己的解题能力,并在bzoj上取得更好的成绩。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值