2021 BNU Winter Training 8 (2020 CCPC威海)

2021 BNU Winter Training 8 (2020 China Collegiate Programming Contest, Weihai Site)

训练网址

A. Labyrinth

  • 听说会卡时间卡空间(动态申请空间),先挖坑吧

B. Rencontre

题解

  • 期望转化的很巧妙。算了,图论还是先挖坑吧。实在不想写。

C. Caesar Cipher

  • 给出一个长度为 n 的序列,接下来有 m 次操作,每次操作分为下列两种类型:

    • 1 l r:区间 [ l , r ] 内的所有数都加 1 并对 65536 取模,也就是 i ∈ [ l , r ] ,有 a[ i ] =( a[ i ] + 1 ) % 65536
    • 2 x y len:查询两段区间 [ x , x + len - 1 ] 和 [ y , y + len - 1 ] 内的序列是否相同
  • 这个题确实有几个可圈可点的地方

  1. 首先,他让每个数增加到到 2 16 2^{16} 216 变为0,相当于又引入了一个模数。而之前的字符串哈希是自然溢出取模,然而 2 16 2^{16} 216 2 64 2^{64} 264 并不互质,因此这样子会错。字符串哈希不仅要保证 base 和 mod 互质,还要保证取模的数也要互质。
  2. 这道题用字符串哈希第二种方法最简单,即 c 1 p + c 2 p 2 + . . . + c m p m c_1p+c_2p^{2}+...+c_mp^m c1p+c2p2+...+cmpm 表示字符串的哈希值。
  3. 小心三个地方,第一个是modify的时候小心,在要修改完整;第二个是取模的时候,出现减法,一定要 + mod 再 % mod,防止出现负数;第三个是,字符串哈希,对一段区间加上某一个数时,是加 base 幂的区间和 s u m p [ r ] − s u m p [ l − 1 ] sump[r] - sump[l-1] sump[r]sump[l1],而不是 p [ r ] − p [ l − 1 ] p[r] - p[l - 1] p[r]p[l1].
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7, P = 131;
const int maxn = 500010;
ll p[maxn], a[maxn], sump[maxn];
struct node {
    
    
	int l, r;
	ll Hash, mmax, add;
}tr[maxn * 4];
void pushup(int u) {
    
    
	tr[u].Hash = (tr[2 * u].Hash + tr[2 * u + 1].Hash) % mod;
	tr[u].mmax = max(tr[2 * u].mmax, tr[2 * u + 1].mmax);
}
void pushdown(int u) {
    
    
	auto& rt = tr[u], & l = tr[2 * u], & r = tr[2 * u + 1];
	if (rt.add) {
    
    
		//出现减法一定要 + mod 再 % mod
		l.add += rt.add, l.Hash = (l.Hash + (sump[l.r] - sump[l.l - 1]) * rt.add % mod + mod) % mod, l.mmax += rt.add;
		r.add += rt.add, r.Hash = (r.Hash + (sump[r.r] - sump[r.l - 1]) * rt.add % mod + mod) % mod, r.mmax += rt.add;
		rt.add = 0;
	}
}
void build(int u, int l, int r) {
    
    
	if (l == r) tr[u] = {
    
     l, r, a[l] * p[l] % mod, a[l], 0 };
	else {
    
    
		tr[u].l = l, tr[u].r = r;
		int mid = (l + r) / 2;
		build(2 * u, l, mid), build(2 * u + 1, mid + 1, r);
		pushup(u);
	}
}
void modify(int u, int l, int r) {
    
    
	if (l <= tr[u].l && tr[u].r <= r) {
    
    
		//这里的 Hash 别忘修改, 出现减法一定要 + mod 再 % mod
		tr[u].Hash = (tr[u].Hash + sump[tr[u].r] - sump[tr[u].l - 1] + mod) % mod;
		tr[u].add++;
		tr[u].mmax++;
	}
	else {
    
    
		pushdown(u);
		int mid = (tr[u].l + tr[u].r) / 2;
		if (l <= mid) modify(2 * u, l, r);
		if (r > mid) modify(2 * u + 1, l, r);
		pushup(u);
	}
}
void modify_mod(int u) {
    
    
	if (tr[u].mmax < 65536) return;
	if (tr[u].l == tr[u].r) {
    
    
		tr[u].mmax %= 65536;
		tr[u].Hash = tr[u].mmax * p[tr[u].l] % mod;
	}
	else {
    
    
		pushdown(u);
		modify_mod(2 * u), modify_mod(2 * u + 1);
		pushup(u);
	}
}
ll query(int u, int l, int r) {
    
    
	if (l <= tr[u].l && tr[u].r <= r) return tr[u].Hash;
	pushdown(u);
	int mid = (tr[u].l + tr[u].r) / 2;
	ll sum = 0;
	if (l <= mid) sum = (sum + query(2 * u, l, r)) % mod;
	if (r > mid) sum = (sum + query(2 * u + 1, l, r)) % mod;
	return sum;
}
bool check(int x, int y, int L) {
    
    
	if (x > y) swap(x, y);
	ll q1 = query(1, x, x + L - 1) * p[y - x] % mod;
	ll q2 = query(1, y, y + L - 1);
	if (q1 == q2) return true;
	return false;
}
int main() {
    
    
	int N, Q;
	scanf("%d%d", &N, &Q);
	p[0] = sump[0] = 1;
	for (int i = 1; i <= N; i++) {
    
    
		scanf("%lld", &a[i]);
		p[i] = p[i - 1] * P % mod;
		sump[i] = (sump[i - 1] + p[i]) % mod;
	}
	build(1, 1, N);
	while (Q--) {
    
    
		/*for (int i = 1; i <= N; i++) {
			printf("%lld ", query(1, i, i));
		}
		printf("\n");*/
		int op;
		scanf("%d", &op);
		if (op == 1) {
    
    
			int l, r;
			scanf("%d%d", &l, &r);
			modify(1, l, r);
			modify_mod(1);
		}
		else {
    
    
			int x, y, L;
			scanf("%d%d%d", &x, &y, &L);
			if (check(x, y, L)) printf("yes\n");
			else printf("no\n");
		}
	}
	/*for (int i = 1; i <= N; i++) {
		printf("%lld %lld\n", p[i], sump[i]);
	}*/
	return 0;
}

D. Steins;Game

  • 博弈论,需要计算 SG 值。学会了博弈论再来填坑。

E. Clock Master

  • T 组数据,每组数据给出一个 n n n ,找到若干个数字的和不超过 n,即 n ≥ a 1 + a 2 + . . . + a m n \ge a_1+a_2+...+a_m na1+a2+...+am, 使得 m a x ( l c m ( a 1 , a 2 , . . . , a m ) ) max(lcm(a_1,a_2,...,a_m)) max(lcm(a1,a2,...,am)) 最大。对结果取以自然对数为底的对数.
  • 这个题我一开始是考虑贪心做法,但是发现不管采用什么策略都很容易找到反例,因此猜测这道题很可能需要将所有答案搜索到取最值。但是3e4组数据,n最大时3e4,每次询问都搜索一遍可能很慢。因此很可能就是动态规划预处理出所有答案的做法。
  • 我们发现,当拆成的数,两两之间互质的时候,答案最大。两两互质的充要条件是把这 m 个数质因数分解后,两两之间没有相同的质因数。那么答案应该是长这个样子 p 1 c 1 ∗ p 2 c 2 ∗ . . . ∗ p n c n p_1^{c_1}*p_2^{c_2}*...*p_n^{c_n} p1c1p2c2...pncn. 那么,我们可以把它变成一个分组背包问题,每一组都是由 p 1 , p 2 , . . . , p m p^1, p^2, ..., p^m p1,p2,...,pm 组成,从中挑选一个数,这个是物品重量;因为是取了对数,因此物品价值就是就是对数之和。
  • 注意,这个题是凑成重量不超过 V,因此初始化 f 数组为 0,重量非负时状态才合法。
  • 而且为了减少运行时间,需要提前处理出 ln 的值和 f 数组。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int maxv = 30010;
double f[maxv], ln[maxv];
int primes[maxv], cnt;
bool st[maxv];
void pre(int N) {
    
    
	for (int i = 2; i <= N; i++) {
    
    
		if (!st[i]) primes[cnt++] = i;
		for (int j = 0; primes[j] <= N / i; j++) {
    
    
			st[primes[j] * i] = true;
			if (i % primes[j] == 0) break;
		}
	}
}

int main() {
    
    
	pre(30000);
	//fill(f, f + maxv, -1e18);
	//f[0] = f[1] = 0;
	for (int i = 1; i <= 30000; i++) ln[i] = log(i);
	//printf("%d\n", cnt);
	for (int i = 0; i < cnt; i++) {
    
    
		int p = primes[i];
		for (int j = 30000; j >= 0; j--) {
    
     
			for (int k = p; k <= j; k *= p) {
    
    
				f[j] = max(f[j], f[j - k] + ln[k]);
			}
		}
	}
	int T;
	scanf("%d", &T);
	while (T--) {
    
    
		int n;
		scanf("%d", &n);
		printf("%.10f\n", f[n]);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45812711/article/details/114067177