【2024.09.15】NOIP2024 赛前集训(2)
【2024.09.15】NOIP2024 赛前集训(2)
A
最大的难点戏剧性地变成了二叉搜索树是什么。
先根据已知序列把二叉树建出来,忘了二叉搜索树的移步 二叉搜索树 & 平衡树 - OI Wiki (oi-wiki.org)
根据题意, 想到dp计数,\(f[u]\) 表示 \(u\) 子树内的答案, 则有转移:
\]
注意!对于只有一个儿子的那些点就老老实实写分类讨论,不要写成一坨!写一坨必错!!!
时间复杂度 \(O(TN)\)
/*think twice, code once
n, m, type
ci
ui,vi,li
q
ai,bi
*/
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
#define int ll
using namespace std;
using ll = long long;
const int N = 1e3 + 5;
const int mod = 1e9 + 7;
int n, T, rt;
int a[N], ch[N][2], siz[N], inv[N], fac[N], f[N];
int quickmod(int x,int y){
int res = 1;
while(y){
if(y & 1) res = res * x % mod;
x = (x * x) % mod;
y >>= 1;
} return res;
}
inline int C(int x, int y){
return fac[x] * inv[y] % mod * inv[x-y] % mod;
}
void insert(int nw, int u){
if(u < nw){
if(!ch[nw][0]) ch[nw][0] = u;
else insert(ch[nw][0],u);
}
else {
if(!ch[nw][1]) ch[nw][1] = u;
else insert(ch[nw][1],u);
}
}
void dfs(int u){
if(!u) return ;
f[u] = 1;
siz[u] = 1;
dfs(ch[u][0]);
dfs(ch[u][1]);
siz[u] += siz[ch[u][0]] + siz[ch[u][1]];
if(ch[u][0] && ch[u][1]) f[u] = f[ch[u][0]] * f[ch[u][1]] % mod * C(siz[ch[u][0]] + siz[ch[u][1]], siz[ch[u][0]]) % mod;
else if(ch[u][0]) f[u] = f[ch[u][0]];
else if(ch[u][1])f[u] = f[ch[u][1]];
}
signed main(){
freopen("bst.in","r",stdin);
freopen("bst.out","w",stdout);
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
fac[0] = 1; F(i,1,1000) fac[i] = i * fac[i - 1] % mod;
inv[1000] = quickmod(fac[1000],mod-2);
G(i,999,1) inv[i] = inv[i + 1] * (i + 1) % mod;
cin >> T;
while(T --){
memset(ch, 0, sizeof(ch));
memset(siz, 0, sizeof(siz));
cin >> n;
F(i, 1, n) {
cin >> a[i];
if(i==1) rt = a[i];
else insert(rt, a[i]);
}
dfs(rt);
cout << (f[rt] + mod - 1) % mod << '\n';
}
return 0;
}
B
思维难度极高的一道小码量题目。
将 a 看作 1, b 看作 2, 那么 连续字母的替换 等效于 连续字母求模3意义下的和。
那么对于原串的任意合法区间(“合法”指存在相邻相同字符), 如果区间和模 3 意义下等于 1,则该区间最后可以通过一系列操作最后变为字符 a;等于 2 最后会变为字符 b;等于 0 则说明这段区间无法缩成一个字符(充分但不必要,例如aba)。
于是我们的任务便转化为:在原串上分段,然后把每段转化成一个字符,问求能得到多少种本质不同的字符串。
并且我们还能发现,我们实际上并不需要在划分的时候保证每个划分段内有相邻相同字符,因为 不合法 的划分可以转化为合法的划分!
注意:不合法仅指区间内不存在相邻相同字符,但是区间和模 3 仍不能为 0,不然无法缩成一个字符!
对序列求mod 3 意义下的前缀和 \(S\) ,然后用 dp 计数, \(f[i]\) 表示 \(1 \sim i\) 的所有划分方式, 得到的结果包括 1 和 2。记 \(val \in {0,1,2 }\)。 \(las[val]\) 表示上一个前缀和为 val 的位置, 有转移:
\]
只有 \(S[i] - val \ne 0\) 的情况才能转移, 因为如果 \(= 0\) 代表 \(las[val]+1 \sim i\) 这个区间没办法合并成一个字母,即 不合法。
时间复杂度 \(O(TN)\)。
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using namespace std;
using ll = long long;
const int N = 1e7 + 5;
const int mod = 1e9 + 7;
char s[N];
int a[N], las[N], f[N];
int T, n;
signed main(){
// freopen("alpha.in","r",stdin);
// freopen("alpha.out","w",stdout);
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >>T;
while(T--){
memset(f, 0, sizeof(f));
memset(las, 0, sizeof(las));
cin >> (s + 1);
bool tag = 0;
n = strlen(s + 1);
F(i, 1, n) {
a[i] = (a[i-1] + s[i]-'a' + 1) % 3;
if(i > 1 && s[i] == s[i-1]) tag = 1;
}
if(!tag){
cout << 1 << '\n';
continue;
}
F(i, 1, n){
f[i] = (a[i]>=1);
F(j, 0, 2) if(j != a[i]) f[i] = (f[i] + f[las[j]]) % mod;
//如果last[j] + 1 ~ i 的区间和(即 a[i] - j) 为0, 那么就会被缩成 ab 或 ba , 是不合法的
//所以只有区间和在模意义下是1或者2才能缩成a或者b
las[a[i]] = i;
}
cout << f[n] << '\n';
}
return 0;
}
C
给定起点 \(a[i]\), 最大长度不超过 \(b[i]\), 也就是最小化最大长度,熟悉的话容易想到 Kruskal 重构树.
那么就先建树,再找到最远能向上走到的祖先节点的位置 \(u\),答案就是以 \(u\) 为根的子树内出现最多次数的颜色编号(颜色信息都储存在叶子上)
对于这些颜色信息的合并,可以使用启发式合并保证复杂度。
一些细节:
- 倍增往上跳的时候和求 \(lca\) 一样,不能跳到 0 位置去了。
- 注意一下启发式合并
swap的具体手法,很有意思。 - 学习一下代码中的分类讨论,很清晰且不容易出错。
时间复杂度 \(O(NlogN)\)。
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
#define fi first
#define se second
using namespace std;
using ll = long long;
const int N = 5e5 + 105;
struct node1{
int u, v, w;
}e[N];
struct node2{ int ls, rs, w, num, col; }tr[N << 1];
unordered_map<int, int> cnt[N << 1];
int n, m, q, idx, type;
int c[N << 1], fa[N << 1], pa[N << 1][22];
inline int get(int x){
return (fa[x] != x) ? fa[x] = get(fa[x]) : fa[x];
}
void kruskal(){
sort(e + 1, e + m + 1, [&](node1 p, node1 q){return p.w < q.w;});
F(i, 1, n*2) fa[i] = i;
idx = n;
F(i, 1, m){
int fu = get(e[i].u), fv = get(e[i].v);
if(fu == fv) continue;
// cout << fu << ' ' << fv << '\n';
tr[++idx] = (node2){fu, fv, e[i].w};
fa[fu] = idx;
fa[fv] = idx;
}
}
void dfs(int u,int f){
pa[u][0] = f;
F(i,1,20) pa[u][i] = pa[pa[u][i-1]][i-1];
if(tr[u].ls){
int v = tr[u].ls;
dfs(v, u);
if(cnt[u].size() < cnt[v].size()) {
swap(cnt[u], cnt[v]);
//cnt 在 ask 的时候不会用到, 所以交换虽然不符合实际但是不影响答案
tr[u].num = tr[v].num;
tr[u].col = tr[v].col;
}
for(auto p : cnt[v]){
cnt[u][p.fi] += p.se;
if(tr[u].num < cnt[u][p.fi]){
tr[u].num = cnt[u][p.fi];
tr[u].col = p.fi;
}
else if(tr[u].num == cnt[u][p.fi]) tr[u].col = min(tr[u].col, p.fi);
}
cnt[v].clear();
}
if(tr[u].rs){
int v = tr[u].rs;
dfs(v, u);
if(cnt[u].size() < cnt[v].size()) {
swap(cnt[u], cnt[v]);
tr[u].num = tr[v].num;
tr[u].col = tr[v].col;
}
for(auto p : cnt[v]){
cnt[u][p.fi] += p.se;
if(tr[u].num < cnt[u][p.fi]){
tr[u].num = cnt[u][p.fi];
tr[u].col = p.fi;
}
else if(tr[u].num == cnt[u][p.fi]) tr[u].col = min(tr[u].col, p.fi);
}
cnt[v].clear();
}
if(!tr[u].ls && !tr[u].rs){
tr[u].num = 1;
tr[u].col = c[u];
cnt[u][c[u]] ++;
}
}
inline int ask(int x, int lim){
G(i,20,0) if(pa[x][i] != 0 && tr[pa[x][i]].w <= lim) x = pa[x][i];
return tr[x].col;
}
signed main(){
freopen("garden.in","r",stdin);
freopen("garden.out","w",stdout);
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n >> m >> type;
F(i, 1, n) cin >> c[i];
F(i, 1, m){
int u, v, w;
cin >> u >> v >> w;
e[i] = {u, v, w};
}
kruskal();
F(i, 1, idx) if(get(i) == i) dfs(i,0);
cin >> q;
int ans = 0;
F(i, 1, q){
int a, b;
cin >> a >> b;
if(type == 2) a ^= ans, b ^= ans;
cout << (ans = ask(a, b)) << '\n';
}
return 0;
}
D
考场上以为考的是最短路,但最后发现又是并查集。出现很多次类似的误判了……
对于能相互到达的区间,是可以相互合并的,于是我们先用一个 vector 把区间存起来,然后在线段树上合并。
注意!合并是根据给出的位置 \(pos\) 来把所有相关区间都合并掉。这也是为什么要先合并再插入新的区间。
这样的话就只剩下单向的区间且没有 被大区间完全包含的小区间了。最后可以直接按题目所给的定义判断。
有一些细节标注在代码里了,还是很受益的。
时间复杂度 \(O(NlogN)\)。
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
using ll = long long;
const int N = 1e6 + 5;
const int mod = 1e9 + 7;
int n, cnt = 0, num = 0;
int rk[N * 2], op[N], st[N], ed[N], fa[N * 2], lx[N], rx[N];
vector<int> cur[N];
inline int get(int x){
return (fa[x] == x) ? x : fa[x] = get(fa[x]);
}
void modify(int u, int l, int r, int pos){
for(auto x : cur[u]){
lx[num] = min(lx[num], lx[x]);
rx[num] = max(rx[num], rx[x]);
fa[get(x)] = num;
}
cur[u].clear();
if(l >= r) return ;
int mid = (l + r) >> 1;
if(pos <= mid) modify(u * 2, l, mid, pos);
else modify(u * 2 + 1, mid + 1, r, pos);
}
void addedge(int u, int l, int r, int x, int y){
if(x <= l && r <= y){
cur[u].emplace_back(num);
return ;
}
int mid = (l + r) >> 1;
if(x <= mid) addedge(u * 2, l, mid, x, y);
if(y > mid) addedge(u * 2 + 1, mid + 1, r, x, y);
}
void add(int l, int r){
modify(1, 1, cnt, l);
modify(1, 1, cnt, r);
if(lx[num] +1 < rx[num]) addedge(1, 1, cnt , lx[num] + 1, rx[num] - 1);
//极易出错!一定要先modify在addedge! 并且不能写成 :
//if(l + 1 < r) addedge(1, 1, cnt , l + 1, r - 1);
//因为在modify中有 "双向可达区间"之间 的合并
//所以为什么 lx[num] 要 +1 ?
}
signed main(){
freopen("interval.in","r",stdin);
freopen("interval.out","w",stdout);
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n;
F(i, 1, n){
cin >> op[i] >> st[i] >> ed[i];
if(op[i] == 1) rk[++cnt] = st[i], rk[++cnt] = ed[i];
}
sort(rk + 1, rk + cnt + 1);
cnt = unique(rk + 1, rk + cnt + 1) - rk - 1;
F(i, 1, n){
if(op[i] == 1){
++num;
fa[num] = num;
st[i] = lower_bound(rk + 1, rk + cnt + 1, st[i]) - rk;
ed[i] = lower_bound(rk + 1, rk + cnt + 1, ed[i]) - rk;
lx[num] = st[i], rx[num] = ed[i];
add(st[i], ed[i]);
}
else{
int u = get(st[i]), v = get(ed[i]);//注意st, ed在两种op值下的不同含义
if(u == v) cout << "YES\n";
else {
if((lx[v] < lx[u] && rx[v] > lx[u]) || (lx[v] < rx[u] && rx[v] > rx[u])) cout << "YES\n";//读清楚定义! 边界不能取等!
else cout << "NO\n";
}
}
}
return 0;
}
总结:熟悉概念,能够识别,学会运用!
【2024.09.15】NOIP2024 赛前集训(2)的更多相关文章
- Lean Data Innovation Sharing Salon(2018.09.15)
时间:2018.09.15地点:北京国华投资大厦
- 牛客网NOIP赛前集训营-提高组(第四场)游记
牛客网NOIP赛前集训营-提高组(第四场)游记 动态点分治 题目大意: \(T(t\le10000)\)组询问,求\([l,r]\)中\(k(l,r,k<2^{63})\)的非负整数次幂的数的个 ...
- 牛客网NOIP赛前集训营-提高组(第四场)B区间
牛客网NOIP赛前集训营-提高组(第四场)B区间 题目描述 给出一个序列$ a_1 \dots a_n$. 定义一个区间 \([l,r]\) 是好的,当且仅当这个区间中存在一个 \(i\),使得 ...
- 牛客网NOIP赛前集训营-提高组(第四场)B题 区间
牛客网NOIP赛前集训营-提高组(第四场) 题目描述 给出一个序列 a1, ..., an. 定义一个区间 [l,r] 是好的,当且仅当这个区间中存在一个 i,使得 ai 恰好等于 al, al+1, ...
- 牛客网NOIP赛前集训营-普及组(第二场)和 牛客网NOIP赛前集训营-提高组(第二场)解题报告
目录 牛客网NOIP赛前集训营-普及组(第二场) A 你好诶加币 B 最后一次 C 选择颜色 D 合法括号序列 牛客网NOIP赛前集训营-提高组(第二场) A 方差 B 分糖果 C 集合划分 牛客网N ...
- 牛客网CSP-S提高组赛前集训营Round4
牛客网CSP-S提高组赛前集训营 标签(空格分隔): 题解 算法 模拟赛 题目 描述 做法 \(BSOJ6377\) 求由\(n\)长度的数组复制\(k\)次的数组里每个连续子序列出现数字种类的和 对 ...
- NOIP赛前集训备忘录(含每日总结)(日更?。。。)
NOIP赛前集训备忘录(含每日考试总结) 标签: 有用的东西~(≧▽≦)/~啦啦啦 阅读体验:https://zybuluo.com/Junlier/note/1279194 考试每日总结(这个东西是 ...
- 牛客CSP-S提高组赛前集训营1
牛客CSP-S提高组赛前集训营1 比赛链接 官方题解 before:T1观察+结论题,T2树形Dp,可以换根或up&down,T3正解妙,转化为图上问题.题目质量不错,但数据太水了~. A-仓 ...
- 牛客网NOIP赛前集训营-提高组(第八场)
染色 链接:https://ac.nowcoder.com/acm/contest/176/A来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 524288K,其他语言10 ...
- [NowCoder]牛客网NOIP赛前集训营-提高组(第七场)
链接 A.中国式家长2 模拟题,毫无坑点 #include<bits/stdc++.h> #define REP(i,a,b) for(int i(a);i<=(b);++i) #d ...
随机推荐
- blender-点线面操作
- B. BerSU Ball
B. BerSU Ball 排序后考虑\(dp\),\(dp_{i,j}\)表示前\(i\)个男生和前\(j\)个女生能匹配的最大数. \[\begin{aligned} if(|a_i - b_j| ...
- 通过JMX监控weblogic服务
一.JMX简介 JMX是一种JAVA的正式规范,它主要目的是让程序有被管理的功能,那么怎么理解所谓的"被管理"呢?试想你开发了一个软件(如WEB网站),它是在24小时不间断运行的, ...
- JavaScript设计模式样例十四 —— 观察者模式
观察者模式(Observer Pattern) 定义:当一个对象被修改时,则会自动通知它的依赖对象.目的:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被 ...
- ubuntu 安装psycopg2包
psycopg2 库是 python 用来操作 postgreSQL 数据库的第三方库. 执行:pip3 install psycopg2==2.8.4 有可能会报错: Collecting psyc ...
- zabbix报错大全
zabbix报错 https://www.cnblogs.com/losbyday/category/876878.html作者总结的很全棒 1.在启动zabbix-agent 时系统日志输出 PID ...
- 工作 6 年,@Transactional 注解用的一塌糊涂
接手新项目一言难尽,别的不说单单就一个 @Transactional 注解用的一塌糊涂,五花八门的用法,很大部分还失效无法回滚. 有意识的在涉及事务相关方法上加@Transactional注解,是个好 ...
- three.js添加3d模型
three官方的几何体也就那么几个,想要生成各种各样的模型,其难度十分之大,这时引入外部模型也不失为一种选择.具体引入办法如下. 导入依赖 点击查看代码 import * as THREE from ...
- JS之AMD、CMD、CommonJS、ES6、UMD的使用笔记
前言 如下图: AMD与CMD的主要区别: 1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行.不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同). ...
- 合合信息AI图像内容安全新技术亮相WAIC2023,防范“生成式造假”
开年以来,多个图像生成软件在全球迅速蹿红,其作画逼真程度"技惊四座".AI一路"狂飙",让生成.篡改等多形式的图片伪造的门槛变得更低,由此引发的隐患也令人忧虑. ...