LOJ508 [LibreOJ NOI Round #1] 失控的未来交通工具

若两点间的路径上出现了环,则该环可以贡献给答案,因为可以从起点到环上一点来回跑 \(m\) 次,然后再跑环的贡献。

因为每个环都可以起任意倍数的贡献,所以由裴蜀定理得,所有环的总贡献为所有环的权值和的 \(\gcd\) 的任意倍数,图是无向图,因此每条边都可以看作是一个环。

可以用带权并查集维护出每个连通块中所有环的 \(\gcd\) 和每个点到该并查集的根的距离,维护距离是为了知道两点间某条路径的权值和,因为每条边都加入到了环的贡献,所以该路径可以任选。

考虑如何解决 \(x+kb,k \in [0,c-1]\) 中权值出现个数。设环的贡献为 \(g\),两点的某条路径的权值和为 \(dis\),得:

\[\large x+kb \equiv dis \pmod{g} \]

然后可以用扩展欧几里得算法求得最小的 \(k\),每隔 \(\frac{g}{\gcd(b,g)}\) 就出现一个合法权值。

#include<bits/stdc++.h>
#define maxn 1000010
using namespace std;
typedef long long ll;
template<typename T> inline void read(T &x)
{
    x=0;char c=getchar();bool flag=false;
    while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    if(flag)x=-x;
}
int n,m,q;
int fa[maxn],g[maxn];
ll d[maxn];
int find(int x)
{
    if(x==fa[x]) return x;
    int anc=find(fa[x]);
    d[x]=(d[x]+d[fa[x]])%m;
    return fa[x]=anc;
}
int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}
void exgcd(int a,int b,int &x,int &y)
{
    if(!b) x=1,y=0;
    else exgcd(b,a%b,y,x),y-=a/b*x;
}
int main()
{
    read(n),read(m),read(q);
    for(int i=1;i<=n;++i) fa[i]=i,g[i]=m;
    while(q--)
    {
        int opt,x,y,fx,fy,w,b,c,t,A,B,C,X,Y;
        read(opt),read(x),read(y),read(w),fx=find(x),fy=find(y);
        if(opt==1)
        {
            if(fx==fy) g[fx]=gcd(g[fx],gcd((d[x]+d[y]+w)%m,2*w));
            else fa[fx]=fy,d[fx]=(d[x]+d[y]+w)%m,g[fy]=gcd(gcd(g[fx],g[fy]),2*w);
        }
        else
        {
            read(b),read(c),A=b%g[fx],B=g[fx],C=(d[x]+d[y]-w+B)%B,t=gcd(A,B);
            if(fx!=fy||C%t)
            {
                puts("0");
                continue;
            }
            A/=t,B/=t,C/=t,exgcd(A,B,X,Y),X=((ll)X*C%B+B)%B;
            printf("%lld\n",X<c?(c-1-X)/B+1:0);
        }
    }
    return 0;
}
posted @ 2020-09-24 21:50  lhm_liu  阅读(298)  评论(0编辑  收藏  举报