zoukankan      html  css  js  c++  java
  • 「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 imes17 imes 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
    ", 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
    ", 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
    ", 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
    ", 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;
    }
    
  • 相关阅读:
    8月15日
    【k8s】创建 tls 类型 Secret
    使用 openssl 生成 CA 证书
    【k8s】跨 Namespace 使用 Ingress
    Windows 和 Centos 导入 CA 证书
    使用 openssl 生成服务器证书
    【k8s】nginx ingress 配置 https
    【k8s】通过 https 访问 dashboard
    1012day人口普查系统
    8.3日志
  • 原文地址:https://www.cnblogs.com/15owzLy1-yiylcy/p/11299833.html
Copyright © 2011-2022 走看看