https://loj.ac/p/508
贼牛逼的题目。想了两天才想明白。网上大多数题解都讲得很烂啊。
对于部分分的情况,我相信是较为容易想到的。因此,我只会阐述正解的思考过程及一些证明。
首先,考虑路径这东西太泛了。能否将其特殊化、具体化。
先观察一些性质。
-
对于一个环,我们可以走若干圈,会回到出发点,但对答案是有贡献的。
那么,我们是可以走 m 圈来抵消这个环的贡献的。
-
既然如此。考虑一条无向边是可以看成两条方向相反的有向边构成的,因此可以看成一个环,也就是说,我们可以经过一条无向边,并去到其他地方,最终回到该无向边的起点,从而这条无向边只是一个桥梁的作用,并没有贡献。注意,这里因为环是绕完后回原点,因此若想该边无贡献,则一定最终是回到起点的。
-
考虑 u-v 的路径上的边,我们是一定要经过一次的。那么,这一部分必须会有贡献。
-
考虑环的贡献,显然我们可以通过引理 2 得到,一个环可以有任意系数的贡献,并且是独立于其他环或者路径的。
事已至此,是不是一定就是 u-v 的某条路径有一个贡献,再与环进行线性组合,便能得到所有路径了呢?
但问题是,要 u-v 的哪条路径?
如果不能确定的话,这个问题将非常不可做。因此,我们猜想,一定可以确定或者使用其中某一条路径而得到正确的答案。
考虑环的线性组合能表示出来的一定是 gcd(m,a1,a2,a3,...,at) 的倍数,其中 a 是环长序列,那么对于任何一条路径 L,它一定有个 2L 在 gcd 中,若 uv 路径唯一,则路径确定。若不唯一,则可以看成两条路径 L1,L2,那么这两条路径一定可以构成一个环,即 L1+L2,2L1,2L2 都在 gcd 中,然后你考虑两条路径无交的地方一定会构成环,然后你去设参数,看看能否通过走这三种长度,来实现路径的替换。你会发现是可以的。
因此,我们只要随便找一条路径即可,因为环的线性组合与这条路径结合总能生成另一条路径。
那么,现在路径是随便找的了。我们可以直接并查集大力维护。注意在合并两棵树时,加的边不一定是在树根,此时魔改一下距离即可。
那么,如何维护环长的 gcd 呢?
考虑加入 (x,y,w),如果没形成环,那么会多一个 2w。
若形成环,则多一个 2w,以及原先其若干路径 L 会形成 L+w 的环。
但因为,gcd(L1+w,L2+w)=gcd(L2+w,L1−L2),gcd(2L1,L1+L2)=gcd(L1+L2,L1−L2),你会发现,L1−L2 实际上已经加入了。所以我们只需要加入 L2+w 即可。
即随便找一条路径。这与前面我们使用并查集维护的功能是相符合的。
接下来便是 exgcd,注意有点细节。
比如 ax+by=c,先求 ax+by=gcd(a,b),得到 x,y,然后乘上 c/gcd(a,b),再去设参数得到单次增量。
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N=(int)(1e6+5);
vector<int>vec[N];
int n,m,q,fa[N],dis[N],d[N];
int gcd(int x,int y) {
return !y?x:gcd(y,x%y);
}
int fd(int x) {
return x==fa[x]?x:fd(fa[x]);
}
void Merge(int x,int y,int w) {
int fx=fd(x),fy=fd(y);
int D=dis[y]+w+dis[x];
fa[fy]=fx; d[fx]=gcd(d[fx],d[fy]);
for(int qwq:vec[fy]) dis[qwq]+=D,vec[fx].pb(qwq);
vector<int>().swap(vec[fy]);
}
void merge(int x,int y,int w) {
int fx=fd(x),fy=fd(y);
if(vec[fx].size()>vec[fy].size()) Merge(x,y,w);
else Merge(y,x,w);
}
pair<int,int> exgcd(int a,int b) {
if(!b) {
return make_pair(1,0);
}
pair<int,int> qwq=exgcd(b,a%b);
return make_pair(qwq.second,qwq.first-(int)(a/b)*qwq.second);
}
int lcm(int a,int b) {
return a/gcd(a,b)*b;
}
int sol(int a,int b,int c,int Lim) {
if(c%gcd(a,b)!=0) return 0;
int g=gcd(a,b);
pair<int,int> qwq=exgcd(a,b);
int D=b/g;
int x=qwq.first;
x=x*c/g;
x=(x%D+D)%D;
if(x>Lim) return 0;
int ans=(Lim-x)/D+1;
return ans;
}
signed main() {
cin.tie(0); ios::sync_with_stdio(false);
cin>>n>>m>>q;
for(int i=1;i<=n;i++) d[i]=m,fa[i]=i,vec[i].pb(i);
while(q--) {
int op; cin>>op;
if(op==1) {
int u,v,w; cin>>u>>v>>w;
if(fd(u)==fd(v)) {
int f=fd(u),L=dis[u]+dis[v];
d[f]=gcd(d[f],gcd(2*w,L+w));
} else {
merge(u,v,w);
d[fd(u)]=gcd(d[fd(u)],2*w);
}
} else {
int u,v,x,b,c; cin>>u>>v>>x>>b>>c;
if(fd(u)!=fd(v)) {
cout<<"0\n"; continue ;
}
int f=fd(u),L=dis[u]+dis[v],ans=0;
if(b==0) {
int qwq=(x-L)%m; qwq=(qwq+m)%m;
if(qwq%d[f]==0) cout<<c<<'\n';
else cout<<"0\n";
continue ;
}
cout<<sol(b,d[f],L-x,c-1)<<'\n';
}
}
return 0;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
2022-08-24 相同形式的多组递推式的根号求法
2022-08-24 CF1715D 2+ doors