「2019纪中集训Day4」解题报告

T1、forging

勇者虽然武力值很高,但在经历了多次战斗后,发现怪物越来越难打。
于是开始思考是不是自己平时锻炼没到位,于是苦练一个月后发现......自己连一个史莱姆都打不过了。
勇者的精灵路由器告诉勇者其实是他自己的武器不好,并把他指引到了锻造厂。
“欢迎啊,老朋友。”
一阵寒暄过后,厂长带他们参观了厂子四周,并给他们讲锻造的流程。
“我们这里的武器分成若干的等级,等级越高武器就越厉害,并且对每一等级的武器都有两种属性值 \(b\)\(c\),但是我们初始只能花 \(a\) 个金币来生产 \(1\)\(0\) 级剑......”
“你们厂子怎么这么垃圾啊,不能一下子就造出来 \(999\) 级的武器吗?”勇者不耐烦的打断了厂长的话。
“别着急,还没开始讲锻造呢......举例你手中有一把 \(x\) 级武器和一把 \(y\) 级武器 \((y = \max(x - 1, 0))\),令锻造附加值 \(k = \min(c_x , b_y)\),则有 \(\frac{k}{c_x}\) 的概率将两把武器融合成一把 \(x + 1\) 级的武器。”
“......但是,锻造不是一帆风顺的,你同样有 \(1 - \frac{k}{c_x}\) 的概率将两把武器融合成一把 \(\max(x - 1, 0)\) 级的武器......”
勇者听完后暗暗思忖,他知道厂长一定又想借此机会坑骗他的零花钱,于是求助这个村最聪明的智者——你,来告诉他,想要强化出一把 \(n(n \le 10^7)\) 级的武器,其期望花费为多少?
由于勇者不精通高精度小数,所以你只需要将答案对 \(998244353\) ( \(7 \times17 \times 2^{23} + 1\),一个质数 ) 取模即可。

\(Sol\)

\(f_i\) 表示造出一把 \(i\) 级武器的期望代价,\(p_i\) 表示强化 \(i\) 武器时成功的概率;
通过推导不难得出:\(f_i = \frac{1}{p} f_{i - 1} + f_{i - 2}\)
要注意常数因子带来的影响。

\(Source\)

#include <cstdio>
int in() {
    int x = 0; char c = getchar(); bool f = c == '-';
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 1e7 + 5, mod = 998244353;

int n, A, a[N], b[N], c[N], f[N], inv[N];

int qpow(int base, int b, int ret = 1) {
    for (; b; b >>= 1, base = 1ll * base * base % mod)
        if (b & 1)
            ret = 1ll * ret * base % mod;
    return ret;
}

inline int min(int _, int __) { return _ < __ ? _ : __; }
inline int max(int _, int __) { return _ > __ ? _ : __; }

int main() {
//  freopen("in", "r", stdin);
    freopen("forging.in", "r", stdin);
    freopen("forging.out", "w", stdout);
    n = in(), A = in();
    int bx = in(), by = in(), cx = in(), cy = in(), p = in();
    b[0] = by + 1, c[0] = cy + 1;
    for(int i = 1; i < n; ++i) {
        b[i] = (1ll * b[i - 1] * bx + by) % p + 1;
        c[i] = (1ll * c[i - 1] * cx + cy) % p + 1;
    }
    inv[1] = 1;
    for (int i = 2; i <= p; ++i)
        inv[i] = 1ll * (mod - (mod / i)) * inv[mod % i] % mod;
    f[0] = A;
    for (int i = 1; i <= n; ++i) {
        f[i] = 1ll * inv[min(b[max(i - 2, 0)], c[i - 1])] * c[i - 1] % mod * f[i - 1] % mod + f[max(i - 2, 0)];
        if (f[i] >= mod)
            f[i] -= mod;
    }
    printf("%d\n", f[n]);
    return 0;
}

T2、division

整除符号为 \(|\)\(d|n\) 在计算机语言中可被描述为 \(n \% d == 0\)
现有一算式 \(n | x^m − x\),给定 \(n\)\(m\),求 \([1, n]\) 以内 \(x\) 解的个数。
解可能很大,输出取模 \(998244353\)

其中 \(n\) 的给定方式是由 \(c\) 个不超过 \(t\) 的质数的乘积给出的,\(c(c \le 10 ^ 4)\)\(t\) 的范围会在数据范围中给出。
第一行一个 \(id\) 表示这个数据点的标号。
多组数据,其中第二行一个整数 \(T\) 表示数据组数。
对于每一组数据:
第一行两个整数 \(c\)\(m(m \le 10 ^ 9)\)
第二行 \(c\) 个整数 \(p_i (p_i \le 10 ^4)\),这些整数都是质数,且两两不同,他们的乘积即为\(n\)
由于你可以通过输入求出 \(t\),输入不再给出。

\(Sol_1\)

首先根据题意可以得到 \(c\) 个形如 \(x ^ m \equiv x \ (mod \ p_i)\) 方程组:
对于第 \(i\) 个方程,可以暴力枚举 \(p_i\) 以内的所有正整数,设得到的解的个数为 \(t_i\)
答案为 \(\Pi t_i\),时间复杂度 \(O(T \ c \ p_i)\)

简单证明:
对于每个 \(i\)\(t_i\) 个解可以看做 \(t_i\) 个形如 \(s_{i,j} \equiv 0 \ (mod \ p_i)\) 的方程组;
现在对于每一个 \(i\) 都选出一个解(即上述方程组中的一个),可以得到一个新的方程组;
这个新的方程组在 \(n\) 以内只有一个正整数解,\(QED\)

\(Source\)

//#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <cstdio>
int in() {
    int x = 0; char c = getchar(); bool f = c == '-';
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

int c, n, m;

const int N = 1e4 + 5, mod = 998244353;
int f[N], min_prime[N], pri[N];
void init_prime() {
    for (int i = 2; i < N; ++i) {
        if (!min_prime[i]) {
            min_prime[i] = i;
            pri[++pri[0]] = i;
        }
        for (int j = 1; j <= pri[0] && i * pri[j] < N; ++j) {
            min_prime[i * pri[j]] = pri[j];
            if (i % pri[j] == 0)
                break;
        }
    }
}

int qpow(int base, int b, int ret = 1, int p = mod) {
    for (; b; b >>= 1, base = 1ll * base * base % p)
        if (b & 1)
            ret = 1ll * ret * base % p;
    return ret;
}

int main() {
//  freopen("in", "r", stdin);
    freopen("division.in", "r", stdin);
    freopen("division.out", "w", stdout);
    int id = in(), T = in();
    init_prime();
    while (T--) {
        c = in(), m = in();
        int res = 1;
        for (int i = 1; i <= c; ++i) {
            n = in();
            int tmp = 2;
            f[1] = 1;
            for (int i = 2; i < n; ++i) {
                if (i == min_prime[i])
                    f[i] = qpow(i, m, 1, n);
                else
                    f[i] = 1ll * f[min_prime[i]] * f[i / min_prime[i]] % n;
                if (f[i] == i)
                    ++tmp;
            }
            res = 1ll * res * tmp % mod;
        }
        printf("%d\n", res);
    }
    return 0;
}

\(p.s.\)关于这个做法好像假了,我这两天再证一下 (2019-08-06)。

\(Sol_2\)

由于 \(p\) 是质数,\([1,p-1]\) 的所有整数都可以表示为 \(g ^ y\)
所以方程组可以写成 \(g^{my} \equiv g^y \ (mod \ (p - 1))\)
根据费马小定理:
\[my \equiv y \ (mod \ (p - 1)) \Rightarrow (m - 1)y \equiv 0 \ (mod \ (p - 1))\]
两边同时除以 \((m - 1, p - 1)\),令 \(d=(m - 1, p - 1)\)
\[ \frac{m-1}{d} y \equiv 0 \ (mod \ \frac{p - 1}{d})\]
由于 \(y \in [0, p - 2]\),所以 \(\frac{p - 1}{d}\) 的任意小于 \(d\) 的非负整数倍都符合条件;
所以第 \(i\) 个方程的解个数为 \(d + 1\)\(x = p\) 显然是唯一一个没有被算到的解。
时间复杂度 \(O(T \ c \ log p)\)

\(Source\)

#include <cstdio>
int in() {
    int x = 0; char c = getchar(); bool f = c == '-';
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int mod = 998244353;

int c, n, m;

int gcd(int a, int b) {
    return b ? gcd(b, a % b) : a;
}

int main() {
//  freopen("in", "r", stdin);
    freopen("division.in", "r", stdin);
    freopen("division.out", "w", stdout);
    int id = in(), T = in();
    while (T--) {
        c = in(), m = in();
        int res = 1;
        for (int i = 1; i <= c; ++i)
            res = 1ll * res * (gcd(in() - 1, m - 1) + 1) % mod;
        printf("%d\n", res);
    }
    return 0;
}

T3、money

\(n(n \le 10 ^ 5)\) 个点,\(m(m \le 10 ^ 6)\) 次操作;
有两种操作:
\(0\)\(a\)\(b\) 连一条边权为 \(c\) 的有向边;
\(1\):查询 \(a\)\(b\) 的通路上的最小值,若没有通路则输出 \(0\)
保证每个点只会连出去一条边,强制在线。

\(Sol_1\)

因为最后一句话的限制,显然这是一棵树,且方向为儿子到父亲,考虑倍增;
每次合并两棵树时考虑启发式合并(重新计算点数较小的树的倍增数组),此时当做无根树,还要记录一下是否是父亲走向儿子的边;
由于每个点只有在该点所在联通块的大小 更新为至少原来的两倍时 才会重新计算它的倍增数组;
所以时间复杂度为 \(O(n \ log ^2 n + m \ log n)\)
没写,所以没有代码;

\(Sol_2\)

因为倍增数组里的信息只会增加,所以只要做到添加信息时做到不重复,时间复杂度仍是 \(O(nlogn)\)
对于一棵树,可以用 \(vector\) 等数据结构来维护同一深度的点;
对于 \(u\) 子树中每一层节点,记录每一层已经有 \(k\) 级祖先的信息,每次连一条 \(u\) -> \(v\) 的边 (\(u\) 为子节点),从 \(k + 1\) 级祖先开始增加信息即可;
没写。

\(Sol_3\)

\(lct\) 裸题。
\(lct\) 维护 \(a,b\) 两点的 \(lca\)\(access(a)\)\(access(b)\) 时最后一次进行 \(splay\) 操作的点就是 \(lca\)
时间复杂度 \(O(m \ logn)\),由于 \(lct\) 的超大常数,考场只拿了 \(80\) 分,要大力卡常开 \(O_3\)才能过(第一次加输出优化变快,快了近 \(2.1s\))。

\(Source\)

//#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <cstdio>
#include <cstring>
inline char get_char(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
int in() {
    register int x = 0; register char c = get_char();
    while (c < '0' || c > '9')
        c = get_char();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = get_char();
    return x;
}
void out(register int x) {
    if (x > 9)
        out(x / 10);
    putchar(x % 10 + '0');
}
inline void out_ln(register int x) {
    out(x), puts("");
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 1e5 + 5, inf = 0x3f3f3f3f;

int n, m, lastans, pre[N];

int get_fa(int u) {
    return pre[u] == u ? u : pre[u] = get_fa(pre[u]);
}

//link_cut_tree begin
int fa[N], c[N][2], val[N], min[N];
bool rev[N];
inline bool nroot(register int p) {
    return c[fa[p]][0] == p || c[fa[p]][1] == p;
}
inline void push_up(register int p) {
    min[p] = val[p];
    chk_min(min[p], min[c[p][0]]);
    chk_min(min[p], min[c[p][1]]);
}
inline void rever(register int p) {
    register int t = c[p][0]; c[p][0] = c[p][1], c[p][1] = t;
    rev[p] ^= 1;
}
inline void push_down(register int p) {
    if (rev[p]) {
        if (c[p][0])
            rever(c[p][0]);
        if (c[p][1])
            rever(c[p][1]);
        rev[p] = 0;
    }
}
void push_all(int p) {
    if (nroot(p))
        push_all(fa[p]);
    push_down(p);
}
void rotate(register int x) {
    register int y = fa[x], z = fa[y], l = c[y][1] == x, r = l ^ 1;
    if (nroot(y))
        c[z][c[z][1] == y] = x;
    fa[x] = z, fa[y] = x;
    if (c[x][r])
        fa[c[x][r]] = y;
    c[y][l] = c[x][r], c[x][r] = y;
    push_up(y), push_up(x);
}
void splay(register int x) {
    register int y, z;
    push_all(x);
    while (nroot(x)) {
        y = fa[x], z = fa[y];
        if (nroot(y))
            rotate(c[y][0] == x ^ c[z][0] == y ? x : y);
        rotate(x);
    }
}

int access(register int p) {
    register int ret;
    for (register int t = 0; p; p = fa[t = p])
        splay(p), c[p][1] = t, push_up(p), ret = p;
    return ret;
}
inline void make_root(register int p) {
    access(p), splay(p), rever(p);
}
inline void split(register int p, register int t) {
    make_root(p), access(t), splay(t);
}
inline void link(register int p, register int t) {
    splay(p), fa[p] = t;
    val[p] = (in() + lastans) % n + 1;
    chk_min(min[p], val[p]);
}
inline int query(register int a, register int b) {
    register int ret = 0;
    access(b);
    if (access(a) == b) {
        make_root(b);
        access(a), splay(b);
        ret = min[c[b][1]];
        make_root(get_fa(b));
    }
    return ret;
}
//link_cut_tree end

int main() {
//  freopen("in", "r", stdin);
    freopen("money.in", "r", stdin);
    freopen("money.out", "w", stdout);
    n = in(), m = in();
    for (register int i = 1; i <= n; ++i)
        pre[i] = i;
    memset(val, inf, sizeof(val));
    min[0] = val[0];
    while (m--) {
        if (in()) {
            int a = (in() + lastans) % n + 1, b = (in() + lastans) % n + 1;
//          printf("%d %d\n", a, b);
            if (get_fa(a) == get_fa(b))
                lastans = query(a, b);
            else
                lastans = 0;
            out_ln(lastans);
        } else {
            int a = (in() + lastans) % n + 1, b = (in() + lastans) % n + 1;
            link(a, b);
            pre[a] = b;
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/15owzLy1-yiylcy/p/11299833.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值