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

发布时间 2023-08-24 21:38:27作者: FxorG

https://loj.ac/p/508

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

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

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

先观察一些性质。

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

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

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

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

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

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

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

考虑环的线性组合能表示出来的一定是 \(\gcd(m,a_1,a_2,a_3,...,a_t)\) 的倍数,其中 \(a\) 是环长序列,那么对于任何一条路径 \(L\),它一定有个 \(2L\)\(\gcd\) 中,若 uv 路径唯一,则路径确定。若不唯一,则可以看成两条路径 \(L_1,L_2\),那么这两条路径一定可以构成一个环,即 \(L_1+L_2,2L_1,2L_2\) 都在 \(\gcd\) 中,然后你考虑两条路径无交的地方一定会构成环,然后你去设参数,看看能否通过走这三种长度,来实现路径的替换。你会发现是可以的。

因此,我们只要随便找一条路径即可,因为环的线性组合与这条路径结合总能生成另一条路径。

那么,现在路径是随便找的了。我们可以直接并查集大力维护。注意在合并两棵树时,加的边不一定是在树根,此时魔改一下距离即可。

那么,如何维护环长的 \(\gcd\) 呢?

考虑加入 \((x,y,w)\),如果没形成环,那么会多一个 \(2w\)

若形成环,则多一个 \(2w\),以及原先其若干路径 \(L\) 会形成 \(L+w\) 的环。

但因为,\(\gcd(L_1+w,L_2+w)=\gcd(L2+w,L1-L2),\gcd(2L_1,L_1+L_2)=\gcd(L_1+L_2,L_1-L_2)\),你会发现,\(L_1-L_2\) 实际上已经加入了。所以我们只需要加入 \(L_2+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;
}