比赛情况
比赛共 287 人提交代码,最高分 300 分,有 64 人 100 分以上, 13 人 200 分以上, 6 人满分。
第一题出题人为 PbTfcLx,选手最高分100。
第二题出题人为 964685331,选手最高分100。
第三题出题人为 Dashgua,选手最高分100。
官方题解在这里,编写者为 867393296。
A 奶牛的身高
题意
t
组数据,有
t≤100,n≤1000,m≤30000,|w|≤30000
。
题解
如果将每条信息看成是一条边,那么我们只需要检查是否两点之间的任意简单路径长度都是相等的即可,简单路径是指每个点至多在路径中出现一次的路径。
既然都相等了,实际上整个图可以看成是一些树,而加的某些边可能是一些树边组合成的,考虑通过加边构造这个森林。
如果当前
u
和
考虑利用并查集来快速维护连通性和这个距离,设
lca(u,v)
表示在同一棵树上的点
u
和点
考虑如何维护这个距离,不难发现带权并查集的形式已经满足需求,直接模拟加边即可,时间复杂度
O(tmα(n))
。
代码
#include <cstdio>
#include <cstring>
#include <cassert>
const int maxt = 100, maxn = 1000, maxm = 30000, maxa = 30000;
int t, n, m, fa[maxn], dis[maxn];
bool flag;
int find(int x)
{
if(x == fa[x])
return x;
int fx = fa[x];
fa[x] = find(fa[x]);
dis[x] += dis[fx];
return fa[x];
}
int main()
{
scanf("%d", &t);
assert(1 <= t && t <= maxt);
while(t--)
{
scanf("%d%d", &n, &m);
assert(1 <= n && n <= maxn);
assert(1 <= m && m <= maxm);
for(int i = 0; i < n; ++i)
{
fa[i] = i;
dis[i] = 0;
}
flag = 1;
for(int i = 0, u, v, w; i < m; ++i)
{
scanf("%d%d%d", &u, &v, &w);
assert(1 <= u && u <= n);
assert(1 <= v && v <= n);
assert(-maxa <= w && w <= maxa);
if(!flag)
continue;
int fu = find(--u), fv = find(--v);
if(fu == fv)
flag &= dis[u] - dis[v] == w;
else
{
fa[fu] = fv;
dis[fu] = dis[v] - dis[u] + w;
}
}
puts(flag ? "Bessie's eyes are good" : "Bessie is blind.");
}
return 0;
}
B 奇特的生物
题意
定义第
m
天的年龄为
题解
设
f(n,m)
表示第
n
天年龄为
不难发现,任意的一个 f(n,i)(i≤k) 都是可以通过矩阵乘法快速幂在 O(k3logn) 的时间复杂度内得到的,而 f(x,y)=f(x−y+1,1) ,所以只需要构建矩阵递推计算即可。
不难得到递推式为
其中
注意计算过程中可能有两个不超过 1012 的数字相乘求模,可以使用快速加法的形式防止数字溢出。
代码
#include <cstdio>
#include <cassert>
typedef long long LL;
const int maxn = 10, maxa = 100;
const LL maxx = (LL)1e14, maxp = (LL)1e12;
int n;
LL x, y, p;
LL mod_add(LL x, LL y)
{
x += y;
if(x >= p)
x -= p;
return x;
}
const int delen = 23, delta = 1 << delen, deltb = delta - 1;
LL mod_mul(LL x, LL y)
{
LL ret = 0;
while(y)
{
if(y & deltb)
ret = mod_add(ret, x * (y & deltb) % p);
x = x * delta % p;
y >>= delen;
}
return ret;
}
struct Matrix
{
int r, c;
LL num[maxn][maxn];
Matrix operator * (const Matrix &x) const
{
Matrix ret = {};
ret.r = r;
ret.c = x.c;
for(int i = 0; i < r; ++i)
for(int j = 0; j < x.c; ++j)
{
for(int k = 0; k < c; ++k)
ret.num[i][j] += mod_mul(num[i][k], x.num[k][j]);
ret.num[i][j] %= p;
}
return ret;
}
Matrix pow(LL k) const
{
Matrix ret = {}, tmp = *this;
ret.r = ret.c = r;
for(int i = 0; i < r; ++i)
ret.num[i][i] = 1;
while(k)
{
if(k & 1)
ret = ret * tmp;
tmp = tmp * tmp;
k >>= 1;
}
return ret;
}
} A;
int main()
{
scanf("%d%lld%lld%lld", &n, &x, &y, &p);
assert(1 <= n && n <= maxn);
assert(0 <= x && x <= maxx);
assert(0 <= y && y <= maxx);
assert(1 <= p && p <= maxp);
A.r = A.c = n;
for(int i = 1; i < n; ++i)
A.num[i - 1][i] = 1;
for(int i = 0; i < n; ++i)
{
scanf("%d", &A.num[i][0]);
assert(1 <= A.num[i][0] && A.num[i][0] <= maxa);
}
printf("%lld\n", x < y ? 0 : A.pow(x - y).num[0][0]);
return 0;
}
C NTT[Never Think about Transformation]
题意
设
fi
表示不大于
i
且与
对于正整数
m
和
有
t
组询问,对于给定的
t≤5,n≤1018
。
题解
由一个显然的结论
(x−1)|(xk−1)
可知,
(2m−1)|(2n−1)
当且仅当
m|n
。
fi
即欧拉函数
ϕ(i)
,本题用到关于欧拉函数的一些性质:
1. 对于质数
p
,
2. 对于互质的
p
和
3. 对于任意正整数
n
,
回到本题,可以发现
n
的可协调数非常多,而
根据
∑m是n的可协调数fm+∑m不是n的可协调数且m|nfm=∑m|nfm=n
,我们知道如果算出
n
的因子里不可协调数的欧拉函数值之和,就可以直接推出答案。
设
考虑到
fm
可以被写成每个因子的欧拉函数乘积,所以我们用乘法原理考虑每个因子的贡献,每个因子
pi
有两种可能,出现在
m
中则贡献
所以答案为
n−p1⋅p2⋯pk
,我们只需要把
n
里面的每个次数大于
考虑所有满足
1.有两个不同的大因子,那么这两个大因子的幂次都是
1
,无需变化。
2.有一个大因子,那么这个大因子的幂次不超过
3.没有大因子,和情况
所以只需要先将不超过
n13
的质因子抽出来,剩下部分如果是平方数则开方即可,时间复杂度
O(n13)
。
代码
#include <cmath>
#include <cstdio>
#include <cassert>
typedef long long LL;
const int maxt = 5, maxp = (int)1e6;
const LL maxn = (LL)1e18;
int tot, prime[maxp + 1], t;
bool vis[maxp + 1];
LL n, m, ans;
LL check(LL x)
{
LL a = (LL)sqrt(x);
if(a * a < x)
++a;
return a * a == x ? a : x;
}
int main()
{
for(int i = 2; i <= maxp; ++i)
{
if(!vis[i])
prime[tot++] = i;
for(int j = 0, k = maxp / i; j < tot && prime[j] <= k; ++j)
{
vis[i * prime[j]] = 1;
if(i % prime[j] == 0)
break;
}
}
scanf("%d", &t);
assert(1 <= t && t <= 5);
while(t--)
{
scanf("%lld", &n);
assert(1 <= n && n <= maxn);
m = n;
ans = 1;
for(int i = 0; i < tot && prime[i] <= m; ++i)
if(m % prime[i] == 0)
{
for( ; m % prime[i] == 0; m /= prime[i]);
ans *= prime[i];
}
printf("%lld\n", n - ans * check(m));
}
return 0;
}
来自民间题解的吐槽
第一题最无聊,第二题最无脑,第三题最好写。