「NOIP Record」#15 数论题目选讲

Hankson的趣味题

从质因子的角度考虑。

把 $a,b,c,d$ 都分解了,对于一个质因子 $p_i$,题目给出的条件等价于$$\min \Big( e_{p_i} (x) ,e_{p_i} (a)\Big) =e_{p_i}(c)$$

$$
\max \Big( e_{p_i}(x),e_{p_i}(b) \Big) = e_{p_i}(d)
$$

讨论一下就能得到 $e_{p_i}(x)$ 的取值范围,或者报告无解。

CF1114C Trailing Loves (or L’oeufs?)

把 $k$ 分解为 $\prod_{i=1}^m p_i^{e_i}$,设 $E_i$ 为 $p_i$ 在 $N$ 中的幂次。

则 $N$ 在 $k$ 进制下后导 $0$ 的个数就是 $\min\Big(\Big\lfloor \frac{E_i}{e_i} \Big\rfloor\Big)$。

对于每个 $p_i$,$n!$ 中质因子 $p_i$ 的个数为$$\sum_{k=1 \wedge p_i^k \le n} \Big\lfloor \frac{n}{p_i^k} \Big\rfloor$$解释一下上式。

$$
\begin{aligned}
\frac{1}{x} + \frac{1}{y} &= \frac{1}{n!}
\\
\frac{x+y}{xy} &=\frac{1}{n!}
\\
xn! + yn! &= xy
\\
xy – xn! -yn! + (n!)^2 &= (n!)^2
\\
(x-n!)(y-n!) &= (n!)^2
\end{aligned}
$$

$x$ 与 $y$ 一一对应。

用上面的办法求出 $(n!)^2$ 的质因子指数,再求 $\sigma_0\Big((n!)^2\Big)$ 即可。

$\texttt{Bonus:}$ 求出所有 $(x+y)$ 的和。

求出 $\sigma_1\Big((n!)^2\Big)$ 即可。

luogu1069 [NOIP2009 普及组] 细胞分裂

先把 $m_1$ 分解了,对于 $m_1$ 的一个质因数 $p_j$,如果 $s_i$ 中不存在 $p_j$ 则无解。

否则,设 $p_j$ 在 $s_i$ 中的指数为 $E_j$,则 $x$ 至少为 $\Big\lceil \frac{e_j \times m_2}{E_j} \Big\rceil$。

取最大值即可。

CF1325E Ehab’s REAL Number Theory Problem

每个数的约数个数不超过 $7$,也就是每个数最多有 $2$ 个质因数。

首先我们判一下完全平方数。然后我们就可以不考虑次数为偶数的质因子,所以所有数一共可以分成两类。

  1. 单个质数 $p$。
  2. 两个质数 $p,q$ 的乘积 $pq$。

用这些数的乘积得到完全平方数,那么每一种质数应该都出现偶数次。

考虑经典的图论建模问题。我们把出现过的质数当作点,能表示为 $pq$ 的点,看作 $p$ 与 $q$ 之间连一条无向边。另外还要额外建立一个点 $1$,对于单个质数 $p$,在 $1$ 和 $p$ 之间连一条无向边。

这样,任何合法解都是图中的一个环。问题转化为求这张图的最小环。

由于边权都为 $1$,我们直接使用 $\text{BFS}$ 找环。

$\text{BFS}$ 树上的每一条返祖边都对应着一个环。枚举起点,如果这个点在环里,那么找到的第一条返祖边就是它所在的最小环,并且两个端点到它的距离再加 $1$ 就是环长。

如果一个点不在环里,那么如果有环,以它为起点搜到的环一定比答案更大;否则以它为起点一定不能搜到环。所以这样做不会有问题。

然而复杂度约是 $O\Big(n \frac{n}{\ln n}\Big)$,无法通过。

注意到每条边至少有一个点小于等于 $\sqrt{\max {a_i}}$,只枚举这部分点作为起点即可。复杂度 $O\Big(n \sqrt{\max {a_i}}\Big)$。

CF582A GCD Table

一个重要的性质是 $\gcd(a,b) \le \min(a,b)$。

表中最大的数、次大的数一定都是原序列中最大和次大的数,但是其他的就不一定了。

可以这样做。找到表中最大的数 $k$,它一定是序列元素。然后删掉 $k$,在序列中加入 $k$,然后扫一遍序列中的其他元素 $j$,在表中删掉两个 $\gcd(k,j)$。

使用std::set可以做到 $O(n^2 \log_2 n^2)$。

CF1344A Hilbert’s Hotel

注意到原本位置距离为 $tn, t \in \mathbb{Z}$ 的点,在移动后仍然会距离 $tn$,所以就可以只考虑 $[0,n-1]$ 的点。

然后 check 每个 $(i+a_i) \bmod n$ 是否唯一即可。

注意要把 $i+a_i$ 可能小于 $0$。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define PII pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define SET(a,b) memset(a,b,sizeof(a))
#define CPY(a,b) memcpy(a,b,sizeof(b))
#define rep(i,j,k) for(int i=(j);i<=(k);++i)
#define per(i,j,k) for(int i=(j);i>=(k);--i)
int read() {
    int a=0, f=1; char c=getchar();
    while(!isdigit(c)) {
        if(c=='-') f=-1;
        c=getchar();
    }
    while(isdigit(c)) a=a*10+c-'0', c=getchar();
    return a*f;
}
const int N=2e5+5;
int T, n, a[N];
unordered_map<int,int> p;
int r(int x) {
    return ((x+a[x])%n+n)%n;
} 
void solve() {

    n=read();
    p.clear();
    rep(i,0,n-1) a[i]=read(), ++p[r(i)];
    int fg=0;
    rep(i,0,n-1) {
        if(p[r(i)]>1) { fg=1; break; }
    }
    if(fg) puts("NO");
    else puts("YES"); 
}
signed main() {
    T=read();
    while(T--) solve();
    return 0;
}

CF1342C Yet Another Counting Problem

容易看出这个是有循环节的,周期为 $\operatorname{lcm}(a,b)$。

然后 $a,b$ 都很小,可以直接暴力处理每个周期内的信息,最后即可 $O(1)$ 回答询问。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define PII pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define SET(a,b) memset(a,b,sizeof(a))
#define CPY(a,b) memcpy(a,b,sizeof(b))
#define rep(i,j,k) for(int i=(j);i<=(k);++i)
#define per(i,j,k) for(int i=(j);i>=(k);--i)
int read() {
    int a=0, f=1; char c=getchar();
    while(!isdigit(c)) {
        if(c=='-') f=-1;
        c=getchar();
    }
    while(isdigit(c)) a=a*10+c-'0', c=getchar();
    return a*f;
}
int T, a, b, q, c, d;
int s[50000];
int calc(int x) {
    int t=x/c;
    return t*s[c]+s[x-t*c];
}
int lcm(int x,int y) { return x/__gcd(x,y)*y; }
void solve() {
    a=read(), b=read(), q=read();
    c=lcm(a,b);
    rep(i,1,c) s[i]=s[i-1]+(i%a%b!=i%b%a);
    while(q--) {
        int l=read(), r=read();
        printf("%lld ",calc(r)-calc(l-1));
    }
    puts("");
}
signed main() {
    T=read();
    while(T--) solve();
    return 0;
}

luogu1477 [NOI2008] 假面舞会

考虑如果 $a$ 能看见 $b$,那么从 $a$ 到 $b$ 连边。

先从简单的图上开始分析。

考虑在 DAG 上的情况,发现还是不太容易确定。

Part1

不妨先只考虑有向链,这个很简单,最大就是链长(超过 $3$ 的话),最小是 $3$。然而当我们把若干形态的链拼成一张 DAG 时,则会出现一个点到达另一个点的路径不止一条,从而导致很诡异的事情,似乎不太容易找到最大值了。然后我们能发现要是不存在这种情况,也就是 DAG 是个有向树,那么答案就是最长链。

可以发现,链这种结构不会使原本合法的 $k$ 变小。

观察这种情况,设较长链长度为 $l_1$,较短链长度为 $l_2$,那么能发现答案就是 $l_1-l_2$,更确切地说,合法环长是 $l_1-l_2$ 的约数。那么根据上一段的结论,最大值是所有 $l_1-l_2$ 的 $\gcd$。

如何找到这样的情况,是我们亟待解决的第一个问题。

Part2

然后加入对环的讨论。设一个简单环环长为 $len$,那么 $len$ 必须是 $k$ 的倍数。不难想到 $k$ 最大能取所有环长的 $\gcd$。同时对于链来说 $k$ 是啥都无所谓,因此此时最大值就是环长 $\gcd$,最小值取 $\gcd$ 大于 $3$ 的约数即可。然而这还是简单环,如何解决有公共边的环,这是我们亟待解决的第二个问题。

Part3

下面不加推导地给出解决两个问题的办法:对于关系 $(a,b)$,从 $a$ 向 $b$ 连权值为 $1$ 的边,从 $b$ 向 $a$ 连权值为 $-1$​ 的边。直接钦定一个连通块中的节点为 $0$,然后顺着边权求出每个点的权值。

  • 每个连通块的最大权值减掉最小权值再加上 $1$ 就是最长链长度。
  • 重复访问到一个节点时,将两个值做差得到环的权值,取 $\gcd$ 即可。

Part4

首要明确连完反边之后就成了无向图,图中的每一个环,都对应着上述结构中的一个,即到一个点的两条路径、有或无公共边的环。

对于第一个问题,在无向图上搜完一圈回来得到的就是 $l_1-l_2$。

对于第二个问题,取公共部分的末端为起点,公共部分的始端为重点,这就是第一个问题的情况又复合上了一条链,而链是不会对种类数产生限制的。因此,第二个问题规约到了第一个问题上。

设较长环长度为 $x$,较短环长度为 $y$,二者公共部分长度为 $z$。那么这个东西所对应的最大值是 $(x-z)-(y-z) = x-y$。这个结构的贡献是 $\gcd(x,y)$。

假设已经搜完了较长环,进入环的时机以及走的路径不同,会导致上述做法得到的较短环的权值也不同。但是塔可以保证这个权值只会是 $y$ 或 $x-y$,并且 $\gcd(x,y) = \gcd(x,x-y)$,所以是对的。

具体证明设计大量分类讨论,此处不予展开。

比较抽象,可以对着图理解。

CODE

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define PII pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define SET(a,b) memset(a,b,sizeof(a))
#define CPY(a,b) memcpy(a,b,sizeof(b))
#define rep(i,j,k) for(int i=(j);i<=(k);++i)
#define per(i,j,k) for(int i=(j);i>=(k);--i)
int read() {
    int a=0, f=1; char c=getchar();
    while(!isdigit(c)) {
        if(c=='-') f=-1;
        c=getchar();
    }
    while(isdigit(c)) a=a*10+c-'0', c=getchar();
    return a*f;
}
const int N=1e5+5, M=2e6+5;
int n, m, t, mx, mn, ans, dis[N];
bool v[N];
int tot, h[N], to[M], w[M], nxt[M];
void add(int x,int y,int z) {
    to[++tot]=y, w[tot]=z, nxt[tot]=h[x], h[x]=tot;
}
int gcd(int x,int y) { return y? gcd(y,x%y):x; }
void dfs(int x,int fa,int k) {
    if(v[x]) {
        ans=gcd(ans,abs(dis[x]-k));
        return;
    }
    v[x]=1, dis[x]=k;
    mx=max(mx,k), mn=min(mn,k);
    for(int i=h[x];i;i=nxt[i]) {
        int y=to[i], z=w[i];
        if(y!=fa) dfs(y,x,k+z);
        // 判一下不要往回搜是因为二元环是一种很没用的东西
        // 搜不搜反正都是和1取gcd
    }
}
signed main() {
    n=read(), m=read();
    rep(i,1,m) {
        int x=read(), y=read();
        add(x,y,1);
        add(y,x,-1);
    }
    rep(i,1,n) if(!v[i]) {
        mx=-1e9, mn=1e9;
        dfs(i,0,1);
        t+=mx-mn+1;

    }
    int ans2=0;
    if(!ans) ans=t, ans2=3;
    else {
        ans2=3;
        while(ans2<ans&&ans%ans2) ++ans2;
    }
    if(ans<3) puts("-1 -1");
    else printf("%lld %lld\n",ans,ans2);
    return 0;
}

CF980D Perfect Groups

该上那个经典套路了。。

乘积为完全平方数具有传递性,因此可以轻易划分成若干唯一确定的不相交集合。

题目要求最小化分组的数量,那么就要保证每个集合都是极大的。

枚举起点,每考虑一个数都贪心把它加入它所对应的集合,没有就新开一个。

应该注意的是, $0$ 可以放到任何一个集合中,我们钦定所有 $0$ 都放到第一个集合里。所以,只有以下两种情况才需要新开集合。

  1. 序列中没有元素。
  2. $a_i \neq 0$,$a_i$ 所在集合还没有元素并且当前序列中不是全 $0$。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define SET(a,b) memset(a,b,sizeof(a))
#define rep(i,j,k) for(int i=(j);i<=(k);++i)
#define per(i,j,k) for(int i=(j);i>=(k);--i)
int read() {
    int a=0, f=1; char c=getchar();
    while(!isdigit(c)) {
        if(c=='-') f=-1;
        c=getchar();
    }
    while(isdigit(c)) a=a*10+c-'0', c=getchar();
    return a*f;
}
const int N=5005;
int n, k, a[N], l[N], ans[N];
int squ(int x,int y) {
    int t=(int)sqrt(x*y);
    return x*y==t*t;
}
signed main() {
    n=read();
    rep(i,1,n) a[i]=read();
    rep(i,1,n) {
        per(j,i-1,1) if(a[i]*a[j]>0&&squ(a[i],a[j])) { l[i]=j; break; }
    }
    rep(i,1,n) {
        int k=0, zero=0;
        rep(j,i,n) {
            if(i==j||(a[j]!=0&&zero&&l[j]<i)) ++k;
            ++ans[k];
            zero|=a[j]!=0;
        }
    }
    rep(i,1,n) printf("%lld ",ans[i]);
    puts("");
}

CF354C Vasya and Beautiful Arrays

考虑直接枚举答案。容易发现一个答案 $d$ 是整个序列的公约数,当且仅当 ${\forall} i \in [1,n], a_i \bmod d \le k$。

然而貌似无法优化了。

注意到答案不会超过 $mn = \min_{i=1}^n {a_i}$。

如果 $mn \le k+1$,那么 $a_i \bmod mn \le k$。

如果 $mn> k+1$,那么令答案为 $k+1$,显然都可以满足。因此答案区间为 $[k+1,mn]$。

考虑如果一个 $d$ 能成为答案,每个 $a_i$ 一定都能写成 $tk+r$,其中 $r \in[0,k]$。

注意到值域不大,可以开一个桶,在桶上做前缀和,然后枚举 $t$,对每个区间求和,最后检查是否等于 $n$ 即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define PII pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define SET(a,b) memset(a,b,sizeof(a))
#define CPY(a,b) memcpy(a,b,sizeof(b))
#define rep(i,j,k) for(int i=(j);i<=(k);++i)
#define per(i,j,k) for(int i=(j);i>=(k);--i)
int read() {
    int a=0, f=1; char c=getchar();
    while(!isdigit(c)) {
        if(c=='-') f=-1;
        c=getchar();
    }
    while(isdigit(c)) a=a*10+c-'0', c=getchar();
    return a*f;
}
const int N=3e5+5, M=1e6+5, lim=1e6;
int n, k, mx, mn=1e9, a[N], c[M];
signed main() {
    n=read(), k=read();
    rep(i,1,n) {
        a[i]=read();
        mx=max(mx,a[i]);
        mn=min(mn,a[i]);
        ++c[a[i]];
    }
    if(mn<=k+1) { printf("%lld\n",mn); return 0; }
    for(int i=1;i<=min(mx+k,lim);++i) c[i]+=c[i-1];
    per(d,mn,k+1) {
        int cnt=0;
        for(int i=1;i<=mx/d;++i) {
            cnt+=c[min(i*d+k,lim)]-c[i*d-1];
        }
        if(cnt==n) { printf("%lld\n",d); return 0; }
    }    
    return 0;
}

CF1114F Please, another Queries on Array?

不要忘了欧拉函数最原始的式子

$$
\varphi(n) = n \prod_{i=1}^m \left(1-\frac{1}{p_i} \right)
$$

这个式子的优点在于只和 $n$ 以及它的质因子有关,并且是个积式。

也就是说,对于一个区间 $[l,r]$,我们只需要知道区间积以及区间出现过的质因子集合即可。

而值域小得令人发指,并且对于乘法操作,能增加的质因子也不超过 $300$。也就是说可能出现的质因子只有 $62$ 个,正好能用long long状压。

用线段树维护区间乘积和区间质因子集合即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define PII pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define SET(a,b) memset(a,b,sizeof(a))
#define CPY(a,b) memcpy(a,b,sizeof(b))
#define rep(i,j,k) for(int i=(j);i<=(k);++i)
#define per(i,j,k) for(int i=(j);i>=(k);--i)
int read() {
    int a=0, f=1; char c=getchar();
    while(!isdigit(c)) {
        if(c=='-') f=-1;
        c=getchar();
    }
    while(isdigit(c)) a=a*10+c-'0', c=getchar();
    return a*f;
}
const int N=4e5+5, mod=1e9+7;
int n, q, a[N];
int cnt, v[N];
int p[N], inv[N], rev[N];
int t[N<<2], tag[N<<2], s[N<<2], stag[N<<2];
void init() {
    inv[0]=inv[1]=1;
    for(int i=2;i<=300;++i) {
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
        if(!v[i]) {
            p[cnt]=i, rev[cnt]=(i-1)*inv[i]%mod, ++cnt;
            for(int j=i*i;j<=300;j+=i) v[j]=1;
        }
    }
}
char ss[14];
int fp(int a,int b) {
    int c=1;
    for(;b;a=a*a%mod,b>>=1) if(b&1) c=c*a%mod;
    return c;
}
void pushup(int x) {
    t[x]=t[x<<1]*t[x<<1|1]%mod;
    s[x]=s[x<<1]|s[x<<1|1];
}
void maketag(int x,int len,int d,int s0) {
    (t[x]*=fp(d,len))%=mod;
    (tag[x]*=d)%=mod;
    s[x]|=s0;
    stag[x]|=s0;
}
void pushdown(int x,int l,int r) {
    if(tag[x]>1||stag[x]) {
        int mid=(l+r)>>1;
        maketag(x<<1,mid-l+1,tag[x],stag[x]);
        maketag(x<<1|1,r-mid,tag[x],stag[x]);
        tag[x]=1, stag[x]=0;
    }
}
void build(int x=1,int l=1,int r=n) {
    tag[x]=t[x]=1;
    if(l==r)  {
        t[x]=a[l];
        for(int i=0;i<cnt;++i) if(a[l]%p[i]==0) s[x]|=1ll<<i;
        return;
    }
    int mid=(l+r)>>1;
    build(x<<1,l,mid);
    build(x<<1|1,mid+1,r);
    pushup(x);
}
void upd(int L,int R,int d,int s0,int x=1,int l=1,int r=n) {
    if(L<=l&&r<=R) { maketag(x,r-l+1,d,s0); return; }
    pushdown(x,l,r);
    int mid=(l+r)>>1;
    if(L<=mid) upd(L,R,d,s0,x<<1,l,mid);
    if(R>mid) upd(L,R,d,s0,x<<1|1,mid+1,r);
    pushup(x);
}
PII query(int L,int R,int x=1,int l=1,int r=n) {
    if(L<=l&&r<=R) {
        return MP(t[x],s[x]);
    }
    pushdown(x,l,r);
    int mid=(l+r)>>1;
    PII res; res.fi=1, res.se=0;
    if(L<=mid) {
        PII tmp=query(L,R,x<<1,l,mid);
        (res.fi*=tmp.fi)%=mod, res.se|=tmp.se;
    }
    if(R>mid) {
        PII tmp=query(L,R,x<<1|1,mid+1,r);
        (res.fi*=tmp.fi)%=mod, res.se|=tmp.se;
    }
    return res;
} 
signed main() {
    n=read(), q=read();
    rep(i,1,n) a[i]=read();
    init();
    build();
    while(q--) {
        scanf("%s",ss);
        if(ss[0]=='M') {
            int l=read(), r=read(), x=read();
            int s0=0;
            for(int i=0;i<cnt;++i) if(x%p[i]==0) s0|=1ll<<i;
            upd(l,r,x,s0);
        } else {
            int l=read(), r=read();
            PII a=query(l,r);
            int ans=a.fi, S=a.se;
            for(int i=0;i<cnt;++i) if((S>>i)&1) (ans*=rev[i])%=mod;
            printf("%lld\n",ans);
        }
    }
    return 0;
}

CF632B Array GCD

首先将「整个序列的 $\gcd$ 大于 $1$」,转化成「整个序列存在公共质因子」。

一个重要的观察:任何一组合法解,整个序列的 $\gcd$ 一定是 $a_1$ 或 $a_n$ 的某个质因子的倍数。

然后 $\omega(a_i)$ 的最大值大概是 $10$ 的样子,所以可以枚举每个质因子。

能发现唯一的影响就是删掉的那一段。

设 $f(i,0/1/2)$ 为考虑前 $i$ 个数,删掉的段在位置 $i$ 还没开始、开始了没结束、结束了的最小代价。

直接做就行了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define PII pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define SET(a,b) memset(a,b,sizeof(a))
#define CPY(a,b) memcpy(a,b,sizeof(b))
#define rep(i,j,k) for(int i=(j);i<=(k);++i)
#define per(i,j,k) for(int i=(j);i>=(k);--i)
int read() {
    int a=0, f=1; char c=getchar();
    while(!isdigit(c)) {
        if(c=='-') f=-1;
        c=getchar();
    }
    while(isdigit(c)) a=a*10+c-'0', c=getchar();
    return a*f;
}
const int N=1e6+5, inf=2e18;
int n, ans, A, B, a[N][3];
vector<int> tmp, v;
void divide(int x) {
    for(int i=2;i*i<=x;++i) if(x%i==0) {
        tmp.pb(i);
        while(x%i==0) x/=i;
    }
    if(x>1) tmp.pb(x);
}
void prework() {
    rep(i,0,2) divide(a[1][i]), divide(a[n][i]);
    sort(tmp.begin(),tmp.end());
    int p=unique(tmp.begin(),tmp.end())-tmp.begin();
    for(int i=0;i<p;++i) v.pb(tmp[i]);
}
int f[N][3];
int solve(int x) {
    SET(f,0x3f);
    f[0][0]=0;
    rep(i,1,n+1) {
        if(i==n+1) {
            f[i][0]=f[i-1][0];
            f[i][2]=min(f[i-1][1],f[i-1][2]);
            f[i][1]=inf;
            continue;
        }
        if(a[i][1]%x==0) {
            f[i][0]=f[i-1][0];
            f[i][1]=min(f[i-1][0],f[i-1][1])+A;
            f[i][2]=min(f[i-1][1],f[i-1][2]);
        } else if(a[i][0]%x==0||a[i][2]%x==0) {
            f[i][0]=f[i-1][0]+B;
            f[i][1]=min(f[i-1][0],f[i-1][1])+A;
            f[i][2]=min(f[i-1][1],f[i-1][2])+B;
        } else {
            f[i][0]=inf;
            f[i][2]=inf;
            f[i][1]=min(f[i-1][0],f[i-1][1])+A;
        }
    }
    return min(f[n+1][0],f[n+1][2]);
}
signed main() {
    n=read(), A=read(), B=read();
    rep(i,1,n) {
        a[i][1]=read();
        a[i][0]=a[i][1]-1;
        a[i][2]=a[i][1]+1;
    }
    prework();
    ans=inf;
    for(auto x:v) {
        int res=solve(x);
        ans=min(ans,res);
    }
    printf("%lld\n",ans);
    return 0;
}

求区间中与给定数互质的数的个数

最后我们关注一个小问题。

给定 $n,L,R$,求 $[L,R]$ 中与 $n$ 互质的数的个数。

怎么做?

问题等价于求 $[L,R]$ 中有多少个数和 $n$ 含有相同质因子,先差分成 $[1,L-1]$ 和 $[1,R]$。

设当前区间为 $[1,R]$。我们把 $n$ 分解了,暴力搜索所有质因数的组合方式,设其为 $m$,那么就有 $\Big\lfloor \frac{R}{m} \Big\rfloor$ 个数含有这个质因子集合。根据熟悉的集合容斥,容易知道集合 $S$ 的容斥系数就是 $(-1)^{|S|}$。

设质因子个数为 $\omega(n)$,那么复杂度就是 $O(\sqrt{n} + 2^{\omega(n)})$。

通过提前筛质数能做到 $O(\omega(n) + 2^{\omega(n)})$,实际上第一项常数较小,第二项小于 $O(n)$。

CF1750D Count GCD

有解一定要有 $a_i \mid a_{i-1}$。

考虑 $\gcd_{j=1}^{i-1} {b_j} = a_{i-1}$,$\gcd_{j=1}^{i} {b_j} = a_{i}$,也就是说 $b_i$ 不能有 $\frac{a_{i-1}}{a_i}$ 的任何质因子。

由于 $a_i \mid b_i$,所以 $\frac{b_i}{a_i} \in [1,\lfloor \frac{m}{a_i} \rfloor]$。

求区间中与 $\frac{a_{i-1}}{a_i}$ 互质的数的个数即可。

单次求解的复杂度已经到了 $O(2^{\omega(m)})$ 了,而 $\omega(m)$ 的上界大概是 $10$。

注意到那个很重要的条件 $a_i \mid a_{i-1}$,也就是这玩意是 $\log$ 级别递减的,所以直接暴力做就行。

// LUOGU_RID: 121128333
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define PII pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define SET(a,b) memset(a,b,sizeof(a))
#define CPY(a,b) memcpy(a,b,sizeof(b))
#define rep(i,j,k) for(int i=(j);i<=(k);++i)
#define per(i,j,k) for(int i=(j);i>=(k);--i)
int read() {
    int a=0, f=1; char c=getchar();
    while(!isdigit(c)) {
        if(c=='-') f=-1;
        c=getchar();
    }
    while(isdigit(c)) a=a*10+c-'0', c=getchar();
    return a*f;
}
const int N=2e5+5, mod=998244353;
int T, n, m, a[N];
vector<int> p;
map<PII,int> mp;
void divide(int x) {
    p.clear();
    for(int i=2;i*i<=x;++i) if(x%i==0) {
        p.pb(i);
        while(x%i==0) x/=i;
    }
    if(x>1) p.pb(x);
}
int dfs(int i,int j,int k,int lim) {
    if(i==p.size()) {
        if(j>0) {
            if(j&1) return lim/k;
            else return -lim/k;
        } else return 0;
    }
    return dfs(i+1,j,k,lim)+dfs(i+1,j+1,k*p[i],lim);
}
int calc(int lim,int r) {
    PII t={lim,r};
    if(mp.count(t)) return mp[t];
    divide(r);
    return mp[t]=lim-dfs(0,0,1,lim);
}
void solve() {
    n=read(), m=read();
    int fg=0;
    rep(i,1,n) {
        a[i]=read();
        if(i>1&&a[i-1]%a[i]) fg=1;
    }
    if(fg) {
        puts("0");
        return;
    }
    int ans=1;
    rep(i,2,n) {
        (ans*=calc(m/a[i],a[i-1]/a[i]))%=mod;
    }
    printf("%lld\n",ans);
}
signed main() {
    T=read();
    while(T--) solve();    
    return 0;
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇