一.前言
本来打算打打这个比赛玩玩,结果同学找我打游戏王去了,就没打现场(逃)
因为是一道不错的数学题,来写写补题的题解
这里点名批评 @HOLIC_2022 喂给我的假题意,让我查错大半天,最后发现题意错了还重新推了好多东西,拳头都硬了
等会儿顺便分享下假题意的一种做法
二.正文
简单题意:
有n个人,速度为属于[1,200000]的两两不等的正整数。现有一个长度为l的跑道,一开始每个人都在跑道的起点位置,每个人从起点开始,跑到跑道对面再跑回来再跑过去。。。。。。一直持续T个单位的时间,问,不算0时刻,有多少个时刻至少有两个人会相遇(就是在跑道的相同位置)。()
假题意:
前面的条件一样,设表示x和y在过程中相遇次数,求
真题意题解:
我们设t时刻,有两个人跑的距离分别为x和y(不妨设x>y,也就是第一个人的速度比第二个人快),那么,如果他们两个人相遇,一定有:
这两个条件至少一个成立。
首先考虑,设两人的速度分别为(),当前时间为,那么很明显有:
很明显,那么,所有满足条件的次数很明显有:
(其实就是算一共有多少个2l能到达)。
设这个值为,那么对应的时间就分别是:
同理,时,次数也就是,对应时间也就是
注意到,两种状态下并无本质不同,我们只需要知道对于一个数,是否存在两个速度和为或者差为,然后就可以获得有存在至少两点相遇的时间了(有多个等于,因为时间都一样,统计一个就好)
那么,我们如何求解上述问题呢?
我们考虑多项式。
设
那么, 我们使用或者,求一遍,的意义是,先从n个速度中选出一个速度,再从n个速度中选出一个速度,使得两个速度和为n的方案数,在这基础上,我们减去两次选择的速度都是同一个人的情况,如果当前的系数不为0,那么,就能说明存在两个速度和为
对于差,我们求一遍就行了,因为的项是是否存在一个人速度为,那么乘出来后的第项就是差为的方案数。
我们求完之后,会很明显发现,答案大了。那是因为各个值所对应的时间是有重叠部分的。
接下来就是考虑如何进行去重。
注意到,每个时间的形式都是的形式,因为我们只需要去重,那么我们不妨把多余的部分去掉,看做这种形式。
我们可以很简单的想到一种去重的方法:将所有分数画出最简,然后再把相同的分数去除即可。
这里我们考虑使用莫比乌斯反演。
我们设
那么根据定义有:
那么,根据莫比乌斯反演有:
所以,我们只需要求出就行了。
注意到,一开始未化简的相同分母的分数的分子都是从1开始递增的。所以,对于一种分母代表的就是分母为时,最大的分子是多少
我们先令存在速度和或者差为的那些。
考虑到一个分数化简只可能从当前分母的倍数化简过来,那么,我们对于当前枚举的分母,我们再枚举它的倍数即可。
对于的一个倍数,能以它为分母的分数的分子是,那么,它们分母化简成后,分子就是,所有倍数取max即可。
最后,再带入莫比乌斯反演公式,就行了。
唯一需要注意一点的就是,对于不可能成为分母的那些数字,不要统计答案,不然会出错
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 6e5 + 10, mod = 1e9 + 7, Mod = 998244353, mod1 = 3, mod2 = 332748118;
const int M = 2e5;
int rs[N], u[N];
bool f[N];
int F[N], G[N], id[N];
int r[N], K, l;
int p[N], cnt;
inline void sai(){
u[1] = 1;
for(int i = 2; i < N; ++ i){
if(!f[i]){
p[++ cnt] = i; u[i] = -1;
}
for(int j = 1; j <= cnt; ++ j){
if(i * p[j] >= N) break;
f[i * p[j]] = 1;
if(i % p[j] == 0){
u[i * p[j]] = 0;
break;
}
u[i * p[j]] = -u[i];
}
}
}
inline int ksm(int x, int y){
int ans = 1;
while(y){
if(y & 1) ans = (1LL * ans * x) % Mod;
x = (1LL * x * x) % Mod; y >>= 1;
}
return ans;
}
inline void NTT(int *v, int tp){
for(int i = 1; i < K; ++ i){
if(r[i] > i) swap(v[r[i]], v[i]);
}
for(int i = 1; i < K; i <<= 1){
int w1 = ksm(tp, (Mod - 1) / (i << 1));
for(int R = i << 1, j = 0; j < K; j += R){
int w = 1;
for(int k = 0; k < i; ++ k){
int x = v[j + k], y = (1LL * w * v[i + j + k]) % Mod;
v[j + k] = (x + y) % Mod;
v[i + j + k] = (x - y + Mod) % Mod;
w = (1LL * w * w1) % Mod;
}
}
}
}
inline void pre(int al){
K = 1, l = 0;
while(K <= al) K <<= 1, ++ l;
for(int i = 1; i < K; ++ i) r[i] = ((r[i >> 1] >> 1) | (i & 1) << (l - 1));
}
inline void Mul(int *a, int *b){
NTT(a, mod1); if(a != b) NTT(b, mod1);
for(int i = 0; i < K; ++ i) a[i] = (1LL * a[i] * b[i]) % Mod;
NTT(a, mod2); int inv = ksm(K, Mod - 2);
for(int i = 0; i < K; ++ i) a[i] = (1LL * a[i] * inv) % Mod;
}
signed main(){
sai();
int l, t, n;
scanf("%lld%lld%lld", &l, &t, &n);
for(int x, i = 1; i <= n; ++ i){
scanf("%lld", &x);
id[x] = F[x] = 1;
}
pre(M << 1); Mul(F, F);
int ans = 0;
for(int i = 3; i < K; ++ i){//和至少是3
if(i % 2 == 0) F[i] -= (id[i / 2]);
rs[i] = (F[i] > 0);
}
memset(F, 0, sizeof(F));
for(int i = 0; i <= M; ++ i){
F[i] = id[i]; G[i] = id[M - i];//G = F'
}
pre(M << 1); Mul(F, G);
for(int i = 0; i < M; ++ i){
int v = (M - i); //x - y的值
rs[v] |= (F[i] > 0);
}
memset(F, 0, sizeof(F)); memset(G, 0, sizeof(G));
for(int i = 1; i < N; ++ i){
if(rs[i]) F[i] = (1LL * t * i) / (2LL * l);
}
for(int i = 1; i < N; ++ i){//求可以作为分母的数字
for(int j = i + i; j < N; j += i){
rs[i] |= rs[j];
}
}
//设F[i] = 化简后分母为i的因子的分数的个数
//G[i] = 化简后分母为i的个数
//F[i] = G[j] (j|i)
//G[i] = F[j] * u[i / j] (j|i)
//求F[i]即可
for(int i = N - 1; i; -- i){//正着反着都一样,因为要最大肯定是从初始化的那些数字转移过了
for(int j = i + i; j < N; j += i){
F[i] = max(F[i], F[j] / (j / i));
}
}
for(int i = 1; i < N; ++ i){
for(int j = i; j < N; j += i){
G[j] += F[i] * u[j / i];
}
}
for(int i = 1; i < N; ++ i){
if(rs[i]) ans = (ans + G[i]) % mod;
}
printf("%d", ans);
return 0;
}
假题意题解
一开始思路是一样的,我们可以通过求出和/差为的有多少种方案。然后,对于每种我们都计算下有多少种方案。也就是,我们分别求有多少个以及,我们注意到,我们重复计算了两者同时成立的情况,也就是以及的情况,我们将答案减去这两种情况的答案即可。
对于第一种情况,我们有:
也就是两人正好都跑完了若干个来回,此时时间相等
化简得:
注意到,当为的倍数,也就是说,我们求最高能到的多少倍就行了。
设
容易得,最高倍数为:
又有可得,
代入得:
又因为
代入得:
因为,我们可以直接枚举
所以,我们只需要求,对于一个数,速度两两求后,有多少组的为即可。
要求它,我们设,那么,,我们当然可以莫比乌斯反演,当然也可以直接倒着容斥一遍即可。
对于的部分,我们同样写出表达式:
化简:
也就是,两者的速度的奇数倍要相等。
这部分比较特殊,我们要这么做:
将所有速度表示为,其中为奇数,我们将所有速度按分类。
注意到,只有同一组的速度才可能满足条件,所以我们只看同一组的情况。
对于同一组,两者的到达的倍数一定是奇数(用的表达式可证)
而每个倍数又是的倍数,那么,我们可以类比之前的做法,然后我们只需要统计所有奇数倍有多少个即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 6e5 + 10, mod = 1e9 + 7, Mod = 998244353, mod1 = 3, mod2 = 332748118;
const int M = 2e5;
int cnt[N];
int rs[N], c[N], d[N];
int F[N], G[N], id[N];
int r[N], K, l;
inline int ksm(int x, int y){
int ans = 1;
while(y){
if(y & 1) ans = (1LL * ans * x) % Mod;
x = (1LL * x * x) % Mod; y >>= 1;
}
return ans;
}
inline void NTT(int *v, int tp){
for(int i = 1; i < K; ++ i){
if(r[i] > i) swap(v[r[i]], v[i]);
}
for(int i = 1; i < K; i <<= 1){
int w1 = ksm(tp, (Mod - 1) / (i << 1));
for(int R = i << 1, j = 0; j < K; j += R){
int w = 1;
for(int k = 0; k < i; ++ k){
int x = v[j + k], y = (1LL * w * v[i + j + k]) % Mod;
v[j + k] = (x + y) % Mod;
v[i + j + k] = (x - y + Mod) % Mod;
w = (1LL * w * w1) % Mod;
}
}
}
}
inline void pre(int al){
K = 1, l = 0;
while(K <= al) K <<= 1, ++ l;
for(int i = 1; i < K; ++ i) r[i] = ((r[i >> 1] >> 1) | (i & 1) << (l - 1));
}
inline void Mul(int *a, int *b){
NTT(a, mod1); if(a != b) NTT(b, mod1);
for(int i = 0; i < K; ++ i) a[i] = (1LL * a[i] * b[i]) % Mod;
NTT(a, mod2); int inv = ksm(K, Mod - 2);
for(int i = 0; i < K; ++ i) a[i] = (1LL * a[i] * inv) % Mod;
}
signed main(){
int l, t, n;
scanf("%lld%lld%lld", &l, &t, &n);
for(int x, i = 1; i <= n; ++ i){
scanf("%lld", &x);
id[x] = F[x] = 1;
}
pre(M << 1); Mul(F, F);
int ans = 0, inv2 = (mod + 1) >> 1;
for(int i = 3; i < K; ++ i){
if(i % 2 == 0) F[i] -= (id[i / 2]);
F[i] = (1LL * F[i] * inv2) % mod; //因为x + y = y + x,会重复计算一遍,所以要除2
cnt[i] = F[i];
}
memset(F, 0, sizeof(F));
for(int i = 0; i <= M; ++ i){
F[i] = id[i]; G[i] = id[M - i];
}
pre(M << 1); Mul(F, G);
for(int i = 0; i < M; ++ i){
int v = (M - i); //x - y的值,因为不考虑x = y(此时对应M),所以x - y 不等于 y - x,不用除2
cnt[v] = (cnt[v] + F[i]) % mod;
}
for(int i = 1; i <= M; ++ i){
for(int j = i; j <= M; j += i){
c[i] += id[j];
}
}
for(int i = M; i; -- i){
d[i] = (1LL * c[i] * (c[i] - 1)) % mod;
d[i] = (1LL * d[i] * inv2) % mod;
for(int j = i + i; j <= M; j += i){
d[i] = (d[i] - d[j] + mod) % mod;
}
cnt[i] = (cnt[i] - d[i] + mod) % mod;
}
memset(c, 0, sizeof(c));
for(int T = 0; T <= 17; ++ T){//分组
int x = (1 << T), y = (1 << (T + 1));
for(int i = 1; i <= M; ++ i){
for(int j = i; j <= M; j += i){
if(j % x == 0 && j % y != 0) c[i] += id[j];
}
}
for(int i = M; i; -- i){
d[i] = (1LL * c[i] * (c[i] - 1)) % mod;
d[i] = (1LL * d[i] * inv2) % mod;
for(int j = i + i; j <= M; j += i){
d[i] = (d[i] - d[j] + mod) % mod;
}
int now = ((1LL * t * i) / (2LL * l));
now = (now + 1) / 2;//统计奇数倍个数
now %= mod;
now = (1LL * now * d[i]) % mod;
ans = (ans - now + mod) % mod;
}
for(int i = 1; i <= M; ++ i) c[i] = 0;
}
for(int i = 1; i < N; ++ i){
int now = ((1LL * t * i) / (2LL * l)) % mod;
now = (1LL * now * cnt[i]) % mod;
ans = (ans + now) % mod;
}
printf("%d", ans);
return 0;
}