CodeVS 第四次月赛 题解

比赛情况

比赛共 287 人提交代码,最高分 300 分,有 64 人 100 分以上, 13 人 200 分以上, 6 人满分。
第一题出题人为 PbTfcLx,选手最高分100。
第二题出题人为 964685331,选手最高分100。
第三题出题人为 Dashgua,选手最高分100。
官方题解在这里,编写者为 867393296。


A 奶牛的身高

题意

t 组数据,有n个奶牛和 m 条信息,每条信息为某只牛u比某只牛 v w,问信息是否矛盾。
t100,n1000,m30000,|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) 的生物,如果 ik ,那么在第 (m+1) 天还会多出 ai 只年龄为 1 的生物,已知第1天只有 1 只年龄为1的生物,问第 x 天的年龄为y的生物有多少只,答案模 p
k10,x,y1014,p1012,ai100

题解

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

f(n,m)=1ki=1aif(n1,i)f(n1,m1)0 if n=1,m=1 if n>1,m=1 if n>1,m>1 otherwise 

不难发现,任意的一个 f(n,i)(ik) 都是可以通过矩阵乘法快速幂在 O(k3logn) 的时间复杂度内得到的,而 f(x,y)=f(xy+1,1) ,所以只需要构建矩阵递推计算即可。
不难得到递推式为
f(n)a1a2a3ak100010010=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,如果 (2m1)|(2n1) ,并且 m 能被至少一个大于1的完全平方数整除,就称 m n的可协调数。
t 组询问,对于给定的n,求 mnfm
t5,n1018

题解

由一个显然的结论 (x1)|(xk1) 可知, (2m1)|(2n1) 当且仅当 m|n
fi 即欧拉函数 ϕ(i) ,本题用到关于欧拉函数的一些性质:
1. 对于质数 p ϕ(pk)=(p1)pk1
2. 对于互质的 p q ϕ(pq)=ϕ(p)ϕ(q)
3. 对于任意正整数 n d|nϕ(d)=n
回到本题,可以发现 n 的可协调数非常多,而n的因子里不可协调数似乎不是很多。
根据 mnfm+mnm|nfm=m|nfm=n ,我们知道如果算出 n 的因子里不可协调数的欧拉函数值之和,就可以直接推出答案。
n=pt11pt22ptkk,考虑 mnm|n ,这意味着 m 是一个无平方因子数,那么m必然是 p1p2pk 的因子。
考虑到 fm 可以被写成每个因子的欧拉函数乘积,所以我们用乘法原理考虑每个因子的贡献,每个因子 pi 有两种可能,出现在 m 中则贡献ϕ(pi),不出现则贡献 1 ,于是每个因子pi的贡献为 (ϕ(pi)+1) ,即 pi
所以答案为 np1p2pk ,我们只需要把 n 里面的每个次数大于1的质因子,除到幂次只剩下 1 即可,这反而简化了我们的需求,不需要真的求出每个因子。
考虑所有满足pin13 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;
}

来自民间题解的吐槽

第一题最无聊,第二题最无脑,第三题最好写。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值