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 ] 内的序列是否相同
-
这个题确实有几个可圈可点的地方
- 首先,他让每个数增加到到 2 16 2^{16} 216 变为0,相当于又引入了一个模数。而之前的字符串哈希是自然溢出取模,然而 2 16 2^{16} 216 与 2 64 2^{64} 264 并不互质,因此这样子会错。字符串哈希不仅要保证 base 和 mod 互质,还要保证取模的数也要互质。
- 这道题用字符串哈希第二种方法最简单,即 c 1 p + c 2 p 2 + . . . + c m p m c_1p+c_2p^{2}+...+c_mp^m c1p+c2p2+...+cmpm 表示字符串的哈希值。
- 小心三个地方,第一个是modify的时候小心,在要修改完整;第二个是取模的时候,出现减法,一定要 + mod 再 % mod,防止出现负数;第三个是,字符串哈希,对一段区间加上某一个数时,是加 base 幂的区间和 s u m p [ r ] − s u m p [ l − 1 ] sump[r] - sump[l-1] sump[r]−sump[l−1],而不是 p [ r ] − p [ l − 1 ] p[r] - p[l - 1] p[r]−p[l−1].
#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 n≥a1+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} p1c1∗p2c2∗...∗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;
}