您的位置:首页 > 其它

CodeVS 第四次月赛 题解

2015-10-05 22:00 323 查看

比赛情况

比赛共 287 人提交代码,最高分 300 分,有 64 人 100 分以上, 13 人 200 分以上, 6 人满分。

第一题出题人为 PbTfcLx,选手最高分100。

第二题出题人为 964685331,选手最高分100。

第三题出题人为 Dashgua,选手最高分100。

官方题解在这里,编写者为 867393296。

A 奶牛的身高

题意

t组数据,有n个奶牛和m条信息,每条信息为某只牛u比某只牛v高w,问信息是否矛盾。

t≤100,n≤1000,m≤30000,|w|≤30000。

题解

如果将每条信息看成是一条边,那么我们只需要检查是否两点之间的任意简单路径长度都是相等的即可,简单路径是指每个点至多在路径中出现一次的路径。

既然都相等了,实际上整个图可以看成是一些树,而加的某些边可能是一些树边组合成的,考虑通过加边构造这个森林。

如果当前u和v不连通,那么加上边权为w的这条边;如果当前u和v连通,那么新加的这条边一定要是已经有的树边组合而成,只需要判断u和v的树上距离是否等于w即可。

考虑利用并查集来快速维护连通性和这个距离,设lca(u,v)表示在同一棵树上的点u和点v的最近公共祖先,原本树上两点u和v的距离是等于u到lca(u,v)到v的距离,我们可以通过构造使得每棵树的任意两个点的lca是根,那么只需要维护每个点到根的距离即可。

考虑如何维护这个距离,不难发现带权并查集的形式已经满足需求,直接模拟加边即可,时间复杂度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天的年龄为i的生物,会在第(m+1)天变成年龄为(i+1)的生物,如果i≤k,那么在第(m+1)天还会多出ai只年龄为1的生物,已知第1天只有1只年龄为1的生物,问第x天的年龄为y的生物有多少只,答案模p。

k≤10,x,y≤1014,p≤1012,ai≤100。

题解

设f(n,m)表示第n天年龄为m的生物的数量,那么显然有

f(n,m)=⎧⎩⎨⎪⎪⎪⎪⎪⎪1∑ki=1ai∗f(n−1,i)f(n−1,m−1)0 if n=1,m=1 if n>1,m=1 if n>1,m>1 otherwise

不难发现,任意的一个f(n,i)(i≤k)都是可以通过矩阵乘法快速幂在O(k3logn)的时间复杂度内得到的,而f(x,y)=f(x−y+1,1),所以只需要构建矩阵递推计算即可。

不难得到递推式为

f(n)∗⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢a1a2a3⋯ak10⋮⋮001⋱⋯⋯⋱⋱⋱⋯0⋮010⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥=f(n+1)

其中

f(n)=[f(n,1)f(n,2)f(n,3)⋯f(n,k)]

注意计算过程中可能有两个不超过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且与i互质的正整数的个数。

对于正整数m和n,如果(2m−1)|(2n−1),并且m能被至少一个大于1的完全平方数整除,就称m是n的可协调数。

有t组询问,对于给定的n,求∑m是n的可协调数fm。

t≤5,n≤1018。

题解

由一个显然的结论(x−1)|(xk−1)可知,(2m−1)|(2n−1)当且仅当m|n。

fi即欧拉函数ϕ(i),本题用到关于欧拉函数的一些性质:

1. 对于质数p,ϕ(pk)=(p−1)⋅pk−1。

2. 对于互质的p和q,ϕ(pq)=ϕ(p)⋅ϕ(q)。

3. 对于任意正整数n,∑d|nϕ(d)=n。

回到本题,可以发现n的可协调数非常多,而n的因子里不可协调数似乎不是很多。

根据∑m是n的可协调数fm+∑m不是n的可协调数且m|nfm=∑m|nfm=n,我们知道如果算出n的因子里不可协调数的欧拉函数值之和,就可以直接推出答案。

设n=pt11⋅pt22⋯ptkk,考虑m不是n的可协调数且m|n,这意味着m是一个无平方因子数,那么m必然是p1⋅p2⋯pk的因子。

考虑到fm可以被写成每个因子的欧拉函数乘积,所以我们用乘法原理考虑每个因子的贡献,每个因子pi有两种可能,出现在m中则贡献ϕ(pi),不出现则贡献1,于是每个因子pi的贡献为(ϕ(pi)+1),即pi。

所以答案为n−p1⋅p2⋯pk,我们只需要把n里面的每个次数大于1的质因子,除到幂次只剩下1即可,这反而简化了我们的需求,不需要真的求出每个因子。

考虑所有满足pi≤n13的pi,可以在O(n13)的时间内通过枚举等方法分解出来,剩下的pi满足pi>n13,满足条件的大因子至多有两个,所以可以分三种情况讨论。

1.有两个不同的大因子,那么这两个大因子的幂次都是1,无需变化。

2.有一个大因子,那么这个大因子的幂次不超过2,判断剩下这个数是不是平方数即可知道幂次是多少,如果幂次为2则除掉一个大因子即可。

3.没有大因子,和情况1一样,不需要变化。

所以只需要先将不超过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;
}


来自民间题解的吐槽

第一题最无聊,第二题最无脑,第三题最好写。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息