LOJ #508. 「LibreOJ NOI Round #1」失控的未来交通工具 数论 并查集

题目链接:「LibreOJ NOI Round #1」失控的未来交通工具

题目简述

一个带边权无向图,有两种操作:加边以及询问在 x,x+b,...,x+(c1)b x , x + b , . . . , x + ( c − 1 ) b 这些数中,有多少存在一条与之模 m m 同余的从 u v v 的路径(可以不是简单路径)。

数据范围

Subtask # 分值 n,q 的限制 m m 的限制 c 的限制 附加限制 1 11 11 1n,q100 1 ≤ n , q ≤ 100 1<m100 1 < m ≤ 100 1c5 1 ≤ c ≤ 5 无 2 21 21 1n,q2×105 1 ≤ n , q ≤ 2 × 10 5 m=2 m = 2 3 13 13 m m 是质数
4 7 m m 是奇数 图中任何时刻都不会出现简单环
5 10 无 无 6 5 5 1c5 图中任何时刻不会有度数大于 2 2 的点
7 13 无 8 20 20 1n,q106 1 ≤ n , q ≤ 10 6

题解

Subtask# 1

对于一开始这个数据 n,m,q100 n , m , q ≤ 100 ,显然是个暴力。
但是这是个(丧)题,所以说模拟是肯定拿不到分的嘛对吧,于是就考虑怎么优化这个模拟啊,我们发现由于 n,m n , m 都很小,所以我们可以考虑每次把跑的记 fi,j f i , j 表示从 u u 开始,走到 i,此时 modm mod m 同余 j j 。这样一来所有的状态也就只有 nm 种,非常地科学。
查询直接 for for 一遍就可以了反正 c c 非常小。
所以复杂度就是 O(cnq(n+q))(边数也是 q q ),代码如下:
(除最后全代码以外,都省略 n,mod,q 的读入)

namespace subtask_1{
    bool f[111][111];
    struct Edge{
        int to,w;
        Edge *next;
    }*head[1111];
    inline void add(R int u,R int v,R int w){
        static Edge E[1111],*e=E;
        *e=(Edge){v,w,head[u]};
        head[u]=e++;
    }
    int cnt;
    void dfs(R int u,R int p){
        if(f[u][p])return;
        f[u][p]=1;
        for(R Edge *i=head[u];i;i=i->next)
            dfs(i->to,(p+i->w)%mod);
    }
    int main(){
        for(R int opt,u,v,w,x,b,c,ans;q--;){
            read(opt);
            if(opt==1){
                read(u);read(v);read(w);
                add(u,v,w);add(v,u,w);
            }else{
                read(u);read(v);read(x);read(b);read(c);
                memset(f,0,sizeof f);
                ++cnt;
                dfs(u,0);
                ans=0;
                for(int i=1;i<=c;++i){
                    ans+=(f[v][x]);
                    x=(x+b)%mod;
                }
                printf("%d\n",ans);
            }
        }
        return 0;
    }
}
规律

然后我们要发现一个规律,就是一个非简单路径,可以分成一个简单路径和一堆环。

Subtask# 2

m=2 m = 2 时,只需要考虑奇数偶数的情况。我们还是把路径拆成一个简单路径和一些环。
对于这个简单路径上,若是有一个点连出去了一个奇环(就是可以从这个点出发,走奇数距离回到这个节点),记为点 X X 吧。那么从 X v v 的路径若是偶数就在这个环上绕一圈,若是奇数就在环上绕两圈凑个偶数加起来就奇数了。
如果没有这样的 X 那么奇偶性就是两个点连起来的简单路径的奇偶性了。
然后用并查集维护一下联通块内是否存在奇环以及每个点到并查集根的路径长度就行了。
查询还是暴力,复杂度 O(cq+q(αn+log(w)) O ( c q + q ( α n + log ⁡ ( w ) )

namespace subtask_2{
    int id[maxn][2],fa[maxn],idcnt;
    int find(R int x){return x!=fa[x]?fa[x]=find(fa[x]):x;}
    inline void merge(int x,int y){
        R int xx=find(x),yy=find(y);
        if(xx>yy)swap(xx,yy);
        if(xx!=yy)fa[xx]=yy;
    }
    int main(){
        for(R int i=1;i<=n;++i){
            id[i][0]=++idcnt,fa[idcnt]=idcnt;
            id[i][1]=++idcnt,fa[idcnt]=idcnt;
        }
        for(R int opt,u,v,w,x,b,c,ans;q--;){
            read(opt);
            if(opt==1){
                read(u);read(v);read(w);
                merge(id[u][0],id[v][w&1]);
                merge(id[v][0],id[u][w&1]);
                merge(id[u][1],id[v][(w+1)&1]);
                merge(id[v][1],id[u][(w+1)&1]);
            }else{
                read(u);read(v);read(x);read(b);read(c);
                ans=0;
                for(;c--;x=(x+b)&1){
                    ans+=(find(id[u][0])==find(id[v][x]));
                }
                printf("%d\n",ans);
            }
        }
        return 0;
    }
}
Subtask# 3

m m 是个奇质数,就可以考虑在一条边上不断绕圈。
假如这条边为 w,则绕一圈走了 2w 2 w m m 又是奇质数,这样 gcd(2w,m)=1。然后我们就可以用费马小定理构造出 1 (mod m) ≡ 1   ( mod   m ) 的长度,自然也就可以构造出任意长度的路径了。
那么如果 w0 (mod m) w ≡ 0   ( mod   m ) 的话,显然只可以走出 0 (mod m) ≡ 0   ( mod   m ) 的路径。
所以我们还是开一个并查集维护这个并查集之中是否只存在 0 (mod m) ≡ 0   ( mod   m ) 的边就行了。
复杂度 O(q(αn+log(w)) O ( q ( α n + log ⁡ ( w ) ) 。代码:

namespace subtask_3{
    int fa[maxn];
    char f[maxn];
    int find(R int x){return x!=fa[x]?fa[x]=find(fa[x]):x;}
    inline void merge(R int x,R int y,R int w){
        R int xx=find(x),yy=find(y);
        if(xx>yy)swap(xx,yy);
        if(xx!=yy)fa[yy]=xx,f[xx]|=f[yy];
        if(w%mod)f[xx]=f[yy]=1;
    }
    int main(){
        for(R int i=1;i<=n;++i)fa[i]=i,f[i]=0;
        for(R int opt,u,v,w,x,b,c,ans;q--;){
            read(opt);
            if(opt==1){
                read(u);read(v);read(w);
                merge(u,v,w);
            }else{
                read(u);read(v);read(x);read(b);read(c);
                ans=0;
                u=find(u);v=find(v);
                if(u!=v){
                    puts("0");
                    continue;
                }
                for(;c--;x=(x+b)%mod){
                    ans+=(bool)((!x)||(f[u]));
                }
                printf("%d\n",ans);
            }
        }
        return 0;
    }
}
Subtask# 4

m m 是一个奇数的时候,如果是质数就按照 Subtask# 3 的做法,否则的话我们考虑当前有一条边长度是 w,设 gcd(m,2w)=gcd(m,w)=g gcd ( m , 2 w ) = gcd ( m , w ) = g
显然我们无论怎么走或者绕圈,对答案的贡献都一定是 g g 的倍数。推而广之,对于所有的边,显然路径的长度一定是 G=gcd(m,w1,w2,,wsiz) 的倍数。
又有对于每一条边,我们可以绕出 g g 的任意倍数,那么就肯定可以走出 G 的任意倍数。
那如何单独让一条边贡献呢?假设这条边为 (u,v) ( u ′ , v ′ ) 那么我们就跑 m m 遍路径 (u,u) 即可(m是一个奇数,所以就是跑一堆来回加一次)。
因此在序列中所有是 G G 倍数的都满足条件。
然后同样开一个并查集维护联通块边的 gcd 就行了。
复杂度与 Subtask# 3 相同,代码由于与 Subtask# 3 以及 Subtask# 5 十分类似,请读者自行理解,就不再赘述了。

Subtask# 5

这个子任务与上一个的不同之处在于 c c 变得很大了,但是我们要找的东西是不变的,就是序列中存在多少个数是 G 的倍数。
观察题目中给出的序列生成方式,这是一个等差数列(废话),我们记这个数列为 {an} { a n } 。这样一来我们如果找到了第一个 ai a i 满足 ai0 (mod G) a i ≡ 0   ( mod   G ) ,那么 ai+1b (mod G) a i + 1 ≡ b   ( mod   G ) ai+jbj (mod G) a i + j ≡ b ∗ j   ( mod   G ) ,则 ai+G0 (mod G) a i + G ≡ 0   ( mod   G ) 。即从 ai a i ai+G1 a i + G − 1 构成了一个模 G G 意义下的完全剩余系,就是这模 G 的值是存在循环的,循环节为 G G
这样一来我们就将题目化为了如何找到第一个 i 使得 ai0 (mod G) a i ≡ 0   ( mod   G ) 。由于 ai=x+bi a i = x + b ∗ i ,那么就是要求最小的 i i 使得 x+bi0 (mod G),即是求方程 bix (mod G) b ∗ i ≡ − x   ( mod   G ) 的最小正整数解 i i
这东西可以化为 bi+Gy=Gx,用扩展欧几里德算法求解即可。
复杂度的话 O(q(αn+log(w))) O ( q ( α n + log ⁡ ( w ) ) )
代码如下:(略去欧几里德算法以及扩展欧几里德算法的定义)

namespace subtask_4_and_5{
    int fa[maxn],g[maxn];
    int find(R int x){return x!=fa[x]?fa[x]=find(fa[x]):x;}
    inline void merge(R int x,R int y,R int w){
        R int xx=find(x),yy=find(y);
        if(xx>yy)swap(xx,yy);
        if(xx!=yy){
            fa[yy]=xx;
            g[xx]=_Math_orzyrt_::gcd(
                        _Math_orzyrt_::gcd(g[xx],g[yy])
                        ,w);
        }else{
            g[xx]=_Math_orzyrt_::gcd(g[xx],w);
        }
    }
    int main(){
        for(R int i=1;i<=n;++i)fa[i]=i,g[i]=mod;
        R int aa,bb,cc,dd,xx,yy;
        for(int opt,u,v,w,x,b,c,ans;q--;){
            read(opt);
            if(opt==1){
                read(u);read(v);read(w);
                merge(u,v,w%mod);
            }else{
                read(u);read(v);read(x);read(b);read(c);
                ans=0;
                u=find(u);v=find(v);
                if(u!=v){
                    puts("0");
                    continue;
                }
                aa=b%g[u];bb=g[u];cc=g[u]-x%g[u];
                dd=_Math_orzyrt_::gcd(aa,bb);
                if(cc%dd){
                    puts("0");
                    continue;
                }
                aa/=dd;bb/=dd;cc/=dd;
                _Math_orzyrt_::exgcd(aa,bb,xx,yy);
                xx=((LL)xx*cc%bb+bb)%bb;
                if(xx<c)ans=(c-1-xx)/bb+1;
                printf("%d\n",ans);
            }
        }
        return 0;
    }
}

然后Subtask# 6&7 我也不懂单独的部分分怎么做,就跳过了,因为我的分析只与 m m 有关。

正解

看我这个标题突然变大就知道是要讲正解了对吧。对于最后的这些部分分,m 都是没有限制的,就没办法表示成某个数的整数倍了,怎么办呢?
按照套路,我们把路径拆成一个简单路径和一堆环,显然对于每一个环,假如其环长为 W W ,那么就可以在环上走出 g=gcd(W,m) 的任意倍数的贡献。
怎么加入一个环呢?直接从开头到环上一点跑 m m 个来回就行了。
于是我们就可以仿照 m 为奇数时的构造方法,使得最终答案为 G=gcd(m,W1,W2,,Wsiz) G = gcd ( m , W 1 , W 2 , ⋯ , W s i z ) 的倍数加上一条简单路径长度。
于是用并查集维护连通块内的 G G 以及每个点到并查集的根路径长,回答询问用扩展欧几里得算法。
那么怎么用并查集维护联通块内环的 gcd 呢?在加入连通块间的边时肯定是直接合并的咯没啥问题;加入连通块内的边(设其为 (u,v) ( u ′ , v ′ ) ,长为 w w )时,设原来环的 gcd G G u v v ′ 某路径长为 L L ,新加入的边,则新的 G=gcd(G,2w,w+L)
复杂度 O(q(αn+log(w))) O ( q ( α n + log ⁡ ( w ) ) )

本题完整代码如下:

#include <bits/stdc++.h>
#define LL long long
#define R register
template<class TT>inline TT Max(R TT a,R TT b){return a<b?b:a;}
template<class TT>inline TT Min(R TT a,R TT b){return a<b?a:b;}
using namespace std;
char __B[1<<18],*__S=__B,*__T=__B;
#define getchar() (__S==__T&&(__T=(__S=__B)+fread(__B,1,1<<15,stdin),__S==__T)?EOF:*__S++)
template<class TT>inline void read(R TT &x){
    x=0;R bool f=0;R char ch=getchar();
    for(;ch<48||ch>57;ch=getchar())f|=(ch=='-');
    for(;ch>47&&ch<58;ch=getchar())
        x=(x<<1)+(x<<3)+(ch^48);
    (f)&&(x=-x);
}
template<class TT>
char write(R TT x){
    x<0&&(putchar('-'),x=-x);
    x>9&&(write(x/10));
    putchar(x%10+48);
}

int n,q,mod;
namespace _Math_orzyrt_{
    template<class TT>
    inline bool isprime(R TT &x){
        for(R TT i=2;i*i<=x;++i)
            if(x%i==0)return 0;
        return 1;
    }

    template<class TT>
    TT gcd(R TT a,R TT b){return b?gcd(b,a%b):a;}

    template<class TT>
    void exgcd(R TT a,R TT b,R TT &x,R TT &y){
        b?(exgcd(b,a%b,y,x),y-=a/b*x):(x=1,y=0);
    }
}
namespace subtask_1{
    bool f[111][111];
    struct Edge{
        int to,w;
        Edge *next;
    }*head[1111];
    inline void add(R int u,R int v,R int w){
        static Edge E[1111],*e=E;
        *e=(Edge){v,w,head[u]};
        head[u]=e++;
    }
    int cnt;
    void dfs(R int u,R int p){
        if(f[u][p])return;
        f[u][p]=1;
        for(R Edge *i=head[u];i;i=i->next)
            dfs(i->to,(p+i->w)%mod);
    }
    int main(){
        for(R int opt,u,v,w,x,b,c,ans;q--;){
            read(opt);
            if(opt==1){
                read(u);read(v);read(w);
                add(u,v,w);add(v,u,w);
            }else{
                read(u);read(v);read(x);read(b);read(c);
                memset(f,0,sizeof f);
                ++cnt;
                dfs(u,0);
                ans=0;
                for(int i=1;i<=c;++i){
                    ans+=(f[v][x]);
                    x=(x+b)%mod;
                }
                printf("%d\n",ans);
            }
        }
        return 0;
    }
}
const int maxn = 1e6;
namespace subtask_2{
    int id[maxn][2],fa[maxn],idcnt;
    int find(R int x){return x!=fa[x]?fa[x]=find(fa[x]):x;}
    inline void merge(int x,int y){
        R int xx=find(x),yy=find(y);
        if(xx>yy)swap(xx,yy);
        if(xx!=yy)fa[xx]=yy;
    }
    int main(){
        for(R int i=1;i<=n;++i){
            id[i][0]=++idcnt,fa[idcnt]=idcnt;
            id[i][1]=++idcnt,fa[idcnt]=idcnt;
        }
        for(R int opt,u,v,w,x,b,c,ans;q--;){
            read(opt);
            if(opt==1){
                read(u);read(v);read(w);
                merge(id[u][0],id[v][w&1]);
                merge(id[v][0],id[u][w&1]);
                merge(id[u][1],id[v][(w+1)&1]);
                merge(id[v][1],id[u][(w+1)&1]);
            }else{
                read(u);read(v);read(x);read(b);read(c);
                ans=0;
                for(;c--;x=(x+b)&1){
                    ans+=(find(id[u][0])==find(id[v][x]));
                }
                printf("%d\n",ans);
            }
        }
        return 0;
    }
}
namespace subtask_3{
    int fa[maxn];
    char f[maxn];
    int find(R int x){return x!=fa[x]?fa[x]=find(fa[x]):x;}
    inline void merge(R int x,R int y,R int w){
        R int xx=find(x),yy=find(y);
        if(xx>yy)swap(xx,yy);
        if(xx!=yy)fa[yy]=xx,f[xx]|=f[yy];
        if(w%mod)f[xx]=f[yy]=1;
    }
    int main(){
        for(R int i=1;i<=n;++i)fa[i]=i,f[i]=0;
        for(R int opt,u,v,w,x,b,c,ans;q--;){
            read(opt);
            if(opt==1){
                read(u);read(v);read(w);
                merge(u,v,w);
            }else{
                read(u);read(v);read(x);read(b);read(c);
                ans=0;
                u=find(u);v=find(v);
                if(u!=v){
                    puts("0");
                    continue;
                }
                for(;c--;x=(x+b)%mod){
                    ans+=(bool)((!x)||(f[u]));
                }
                printf("%d\n",ans);
            }
        }
        return 0;
    }
}
namespace subtask_4_and_5{
    int fa[maxn],g[maxn];
    int find(R int x){return x!=fa[x]?fa[x]=find(fa[x]):x;}
    inline void merge(R int x,R int y,R int w){
        R int xx=find(x),yy=find(y);
        if(xx>yy)swap(xx,yy);
        if(xx!=yy){
            fa[yy]=xx;
            g[xx]=_Math_orzyrt_::gcd(
                        _Math_orzyrt_::gcd(g[xx],g[yy])
                        ,w);
        }else{
            g[xx]=_Math_orzyrt_::gcd(g[xx],w);
        }
    }
    int main(){
        for(R int i=1;i<=n;++i)fa[i]=i,g[i]=mod;
        R int aa,bb,cc,dd,xx,yy;
        for(int opt,u,v,w,x,b,c,ans;q--;){
            read(opt);
            if(opt==1){
                read(u);read(v);read(w);
                merge(u,v,w%mod);
            }else{
                read(u);read(v);read(x);read(b);read(c);
                ans=0;
                u=find(u);v=find(v);
                if(u!=v){
                    puts("0");
                    continue;
                }
                aa=b%g[u];bb=g[u];cc=g[u]-x%g[u];
                dd=_Math_orzyrt_::gcd(aa,bb);
                if(cc%dd){
                    puts("0");
                    continue;
                }
                aa/=dd;bb/=dd;cc/=dd;
                _Math_orzyrt_::exgcd(aa,bb,xx,yy);
                xx=((LL)xx*cc%bb+bb)%bb;
                if(xx<c)ans=(c-1-xx)/bb+1;
                printf("%d\n",ans);
            }
        }
        return 0;
    }
}
namespace final{
    int fa[maxn],g[maxn],d[maxn];
    int find(R int x){
        if(x!=fa[x]){
            R int f=fa[x];
            fa[x]=find(fa[x]);
            d[x]=(d[x]+d[f])%mod;
        }
        return fa[x];
    }
    inline void merge(R int x,R int y,R int w){
        R int xx=find(x),yy=find(y);
        if(xx>yy)swap(xx,yy);
        if(xx!=yy){
            fa[yy]=xx;
            d[yy]=((unsigned)d[x]+d[y]+w)%mod;
            g[xx]=_Math_orzyrt_::gcd(
                        _Math_orzyrt_::gcd(g[xx],g[yy])
                        ,w<<1);
        }else{
            g[xx]=_Math_orzyrt_::gcd(
                        _Math_orzyrt_::gcd((unsigned)w<<1,(unsigned)d[x]+d[y]+w)
                        ,(unsigned)g[xx]);
        }
    }
    int main(){
        for(R int i=1;i<=n;++i)fa[i]=i,g[i]=mod,d[i]=0;
        R int aa,bb,cc,dd,xx,yy;
        for(R int opt,u,v,uu,vv,w,x,b,c,ans;q--;){
            read(opt);
            if(opt==1){
                read(u);read(v);read(w);
                merge(u,v,w%mod);
            }else{
                read(u);read(v);read(x);read(b);read(c);
                ans=0;
                uu=find(u);vv=find(v);
                if(uu!=vv){
                    puts("0");
                    continue;
                }
                aa=b%g[uu];bb=g[uu];cc=d[u]+d[v]-x;
                dd=_Math_orzyrt_::gcd(aa,bb);
                if(cc%dd){
                    puts("0");
                    continue;
                }
                aa/=dd;bb/=dd;cc/=dd;
                _Math_orzyrt_::exgcd(aa,bb,xx,yy);
                xx=((LL)xx*cc%bb+bb)%bb;
                if(xx<c)ans=(c-1-xx)/bb+1;
                write(ans);
                putchar('\n');
            }
        }
        return 0;
    }
}
int main(){
    read(n);read(mod);read(q);
    if(mod==2)return subtask_2::main();
    if(n<111&&111>q)return subtask_1::main();
    if(mod==2)return subtask_2::main();
    if(_Math_orzyrt_::isprime(mod))return subtask_3::main();
    if(mod&1)return subtask_4_and_5::main();
    return final::main();
}
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值