loj#508. 「LibreOJ NOI Round #1」失控的未来交通工具

https://loj.ac/p/508

贼牛逼的题目。想了两天才想明白。网上大多数题解都讲得很烂啊。

对于部分分的情况,我相信是较为容易想到的。因此,我只会阐述正解的思考过程及一些证明。

首先,考虑路径这东西太泛了。能否将其特殊化、具体化。

先观察一些性质。

  1. 对于一个环,我们可以走若干圈,会回到出发点,但对答案是有贡献的。
    那么,我们是可以走 m 圈来抵消这个环的贡献的。

  2. 既然如此。考虑一条无向边是可以看成两条方向相反的有向边构成的,因此可以看成一个环,也就是说,我们可以经过一条无向边,并去到其他地方,最终回到该无向边的起点,从而这条无向边只是一个桥梁的作用,并没有贡献。注意,这里因为环是绕完后回原点,因此若想该边无贡献,则一定最终是回到起点的。

  3. 考虑 u-v 的路径上的边,我们是一定要经过一次的。那么,这一部分必须会有贡献。

  4. 考虑环的贡献,显然我们可以通过引理 2 得到,一个环可以有任意系数的贡献,并且是独立于其他环或者路径的。

事已至此,是不是一定就是 u-v 的某条路径有一个贡献,再与环进行线性组合,便能得到所有路径了呢?

但问题是,要 u-v 的哪条路径?

如果不能确定的话,这个问题将非常不可做。因此,我们猜想,一定可以确定或者使用其中某一条路径而得到正确的答案。

考虑环的线性组合能表示出来的一定是 gcd(m,a1,a2,a3,...,at) 的倍数,其中 a 是环长序列,那么对于任何一条路径 L,它一定有个 2Lgcd 中,若 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,L1L2),gcd(2L1,L1+L2)=gcd(L1+L2,L1L2),你会发现,L1L2 实际上已经加入了。所以我们只需要加入 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) { //把 y 放到 x 上 int fx=fd(x),fy=fd(y); int D=dis[y]+w+dis[x]; fa[fy]=fx; d[fx]=gcd(d[fx],d[fy]); // if(vec[fy].size()>vec[fx].size()) vec[fy].swap(vec[fx]); 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) { // int ans=0; // for(int i=0;i<=Lim;i++) { // int qwq=c-a*i; // if(qwq%b==0) ++ans; // } return ans; //} int sol(int a,int b,int c,int Lim) { if(c%gcd(a,b)!=0) return 0; // cout<<a<<" "<<b<<" :c "<<c<<'\n'; 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; // int ans=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]; // cout<<"! "<<f<<" "<<dis[u]<<" "<<dis[v]<<" "<<L<<'\n'; 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; // for(int i=0;i<c;i++) { // int qwq=x+i*b; // qwq-=L; // qwq=(qwq%m+m)%m; // if(qwq%d[f]==0) ++ans; // } // int qwq=L-x; 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__

本文作者F x o r G
本文链接https://www.cnblogs.com/xugangfan/p/17655224.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   FxorG  阅读(60)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
历史上的今天:
2022-08-24 相同形式的多组递推式的根号求法
2022-08-24 CF1715D 2+ doors
点击右上角即可分享
微信分享提示