In mathematics you don't understand things, you just get used to them.

分类 笔记 下的文章

「CF 1486A」Shifting Stacks

Link.

考虑最少需要操作多少次后判断。

#include<map>
#include<cstdio>
using namespace std;
int t,n,flag;
long long sum,cum,x;
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        flag=1;
        for(int i=1;i<=n;++i)
        {
            scanf("%lld",&x);
            sum+=x;
            if(sum-cum<0)    flag=0;
            cum+=i;
        }
        printf("%s\n",flag?"YES":"NO");
        sum=cum=0;
    }
    return 0;
}

「CF 1486B」Eastern Exhibition

Link.

可以发现行列独立,所以用个结论就行了。

#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
int t,n;
long long one[1010],ano[1010];
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i)    scanf("%lld %lld",&one[i],&ano[i]);
        sort(one+1,one+n+1);
        sort(ano+1,ano+n+1);
        printf("%lld\n",(one[(n+2)/2]-one[(n+1)/2]+1)*(ano[(n+2)/2]-ano[(n+1)/2]+1));
    }
    return 0;
}

「CF 1486C1」Guessing the Greatest (easy version)

Link.

看到 $20$ 的限制,想到 Robot Arms,猜想是二分。

然后就完了。

#include<cstdio>
int engoric(int l,int r)
{
    int res;
    printf("? %d %d\n",l,r);
    fflush(stdout);
    scanf("%d",&res);
    return res;
}
int n;
int main()
{
    scanf("%d",&n);
    int mxpos=engoric(1,n);
    int l=1,r=n;
    if(mxpos==1)    l=1;
    else
    {
        if(engoric(1,mxpos)==mxpos)    r=mxpos;
        else    l=mxpos;
    }
    if(l==mxpos)
    {
        while(l+1<r)
        {
            int mid=(l+r)>>1;
            if(engoric(mxpos,mid)==mxpos)    r=mid;
            else    l=mid;
        }
        printf("! %d\n",r);
    }
    else
    {
        while(l+1<r)
        {
            int mid=(l+r)>>1;
            if(engoric(mid,mxpos)==mxpos)    l=mid;
            else    r=mid;
        }
        printf("! %d\n",l);
    }
    return 0;
}

「CF 1486C2」Guessing the Greatest (hard version)

Link.

同 C1。

「CF 1486D」Max Median

Link.

「CF 1486E」Paired Payment

Link.

「CF 1486F」Pairs of Paths

Link.

「CF 1490A」Dense Array

Link.

显然不满足的 adjacent elements 之间一直加 $\min\times2,\min\times4,\cdots,\min\times2^{k}$,满足 $\min\times2^{k}\le\max$ 即可。

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
int t,n,a[60],ans;
bool judge(double one,double ano)
{
    return max(one,ano)/min(one,ano)<=2.0;
}
int jump(int one,int ano)
{
    int cone=min(one,ano),cano=max(one,ano),res=0;
    while(cone<=cano)
    {
        if((cone<<1)>=cano)    break;
        else
        {
            cone<<=1;
            res++;
        }
    }
    return res;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        ans=0;
        scanf("%d",&n);
        for(int i=1;i<=n;++i)    scanf("%d",&a[i]);
        for(int i=2;i<=n;++i)    ans+=judge(a[i],a[i-1])?0:jump(a[i],a[i-1]);
        printf("%d\n",ans);
    }
    return 0;
}

「CF 1490B」Balanced Remainders

Link.

把原序列的 $c_{0\sim2}$ 统计出来然后贪心(具体怎么贪看代码,不好描述)模拟。

#include<cstdio>
#include<algorithm>
using namespace std;
int t,n,a[30010],c[3],ans;
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&a[i]);
            ++c[a[i]%3];
        }
        while((c[0]^c[1])||(c[0]^c[2]))
        {
            ans++;
            if(c[0]==*max_element(c,c+3))
            {
                --c[0];
                ++c[1];
            }
            else if(c[1]==*max_element(c,c+3))
            {
                --c[1];
                ++c[2];
            }
            else
            {
                --c[2];
                ++c[0];
            }
        }
        printf("%d\n",ans);
        for(int i=0;i<3;++i)    c[i]=0;
        ans=0;
    }
    return 0;
}

「CF 1490C」Sum of Cubes

Link.

枚举一个 $a$,然后判断 $n-a^{3}$ 是否为完全立方数即可,这个可以二分,注意二分的范围不要乱搞,容易溢出。

#include<cmath>
#include<cstdio>
using namespace std;
int t,flag;
long long n;
long long cud(long long x)
{
    return x*x*x;
}
bool check(long long x)
{
    long long l=1,r=pow(x,1.0/3.0)+5;
    while(l<=r)
    {
        long long mid=(l+r)>>1;
        if(cud(mid)>x)    r=mid-1;
        else if(cud(mid)<x)    l=mid+1;
        else    return true;
    }
    return false;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        flag=0;
        scanf("%lld",&n);
        for(int i=1;cud(i)<n;++i)
        {
            if(check(n-cud(i)))
            {
                flag=1;
                break;
            }
        }
        if(flag)    printf("YES\n");
        else    printf("NO\n");
    }
    return 0;
}

「CF 1490D」Permutation Transformation

Link.

递归建树,照题意模拟即可。

#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
vector<int> e[110];
int t,n,a[110],dep[110];
int build(int l,int r)
{
    if(l>r)    return -1;
    int root=0,pos=0;
    for(int i=l;i<=r;++i)
    {
        if(a[i]>root)
        {
            root=a[i];
            pos=i;
        }
    }
    if(l^r)
    {
        int one=build(l,pos-1),ano=build(pos+1,r);
        if(~one)    e[root].push_back(one);
        if(~ano)    e[root].push_back(ano);
        return root;
    }
    else    return root;
}
void dfs(int x)
{
    for(int i=0;i<e[x].size();++i)
    {
        int y=e[x][i];
        dep[y]=dep[x]+1;
        dfs(y);
    }
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i)    scanf("%d",&a[i]);
        dfs(build(1,n));
        for(int i=1;i<=n;++i)    printf("%d ",dep[a[i]]);
        printf("\n");
        for(int i=1;i<=n;++i)
        {
            dep[i]=0;
            e[i].clear();
        }
    }
    return 0;
}

「CF 1490E」Accidental Victory

Link.

贪心,记录个 id 后排序(看代码吧)。

#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
vector<int> ans;
pair<long long,int> a[200010];
int t,n;
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
        {
            scanf("%lld",&a[i].first);
            a[i].second=i;
        }
        sort(a+1,a+n+1);
        for(int i=1;i<=n;++i)    a[i].first+=a[i-1].first;
        ans.push_back(a[n].second);
        for(int i=n-1;i>=1;--i)
        {
            if(a[i].first>=a[i+1].first-a[i].first)    ans.push_back(a[i].second);
            else    break;
        }
        sort(ans.begin(),ans.end());
        printf("%d\n",(int)ans.size());
        for(int i=0;i<ans.size();++i)    printf("%d ",ans[i]);
        printf("\n");
        ans.clear();
        for(int i=1;i<=n;++i)    a[i]=make_pair(0,0);
    }
    return 0;
}

「CF 1490F」Equalize the Array

Link.

统计出现次数和出现次数的出现次数,然后根号模拟取 $\min$。

#include<map>
#include<cstdio>
#include<algorithm>
using namespace std;
const int INF=1e9;
map<int,int> one,ano;
int t,n,a[200010],ans;
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&a[i]);
            ++one[a[i]];
        }
        for(map<int,int>::iterator now=one.begin();now!=one.end();++now)    ++ano[now->second];
        ans=INF;
        int l=0,r=n,c=one.size();
        for(map<int,int>::iterator now=ano.begin();now!=ano.end();++now)
        {
            ans=min(ans,l+r-c*now->first);
            l+=now->first*now->second;
            r-=now->first*now->second;
            c-=now->second;
        }
        printf("%d\n",ans);
        one.clear();
        ano.clear();
    }
    return 0;
}

「CF 1490G」Old Floppy Drive

Link.

denote for $S$ of the sum of all elements,for $pre$ of the prefix sum of the origin sequence。

首先判断原 $pre$ 里面有没有 $x$,这个搞个 std::map 就有了。

when $S\le0\and\max\{pre_{i}\}<x$ the answer doesn't exist.

if $S\ge0\and\not\exists i,s.t.pre_{i}=x$:此时先把 $x:=x\bmod S$,然后就查 std::map

但是你会发现这样做写起来非常麻烦,可能需要手写平衡树。

于是你发现读错了题,是 $\ge x$ 不是 $=x$ (日你 horse)。

然后负数直接不存进 $pre$ 然后开两个 std::vector 二分就好了。

#include<vector>
#include<cstdio>
#include<algorithm>
using namespace std;
const long long INF=1e18;
vector<long long> onepre;
vector<int> anopre;
long long x,S,mx,len;
int t,n,m;
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        mx=-INF;
        S=0;
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;++i)
        {
            scanf("%lld",&x);
            S+=x;
            if(onepre.empty()||S>*(prev(onepre.end())))
            {
                onepre.push_back(S);
                anopre.push_back(i-1);
            }
            mx=max(S,mx);
        }
//        printf("-------------------------\n");
//        printf("onemp area:\n");
//        for(auto now:onemp)
//        {
//            printf("    preval=%lld ; preval appearing position=",now.first);
//            for(auto won:now.second)    printf("%d ",won);
//            printf("\n");
//        }
//        printf("\nanomp area:\n");
//        for(auto now:anomp)
//        {
//            printf("[preval=%lld boolean=%d]\n",now.first,now.second);
//        }
//        printf("-------------------------\n");
        while(m--)
        {
//            int minuser=0;
            scanf("%lld",&x);
            if(lower_bound(onepre.begin(),onepre.end(),x)!=onepre.end())    printf("%d ",anopre[lower_bound(onepre.begin(),onepre.end(),x)-onepre.begin()]);
            else if(S<=0)    printf("-1 ");
            else
            {
//                minuser=((x%S)==0);
                len=(mx<x)?((x-mx+S-1)/S):0;
//                printf("(%lld %lld %lld %lld)",x,S,x%S,x/S);
                printf("%lld ",(lower_bound(onepre.begin(),onepre.end(),x%S)==onepre.end())?(-1):(len*n+anopre[lower_bound(onepre.begin(),onepre.end(),x-len*S)-onepre.begin()])/*((((x%S)==0)?(0):(anopre[lower_bound(onepre.begin(),onepre.end(),x%S)-onepre.begin()]))+(int)(x/S)*len-minuser)*/);
            }
        }
        printf("\n");
        onepre.clear();
        anopre.clear();
    }
    return 0;
}

「CF 1485A」Add and Divide

Link.

贪心。枚举 $[b,b+\log_{2}\text{range}]$ 然后取个 $\min$。

#include<cstdio>
#include<algorithm>
using namespace std;
int t,a,b,ans;
int search(int bas)
{
    if(bas>1)
    {
        int tmp=a,res=0;
        while(tmp>0)
        {
            tmp/=bas;
            res++;
        }
        return res;
    }
    else    return 1e9;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d",&a,&b);
        if(a==b)    printf("%d\n",2);
        else if(a<b)    printf("%d\n",1);
        else
        {
            ans=1e9;
            for(int i=b;i<=b+233;++i)    ans=min(ans,search(i)+i-b);
            printf("%d\n",ans);
        }
    }
    return 0;
}

「CF 1485B」Replace and Keep Sorted

Link.

每个元素都可以上下摇摆于是预处理前缀差分和和后缀差分和(因为是 strictly increasing 所以要减 $1$)即可。

#include<cstdio>
int n,m,k,a[100010],fro[100010],rea[100010];
void pre()
{
    for(int i=1;i<=n;++i)    fro[i]=a[i]-a[i-1]-1;
    for(int i=n;i>=1;--i)    rea[i]=a[i+1]-a[i]-1;
    for(int i=1;i<=n;++i)    fro[i]+=fro[i-1];
    for(int i=n;i>=1;--i)    rea[i]+=rea[i+1];
}
int main()
{
    scanf("%d %d %d",&n,&m,&k);
    for(int i=1;i<=n;++i)    scanf("%d",&a[i]);
    pre();
    while(m--)
    {
        int l,r;
        scanf("%d %d",&l,&r);
        printf("%d\n",k-a[r]+a[l]-1+fro[r]-fro[l]+rea[l]-rea[r]);
    }
    return 0;
}

「CF 1485C」Floor and Mod

Link.

$$ a\bmod b=\lfloor\frac{a}{b}\rfloor=k \\ \rightarrow a=kb+k\rightarrow a=(b+1)k\rightarrow k=\frac{a}{b+1} \\ k<b\rightarrow k^{2}<k(b+1)=a\le x\rightarrow 1\le k\le\sqrt{x} \\ 1\le a\le x\rightarrow 1\le(b+1)k\le x\rightarrow1\le b\le\frac{x}{k}-1 \\ \rightarrow\text{ans}=\sum_{k=1}^{\sqrt{x}}\max(0,\min(y,\frac{x}{k}-1)-k) $$

#include<cstdio>
#include<algorithm>
using namespace std;
const long long zero=0ll;
long long t,x,y,ans;
int main()
{
    scanf("%lld",&t);
    while(t--)
    {
        scanf("%lld %lld",&x,&y);
        ans=0;
        for(long long i=1;i*i<=x;++i)    ans+=max(zero,min(y,x/i-1)-i);
        printf("%lld\n",ans);
    }
    return 0;
}

「CF 1485D」Multiples and Power Differences

Link.

  • $(i+j)\bmod 2=1$:$b_{i,j}=\text{lcm}(1,\cdots,16)$。
  • $(i+j)\bmod 2=0$:$b_{i,j}=\text{lcm}(1,\cdots,16)+a_{i,j}^{4}$。

#include<cstdio>
int n,m,x;
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=m;++j)
        {
            scanf("%d",&x);
            if((i+j)&1)    printf("%d ",720720);
            else    printf("%d ",720720+x*x*x*x);
        }
        printf("\n");
    }
    return 0;
}

「CF 1485E」Move and Swap

Link.

blue 因为就是一直往下跑,所以一次操作在哪里不影响。

于是设 $f_{u}$ 为操作完毕后 red 跑到 $u$ 的 maximum value。

  • $v\in\text{son}(u)$ 为 red:此时没发生 swapping,$f_{u}=f_{v}+|a_{u}-a_{v}|$。
  • $v\in\text{son}(u)$ 为 blue:此时发生了 swapping,那么枚举 $v$ 的同层结点 $anov$,$f_{u}=f_{anov}+|a_{u}-a_{anov}|$。

#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
const long long INF=1e18;
vector<int> e[200010],same[200010];
int t,n,dep[200010],fa[200010],leaf;
long long a[200010],f[200010];
void dfs(int x,int las)
{
    dep[x]=dep[las]+1;
    fa[x]=las;
    leaf=max(leaf,dep[x]);
    for(int i=0;i<e[x].size();++i)
    {
        int y=e[x][i];
        if(y^las)    dfs(y,x);
    }
}
void DP(int d)
{
    for(int i=d;i>1;--i)
    {
        long long mn=INF,mx=-INF,one=-INF,ano=-INF;
        for(int j=0;j<same[i].size();++j)
        {
            mn=min(mn,a[same[i][j]]);
            mx=max(mx,a[same[i][j]]);
        }
        for(int j=0;j<same[i].size();++j)    f[fa[same[i][j]]]=max(f[fa[same[i][j]]],max(a[same[i][j]]-mn,mx-a[same[i][j]])+f[same[i][j]]);
        for(int j=0;j<same[i].size();++j)
        {
            one=max(one,f[same[i][j]]+a[same[i][j]]);
            ano=max(ano,f[same[i][j]]-a[same[i][j]]);
        }
        for(int j=0;j<same[i].size();++j)    f[fa[same[i][j]]]=max(f[fa[same[i][j]]],max(one-a[same[i][j]],ano+a[same[i][j]]));
    }
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=2;i<=n;++i)
        {
            int x;
            scanf("%d",&x);
            e[x].push_back(i);
            e[i].push_back(x);
        }
        for(int i=2;i<=n;++i)    scanf("%d",&a[i]);
        dfs(1,0);
        for(int i=1;i<=n;++i)    same[dep[i]].push_back(i);
        DP(leaf);
        printf("%lld\n",f[1]);
        for(int i=1;i<=n;++i)
        {
            f[i]=dep[i]=fa[i]=0;
            same[i].clear();
            e[i].clear();
        }
        leaf=0;
    }
    return 0;
}

「CF 1485F」Copy or Prefix Sum

Link.

Description

Link.

$\operatorname{Rainyrabbit}$ 是一个数学极好的萌妹子,近期他发现了一个可爱的函数:

$$ f(n,m,k)=\sum_{d=1}^n d^k\lfloor\dfrac{n}{\operatorname{lcm}(d,m)}\rfloor $$

其中 $\operatorname{lcm}(d,m)$ 表示 $d$ 和 $m$ 的最小公倍数。

她觉得只算这一个函数太单调了于是想要对这个函数求和,给出 $a,b,c$,求:

$$ \sum_{i=1}^a\sum_{j=1}^b\sum_{k=0}^cf(i,j,k) $$

同时 $\operatorname{I.w.rabbit}$ 固定 $c$ 的值不变,给出 $T$ 组 $a,b$,请回答此时式子对 $998244353$ 取模后的值。

算是概括过了额。

Solution

先看 $f$ 怎么把那个烦死的整除去掉。

$$ \begin{aligned} f(n,m,k)&=\sum_{d=1}^{n}d^{k}\lfloor\frac{n}{\text{lcm}(d,m)}\rfloor \\ &=\sum_{d=1}^{n}d^{k}\lfloor\frac{\frac{n}{d}}{\frac{m}{\gcd(d,m)}}\rfloor \\ &=\sum_{d=1}^{n}d^{k}\lfloor\frac{\frac{n}{d}}{\frac{m}{\gcd(d,m)}}\rfloor \\ \end{aligned} $$

来看 $\lfloor\frac{\frac{n}{d}}{\frac{m}{\gcd(d,m)}}\rfloor$,因为 $\lfloor\frac{a}{b}\rfloor=\sum_{i=1}^{a}[b|i]$,所以 $\lfloor\frac{\frac{n}{d}}{\frac{m}{\gcd(d,m)}}\rfloor=\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}[\frac{m}{\gcd(d,m)}|i]=\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}[m|id]$。所以

$$ \begin{aligned} f(n,m,k)&=\sum_{d=1}^{n}d^{k}\lfloor\frac{\frac{n}{d}}{\frac{m}{\gcd(d,m)}}\rfloor \\ &=\sum_{d=1}^{n}d^{k}\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}[m|id] \\ &=\sum_{m|i}^{n}\sum_{d|i}d^{k} \\ &=\sum_{m|i}^{n}\sigma_{k}(i) \\ \end{aligned} $$

然后代回原式。

$$ \begin{aligned} \sum_{i=1}^{a}\sum_{j=1}^{b}\sum_{k=0}^{c}\sum_{j|d}^{i}\sigma_{k}(d)&=\sum_{k=0}^{c}\sum_{d=1}^{a}\sigma_{k}(d)(a-d+1)\sum_{j=1}^{b}[j|d] \\ \end{aligned} $$

考虑如何计算 $\sum_{k=0}^{c}\sigma_{k}(d)$。把函数又拆回去:

$$ \begin{aligned} \sum_{k=0}^{c}\sigma_{k}(d)&=\sum_{w|d}\sum_{k=0}^{c}w^{k}=\sum_{w|d}\frac{w^{c+1}-1}{w-1} \end{aligned} $$

最后一步是等比数列求和,然后你就可以调和级数预处理了。具体来说就是线筛的时候筛一下 $w^{c+1}$,这东西是个完全积性函数,你乱筛就行了。

设这玩意儿为 $s(d)=\sum_{w|d}\frac{w^{c+1}-1}{w-1}$,原式改写为:

$$ \sum_{d=1}^{a}s(d)(a-d+1)\sum_{j=1}^{b}[j|d] \\ $$

然后后面那个 sigma 你也可以反过来直接调和级数。还有就是 $b>a$ 的时候没有贡献,所以可以取个 $\min$,这样能多几分。

来看看怎么屮多测。

数表 那道题一样,我们把询问离线下来,以 $b$ 为关键字排序后树状数组。

把中间那个系数拆出来,变成:

$$ \sum_{d=1}^{a}(a+1)s(d)-d\times s(d)\sum_{j=1}^{b}[j|d] \\ $$

前面那个好说,直接来;后面就在树状数组修改时乘上系数即可。

综上,维护两个树状数组即可。

#include<cstdio>
#include<algorithm>
using namespace std;
void read(long long &x)
{
    x=0;
    char c=getchar();
    while(c<'0'||c>'9')    c=getchar();
    while(c>='0'&&c<='9')
    {
        x=(x<<3)+(x<<1)+(c^'0');
        c=getchar();
    }
}
void write(long long x)
{
    if(x>9)    write(x/10);
    putchar((x%10)^'0');
}
const long long mod=998244353;
struct node
{
    long long a,b,ID;
}nodes[200010];
struct fenwick
{
    #define lowbit(x) ((x)&-(x))
    long long fen[1000010],mx;
    void ins(long long x,long long y)
    {
        while(x<=mx)
        {
            fen[x]=(fen[x]+y)%mod;
            x+=lowbit(x);
        }
    }
    long long find(long long x)
    {
        long long res=0;
        while(x)
        {
            res=(res+fen[x])%mod;
            x^=lowbit(x);
        }
        return res;
    }
}onefe,anofe;
long long t,a,b,c,tag[1000010],prime[1000010],cnt,fu[1000010],exfu[1000010],power[1000010],cur=1,mx,ans[200010];
bool cmp(node one,node ano)
{
    return one.b<ano.b;
}
long long cqpow(long long bas,long long fur)
{
    long long res=1;
    while(fur)
    {
        if(fur&1)    res=res*bas%mod;
        bas=bas*bas%mod;
        fur>>=1;
    }
    return res;
}
void search(long long x)
{
    tag[1]=power[1]=1;
    for(long long i=2;i<=x;++i)
    {
        if(!tag[i])
        {
            prime[++cnt]=i;
            power[i]=cqpow(i,c+1);
        }
        for(long long j=1;j<=cnt&&prime[j]*i<=x;++j)
        {
            tag[prime[j]*i]=1;
            power[prime[j]*i]=power[prime[j]]*power[i]%mod;
            if(i%prime[j]==0)    break;
        }
    }
    fu[1]=(c%mod+1)%mod;
    for(long long i=2;i<=x;++i)    fu[i]=((power[i]-1+mod)%mod)*cqpow(i-1,mod-2)%mod;
    for(long long i=1;i<=x;++i)
    {
        for(long long j=i;j<=x;j+=i)    exfu[j]=(exfu[j]+fu[i])%mod;
    }
}
int main()
{
    read(t);
    read(c);
    search(1000000);
    for(long long i=1;i<=t;++i)
    {
        read(nodes[i].a);
        read(nodes[i].b);
        nodes[i].ID=i;
        nodes[i].b=min(nodes[i].a,nodes[i].b);
        mx=max(mx,nodes[i].a);
    }
    sort(nodes+1,nodes+t+1,cmp);
    onefe.mx=anofe.mx=mx;
    for(long long i=1;i<=mx&&cur<=t;++i)
    {
        for(long long j=i;j<=mx;j+=i)
        {
            onefe.ins(j,exfu[j]);
            anofe.ins(j,exfu[j]*j%mod);
        }
        while(i==nodes[cur].b)
        {
            ans[nodes[cur].ID]=((onefe.find(nodes[cur].a)*(nodes[cur].a+1))%mod-anofe.find(nodes[cur].a)+mod)%mod;
            cur++;
        }
    }
    for(long long i=1;i<=t;++i)
    {
        write(ans[i]);
        putchar('\n');
    }
    return 0;
}

Part. 1 Preface

这个东西是我在做 JZPTAB 的时候 LYC 给我讲的。

然后发现这是个通法,就写一写。

本文除了例题所有代码均未经过编译,仅作为参考

Part. 2 Untitled(怎么取标题呀)(哦 正文)

Part. 2-1 Worse ver.

对于一个积性函数 $f(n)$,如果我们已知 $f(1),f(p),f(p^{k})$ ($p$ 是一个素数)并且可以在 $O(\log_{2}(n))$ 的时间内算出来的话,我们就可以在 $O(n\log_{2}(n))$ 的时间内利用 Euler 筛筛出 $f(1\cdots n)$ 的值。

举个例子,假设

$$ f(n)=\sum_{d|n}d\times\varphi(\lfloor\frac{n}{d}\rfloor) $$

由于 $\text{id}$ 卷 $\varphi$ 卷不出个什么现成的函数,所以我们得考虑自己把它筛出来。

带个 $p$ 进去可知

$$ \begin{cases} f(1)=1 \\ \displaystyle f(p)=2\times p-1 \\ \displaystyle f(p^{k})=(k+1)\times p^{k}-k\times p^{k-1} \end{cases} $$

以下内容请参考 Euler 筛代码来看:

void sieve ( const int x ) {
    tag[1] = 1, f[1] = /* DO SOMETHING 1 */;
    for ( int i = 2; i <= x; ++ i ) {
        if ( ! tag[i] ) {
            pSet[++ psc] = i;
            f[i] = /* DO SOMETHING 2 */;
        }
        for ( int j = 1; j <= psc && pSet[j] * i <= x; ++ j ) {
            tag[pSet[j] * i] = 1;
            if ( ! ( i % pSet[j] ) ) {
                f[pSet[j] * i] = /* DO SOMETHING 3 */;
                break;
            }
            else    f[pSet[j] * i] = /* DO SOMETHING */;
        }
    }
}

函数 $\text{sieve}$ 就是 Euler 筛的过程。我在代码中留了四个空,分别来看我们需要做什么。

  • 第一个空很显然,把 $f(1)$ 赋给 f[1] 即可。
  • 第二个空也很显,把 $f(p)$ 付给 f[i]
  • 我们重点来看第三个空。

首先因为此时的 $i,\text{pSet}_{j}$ 不互质,所以不能直接照完全积性函数筛。

首先,我们需要把 $i\times\text{pSet}_{j}$ 中 $\text{pSet}_{j}$ 因子全部除掉,除完后的结果记为 $\text{tmp}$,$\text{pSet}_{j}$ 因子数量记为 $\text{power}$,即 $i\times\text{pSet}_{j}=\text{pSet}_{j}^{\text{power}}\times c$。

就是类似下面代码做的事情

int tmp = i / pSet[j], power = 2;
while ( ! ( i % pSet[j] ) )    i /= pSet[j], ++ power;

然后对 $\text{tmp}$ 进行分类讨论:

    • $\text{tmp}=1$:此时 $i\times\text{pSet}_{j}$ 是 $\text{pSet}_{j}$ 的 $\text{power}$ 次方,把 $f(p^{k})$ 赋给 f[pSet[j] * i] 即可。
    • $\text{tmp}>1$:此时 $\text{tmp}$ 与 $\frac{i\times\text{pSet}_{j}}{\text{tmp}}$ 互质,于是照积性函数 f[pSet[j] * i] = f[pSet[j] * i / tmp] * f[tmp]

于是第三个空做完了。

  • 第四个空中 $\text{pSet}_{j}$ 与 $i$ 互质,于是照积性函数 f[pSet[j] * i] = f[pSet[j]] * f[i]

于是我们得到了完整代码

void sieve ( const int x ) {
    tag[1] = 1, f[1] = 1;
    for ( int i = 2; i <= x; ++ i ) {
        if ( ! tag[i] ) {
            pSet[++ psc] = i;
            f[i] = 2 * i - 1;
        }
        for ( int j = 1; j <= psc && pSet[j] * i <= x; ++ j ) {
            tag[pSet[j] * i] = 1;
            if ( ! ( i % pSet[j] ) ) {
                int tmp = i / pSet[j], power = 2;
                while ( ! ( i % pSet[j] ) )    i /= pSet[j], ++ power;
                if ( tmp == 1 )    f[pSet[j] * i] = ( power + 1 ) * cqpow ( pSet[j], power ) - power * cqpow ( pSet[j], power - 1 );
                else    f[pSet[j] * i] = f[pSet[j] * i / tmp] * f[tmp];
                break;
            }
            else    f[pSet[j] * i] = f[pSet[j]] * f[i];
        }
    }
}

Part. 2-2 Better ver.

上述的方法的缺点显而易见:复杂度多出来个 $\log_{2}$。

更好的方法是记录最小质因子,具体见 ljs 博客 Link

Part. 3 Example

LOCAL 64388 - GCD SUM

$$ \sum_{i=1}^n\sum_{j=1}^m\textrm{gcd}(i,j) $$

共有 $T$ 组询问

$\text{task_id}$测试点数$n,m\leq$$T\leq$特殊性质
$1$1$10$$10^3$
$2$2$10^3$$10$
$3$3$10^3$$10^4$
$4$4$10^6$$10$$n = m$
$5$5$10^6$$10^4$$n = m$
$6$2$10^6$$10^5$$n = m$
$7$3$10^7$$10^6$$n = m$
$8$2$10^6$$10$
$9$3$10^6$$10^4$

放个 task 7 以外的部分分的推导

$$ \sum_{i=1}^{n}\sum_{j=1}^{m}\gcd(i,j) \\ \begin{aligned} &=\sum_{d=1}^{\min\{n,m\}}d\sum_{i=1}^{n}\sum_{j=1}^{m}[\gcd(i,j)=d] \\ &=\sum_{d=1}^{\min\{n,m\}}d\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}[\gcd(i,j)=1] \\ &=\sum_{d=1}^{\min\{n,m\}}d\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}\sum_{k|i,k|j}\mu(k) \\ &=\sum_{d=1}^{\min\{n,m\}}d\sum_{k|(\lfloor\frac{n}{d}\rfloor),k|(\lfloor\frac{m}{d}\rfloor)}\mu(k)(\lfloor\frac{n}{d\times k}\rfloor)(\lfloor\frac{m}{d\times k}\rfloor) \\ &=\sum_{d=1}^{\min\{n,m\}}d\sum_{k|(\lfloor\frac{n}{d}\rfloor),k|(\lfloor\frac{m}{d}\rfloor)}\mu(k)(\lfloor\frac{n}{d\times k}\rfloor)(\lfloor\frac{m}{d\times k}\rfloor) \\ &=\sum_{T=1}^{\min\{n,m\}}\sum_{d|T}d\times\mu(\lfloor\frac{T}{d}\rfloor)\times(\lfloor\frac{n}{T}\rfloor)\times(\lfloor\frac{m}{T}\rfloor) \\ &=\sum_{T=1}^{\min\{n,m\}}(\lfloor\frac{n}{T}\rfloor)\times(\lfloor\frac{m}{T}\rfloor)\times\sum_{d|T}d\times\mu(\lfloor\frac{T}{d}\rfloor) \\ &=\sum_{T=1}^{n}(\lfloor\frac{n}{T}\rfloor)^{2}\times\varphi(T) \\ \end{aligned} $$

对于 task 7,$n=m$ 让我们很方便地直接少了一个变量,然后就继续推

$$ \sum_{i=1}^{n}\sum_{j=1}^{n}\gcd(i,j) \\ \begin{aligned} &=\left(2\sum_{i=1}^{n}\sum_{j=1}^{i}\gcd(i,j)\right)-\frac{n(n+1)}{2} \\ &=\left(2\sum_{i=1}^{n}\sum_{d|i}d\times\sum_{j=1}^{i}[\gcd(i,j)=d]\right)-\frac{n(n+1)}{2} \\ &=\left(2\sum_{i=1}^{n}\sum_{d|i}d\times\sum_{j=1}^{\lfloor\frac{i}{d}\rfloor}[\gcd(\lfloor\frac{i}{d}\rfloor,j)=1]\right)-\frac{n(n+1)}{2} \\ &=\left(2\sum_{i=1}^{n}\sum_{d|i}d\times\varphi(\lfloor\frac{i}{d}\rfloor)\right)-\frac{n(n+1)}{2} \\ \end{aligned} $$

然后

$$ \text{let }f(n)=\sum_{d|n}d\times\varphi(\lfloor\frac{n}{d}\rfloor) $$

后面的就是前面举的例子了,略。

/*
\large\text{For 1e6 part} \\
\sum_{i=1}^{n}\sum_{j=1}^{m}\gcd(i,j) \\
\sum_{d=1}^{\min(n,m)}d\sum_{i=1}^{n}\sum_{j=1}^{m}[\gcd(i,j)=d] \\
\sum_{d=1}^{\min(n,m)}d\sum_{i=1}^{n/d}\sum_{j=1}^{m/d}[\gcd(i,j)=1] \\
\sum_{d=1}^{\min(n,m)}d\sum_{i=1}^{n/d}\sum_{j=1}^{m/d}\sum_{k|i,k|j}\mu(k) \\
\sum_{d=1}^{\min(n,m)}d\sum_{k|(n/d),k|(m/d)}\mu(k)(n/(dk))(m/(dk)) \\
\sum_{d=1}^{\min(n,m)}d\sum_{k|(n/d),k|(m/d)}\mu(k)(n/(dk))(m/(dk)) \\
\sum_{T=1}^{\min(n,m)}\sum_{d|T}d\times\mu(T/d)\times(n/T)\times(m/T) \\
\sum_{T=1}^{\min(n,m)}(n/T)\times(m/T)\times\sum_{d|T}d\times\mu(T/d) \\
\sum_{T=1}^{n}(n/T)^{2}\times\varphi(T) \\
\text{precalculate the last part} \\
\large\text{For 1e7 part} \\
n=m \\
\left(2\sum_{i=1}^{n}\sum_{j=1}^{i}\gcd(i,j)\right)-\frac{n(n+1)}{2} \\
\left(2\sum_{i=1}^{n}\sum_{d|i}d\times\sum_{j=1}^{i}[\gcd(i,j)=d]\right)-\frac{n(n+1)}{2} \\
\left(2\sum_{i=1}^{n}\sum_{d|i}d\times\sum_{j=1}^{i/d}[\gcd(i/d,j)=1]\right)-\frac{n(n+1)}{2} \\
\left(2\sum_{i=1}^{n}\sum_{d|i}d\times\varphi(i/d)\right)-\frac{n(n+1)}{2} \\
f(i)=\sum_{d|i}d\times\varphi(i/d) \\
\text{f(i) is able to be sieved;} \\
f(1)=1,f(p)=p-1+p=2\times p-1,f(p^{k})=(k+1)\times p^{k}-k\times p^{k-1}
*/
#include<cstdio>
#include<algorithm>
using namespace std;
int id,t,n,m,tag[10000010],prime[10000010],cnt;
long long f[10000010],phi[10000010];
long long cqpow(long long bas,int fur)
{
    long long res=1;
    while(fur)
    {
        if(fur&1)    res*=bas;
        bas*=bas;
        fur>>=1;
    }
    return res;
}
void search(int x)
{
    tag[1]=phi[1]=1;
    for(int i=2;i<=x;++i)
    {
        if(!tag[i])
        {
            prime[++cnt]=i;
            phi[i]=i-1;
        }
        for(int j=1;j<=cnt&&(long long)prime[j]*i<=x;++j)
        {
            tag[prime[j]*i]=1;
            if(i%prime[j]==0)
            {
                phi[prime[j]*i]=phi[i]*prime[j];
                break;
            }
            else    phi[prime[j]*i]=phi[i]*(prime[j]-1);
        }
    }
    for(int i=1;i<=x;++i)    phi[i]+=phi[i-1];
}
long long calc(int x,int y)
{
    long long res=0;
    int lim=min(x,y);
    for(int l=1,r;l<=lim;l=r+1)
    {
        r=min(x/(x/l),y/(y/l));
        res+=(long long)(n/l)*(m/l)*(phi[r]-phi[l-1]);
    }
    return res;
}
void exsearch(int x)
{
    tag[1]=f[1]=1;
    for(int i=2;i<=x;++i)
    {
        if(!tag[i])
        {
            prime[++cnt]=i;
            f[i]=(i<<1)-1;
        }
        for(int j=1;j<=cnt&&(long long)prime[j]*i<=x;++j)
        {
            tag[prime[j]*i]=1;
            if(i%prime[j]==0)
            {
                int tmp=i/prime[j],power=2;
                while(tmp%prime[j]==0)
                {
                    tmp/=prime[j];
                    power++;
                }
                if(tmp==1)    f[prime[j]*i]=(power+1)*cqpow(prime[j],power)-power*cqpow(prime[j],power-1);
                else    f[prime[j]*i]=f[prime[j]*i/tmp]*f[tmp];
                break;
            }
            else    f[prime[j]*i]=f[prime[j]]*f[i];
        }
    }
    for(int i=1;i<=x;++i)    f[i]+=f[i-1];
}
long long excalc(long long x)
{
    return (f[x]<<1)-((x*(x+1))>>1);
}
int main()
{
    scanf("%d%d",&id,&t);
    if(id^7)
    {
        search(1000000);
        while(t--)
        {
            scanf("%d%d",&n,&m);
            printf("%lld\n",calc(n,m));
        }
    }
    else
    {
        exsearch(10000000);
        while(t--)
        {
            scanf("%d%d",&n,&m);
            printf("%lld\n",excalc(n));
        }
    }
    return 0;
}