2024牛客多校第9场
9#
B Break Sequence (B)#
似乎不止一次遇到线段树优化dp了,但仍然没做出来()
\(dp[i]\) 表示到 \(i\) 位置为止、将序列分成若干段的情况总数,一个显而易见的 \(n^2\) 做法是从 \(1\) 到 \(i - 1\) 枚举 \(j\),若 \(j + 1\) 至 \(i\) 为合法区间,则有 \(dp[i] = \sum\limits dp[j]\). 假设当前数字 \(a[i]\) 是第 \(k\) 次出现,对于集合 \(s\) 中的某个元素 \(x \leq k\),其对应的不合法位置是 \(a[i]\) 第 \(k - x\) 次至 \(k - x + 1\) 次出现之前的整个区间,因此可用线段树标记所有不合法区间,每次查询即寻找未被标记的 \(\sum\limits dp[j]\);维护 \(sum\) 作为当前区间标记值最小的位置权值和,即有每次标记完成后 \(dp[i] = sum[1]\). 一次操作中最多修改 \(2m\) 次标记(撤销先前标记、加上新标记),整体复杂度 \(m\cdot n\log n\).
线段树代码:
void pushup(int i) { cnt[i] = min(cnt[i * 2], cnt[i * 2 + 1]); if(cnt[i * 2] == cnt[i * 2 + 1]) { sum[i] = sum[i * 2] + sum[i * 2 + 1]; // 标记值相等时直接合并 if(sum[i] >= mo) sum[i] -= mo; } else { if(cnt[i * 2] < cnt[i * 2 + 1]) sum[i] = sum[i * 2]; // 取最小值计入sum[i]中 else sum[i] = sum[i * 2 + 1]; } } // 懒标记针对cnt,pushdown中并不需要更新sum void pushdown(int i) { if(lz[i]) { cnt[i * 2] += lz[i]; cnt[i * 2 + 1] += lz[i]; lz[i * 2] += lz[i]; lz[i * 2 + 1] += lz[i]; lz[i] = 0; } } // update单点修改sum,用于将循环中获得的dp值加入线段树中 void update(int i, int l, int r, int q, ll val) { if(l == r) { sum[i] = val; return; } pushdown(i); int mid = (l + r) / 2; if(q <= mid) update(i * 2, l, mid, q, val); else update(i * 2 + 1, mid + 1, r, q, val); pushup(i); } void modify(int i, int l, int r, int ql, int qr, int d) { if(ql <= l && qr >= r) { cnt[i] += d; lz[i] += d; return; } pushdown(i); int mid = (l + r) / 2; if(ql <= mid) modify(i * 2, l, mid, ql, qr, d); if(qr > mid) modify(i * 2 + 1, mid + 1, r, ql, qr, d); pushup(i); }
main函数中主要代码:
for(int i = 1; i <= n; i++) { scanf("%d", &a[i]); v[i].push_back(0); // vector保存每个数字出现的位置 } for(int i = 1; i <= m; i++) { scanf("%d", &s[i]); } update(1, 0, n, 0, 1); // 初值 for(int i = 1; i <= n; i++) { v[a[i]].push_back(i); int cur = v[a[i]].size() - 1; for(int j = 1; j <= m; j++) { if(cur >= s[j]) { modify(1, 0, n, v[a[i]][cur - s[j]], v[a[i]][cur - s[j] + 1] - 1, 1); // 不合法标记 } if(cur > s[j]) { modify(1, 0, n, v[a[i]][cur - s[j] - 1], v[a[i]][cur - s[j]] - 1, -1); // 撤销先前的标记 } } ll x = sum[1]; if(i == n) printf("%lld", x); update(1, 0, n, i, x); // 更新dp值 }
C Change Matrix (C)#
非常巧妙的数学题,似乎默认了数学可以交给队友()导致很久没练过gcd和排列组合以外的数学题了,现在这么菜是有原因的。
对于内部元素不同的矩阵,计算其行/列倍乘后的总和比较困难,若矩阵中元素完全一致则相对简单。假设 \(n\cdot m\) 的矩阵初始值为 \(1\),第 \(i\) 行倍乘系数为 \(a_i\),第 \(j\) 列倍乘系数 \(b_j\),则有:\(sum = (a_1b_1 + a_1b_2 + ... + a_1b_m) + (a_2b_1 + a_2b_2 + ...+ a_2b_m) + ... + (a_nb_1 + a_nb_2 + ... + a_nb_m) = a_1\cdot\sum\limits_{j = 1} ^ m b_j + a_2\cdot\sum\limits_{j = 1}^m b_j + ... + a_n\cdot\sum\limits_{j = 1} ^ m b_j = (\sum\limits_{i = 1} ^ n a_i)(\sum\limits_{j = 1} ^ m b_j)\);若初始值为 \(x\),则 \(sum' = sum \cdot x\);可通过 \(a,b\) 数列动态维护。
矩阵内部元素为 \(gcd(i, j)\),其分布具有规律性,如下图所示:
(图源b站牛客竞赛,雨巨讲题视频)
将整个矩阵视为多个小矩阵的叠加和,其中每个小矩阵的内部初值一致。图中颜色相同的位置含有相同的因数 \(i\),可将其当作一个小矩阵,设原矩阵中某位置元素为 \(x\),由于 \(x = \sum\limits_{\substack{i=1\\i|x}}^x \varphi(i)\)(这个证明好麻烦,不想写了,手算也能发现是成立的),小矩阵初值即欧拉函数 \(\varphi(i)\). 维护 \(n\) 个小矩阵的 \(a,b\) 倍乘系数,每个矩阵的大小为 \(n / i\),对第 \(k\) 行的倍乘即对 \(k\) 的所有因数 \(i\) 改变 \(a_{k / i}\),可 \(O(1)\) 计算答案变化量;对列的倍乘同理。
筛法求欧拉函数:
vector <int> p; bool no[N]; ll phi[N]; void ori(int n) { phi[1] = 1; for(int i = 2; i <= n; i++) { if(!no[i]) { p.push_back(i); phi[i] = i - 1; } for(int j = 0; j < p.size(); j++) { if(i * p[j] > n) break; no[i * p[j]] = 1; if(i % p[j] == 0) { phi[i * p[j]] = phi[i] * p[j]; break; } phi[i * p[j]] = phi[i] * phi[p[j]]; } } }
主要代码:
ll ans = 0; for(int i = 1; i <= n; i++) { r[i].resize(n / i + 1, 1); // r,c即题解中a,b数列 c[i].resize(n / i + 1, 1); rsum[i] = csum[i] = n / i; // 维护每个矩阵r,c之和 ans += rsum[i] * csum[i] % mo * phi[i] % mo; ans %= mo; for(int j = 1; i * j <= n; j++) { fac[i * j].push_back(i); // 预处理出1-n的所有因数 } } while(q--) { char ch = getchar(); while(ch != 'R' && ch != 'C') ch = getchar(); int x, t; scanf("%d%d", &x, &t); for(int i = 0; i < fac[x].size(); i++) { int j = fac[x][i]; if(ch == 'R') { rsum[j] += (t - 1) * r[j][x / j] % mo; rsum[j] %= mo; ans += (t - 1) * r[j][x / j] % mo * csum[j] % mo * phi[j] % mo; ans %= mo; r[j][x / j] *= t; r[j][x / j] %= mo; } else { csum[j] += (t - 1) * c[j][x / j] % mo; csum[j] %= mo; ans += (t - 1) * c[j][x / j] % mo * rsum[j] % mo * phi[j] % mo; ans %= mo; c[j][x / j] *= t; c[j][x / j] %= mo; } } printf("%lld\n", ans); }
即使队友很擅长的领域也要自己学明白啊QwQ 像之前认真练数据结构的时候,心里想的是万一哪天被他俩踢出来我得自力更生(?)
作者:Aderose_yr
出处:https://www.cnblogs.com/meowqwq/p/18364260
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
喵喵喵)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!