IOI2021集训队作业147FK. Traffic Blights

一个数轴上有些红绿灯,它们距离原点\(pos_i\),一开始刚好为红,以\(red_i,green_i\)为周期红绿闪烁。

有个车从原点随机时间出发。对于每个\(i\),车第一次遇到红灯在\(i\)的概率。

\(n\le 500,1\le red_i+green_i\le 100\)


\(x\)为出发时间。对于每个灯,可以列出个这样的方程:\(x\equiv a_i\pmod {p_i}\)\(p_i=red_i+green_i\)\(a_i\)有多个(看做有个表\(c_{i,j}\),如果\(c_{i,j}=1\)\(a_j\)出现)。

问题变为:有多少个方程组满足有解。

如果\(p_i\)互质,直接乘起来即可。

如果\(p_i\)互为倍数关系,可以把小的那个暴力扩张它的表(即\(c_{i}\)),然后把它们的表并起来。

更一般的情况:考虑钦定一个整数\(M\),枚举\(r=x\mod M\),把方程改写为\(r+Mt\equiv a\pmod p\)。推一下:

\[Mt\equiv a-r\pmod p\\ \gcd(M,p)t\equiv a-r \pmod p\\ if \ \gcd(M,p)|(a-r)\\ t\equiv \frac{a-r}{\gcd(M,p)}\pmod {\frac{p}{\gcd(M,p)}} \]

现在用\(t\)来替代\(x\)。可以发现周期是\(\frac{p}{\gcd(M,p)}\)。当\(M=LCM(1,2,\dots,\sqrt V)\)时,\(\frac{p}{\gcd(M,p)}\)得到的东西都是质数的幂次的形式。这题中\(M=2520\)

于是枚举\(r\),改写方程组,得到了一堆要么互质要么互为倍数的方程组,然后就容易处理了。

时间\(O(n*2520*V)\)

PS:如果直接按照上面写,会发现它挂了(

注意这一步\(Mt\equiv a-r\pmod p\to \gcd(M,p)t\equiv a-r \pmod p\)

如果只是在意解的个数,这样是没问题的。问题在于,两个方程中的\(t\)不是同一个东西。

解决方法:直接枚举\(t\in [0,\frac{p}{\gcd(M,p)})\),看是否\((r+Mt)\mod p\)在表中。


using namespace std;
#include <bits/stdc++.h>
#define N 505
#define ll long long
#define db long double
int gcd(int a,int b){
	int k;
	while (b)
		k=a%b,a=b,b=k;
	return a;
}
const int M=2520;
const int V=100;
int n;
int c[N][N];
int p[N];
int cnt[N];
db f[N],ans[N];
int buc[N][N],ori[N];
int ex(int x){
	if (x%2==0) return 8;
	if (x%3==0) return 9;
	return x;
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%d",&n);
	for (int i=1;i<=n;++i){
		int pos,red,gre;
		scanf("%d%d%d",&pos,&red,&gre);
		p[i]=red+gre;
		for (int j=red;j<p[i];++j)
			c[i][((j-pos)%p[i]+p[i])%p[i]]=1;
	}
	db tot,rem;
	for (int r=0;r<M;++r){
		tot=1;
		for (int i=1;i<=V;++i)
			if (ex(i)==i){
				tot*=i;
				cnt[i]=i;
				for (int j=0;j<i;++j)
					buc[i][j]=1;
			}
		rem=tot;
		for (int i=1;i<=n;++i){
			int g=gcd(M,p[i]),p_=p[i]/g;
			memset(ori,0,sizeof(int)*p_);
			for (int j=0;j<p_;++j){
				int a=r+j*M/*g*/;
				if (c[i][a%p[i]])
					ori[j%p_]=1;
			}
//			static int _ori[N];
//			memset(_ori,0,sizeof(int)*p_);
//			for (int j=0;j<p[i];++j)
//				if (c[i][j] && (j-r)%g==0)
//					_ori[((j-r)/g%p_+p_)%p_]=1;
//			for (int j=0;j<p_;++j)
//				if (ori[j]!=_ori[j])
//					printf("fuck\n");
			int e=ex(p_);
			for (int j=0;j<e;++j)
				if (ori[j%p_]==0 && buc[e][j]){
					buc[e][j]=0;
					rem=rem/cnt[e]*(cnt[e]-1);
					cnt[e]--;
				}
			f[i]+=rem/tot;
		}
	}
	ans[0]=1;
	for (int i=1;i<=n;++i)
		ans[i]=f[i]/M;
	for (int i=0;i<=n;++i)
		printf("%.9lf\n",(double)(ans[i]-ans[i+1]));
	return 0;
}
posted @ 2021-03-31 19:40  jz_597  阅读(81)  评论(0编辑  收藏  举报