CSP-S模拟1 [斐波那契,数颜色,分组]
CSP-S 模拟1
洛谷上原题,不挂题面了。
A.斐波那契
P3938 斐波那契
观察上图,可发现规律:一个数的父亲等于这个数减去最大的小于它的斐波那契数。特殊的,如果这个数是斐波那契数,设这个数为 ,,那它的父亲为 。
数据最大到 ,打表发现,。所以预处理出来 ,再二分查找。
找两个点的最近公共祖先,用类似树剖的思想,一个个的往上跳。这棵树的最大深度是 ,查到根总计 ,均摊为 。我们把找父亲的复杂度记作 。
总复杂度:。
最优复杂度:。
Code
#include<cstdio> #include<algorithm> #define LL long long using namespace std; const int MAXM = 3e5 + 20, SIZE = 65; int m; LL fib[SIZE]; inline LL read(){ LL x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } void init(){ fib[1] = 1, fib[2] = 1; for(register int i = 3; i <= 60; i++) fib[i] = fib[i - 1] + fib[i - 2]; } int Find_Pos(LL num){ int l = 1, r = 60, ans = 0; while(l <= r){ int mid = (l + r) >> 1; if(fib[mid] <= num){ ans = mid; l = mid + 1; } else r = mid - 1; } return ans; } LL Get_Lca(LL a, LL b){ int pos_a, pos_b; while(a != b){ pos_a = Find_Pos(a), pos_b = Find_Pos(b); if(pos_a < pos_b) swap(a, b), swap(pos_a, pos_b); a -= fib[pos_a]; if(!a) a = fib[pos_a - 2]; } return a; } int main(){ init(); m = read(); for(register int i = 1; i <= m; i++){ LL a, b; a = read(), b = read(); printf("%lld\n", Get_Lca(a, b)); } return 0; }
B.数颜色
P3939 数颜色
也许上来就会想到数据结构,例如树套树,带修莫队等。不过如果真的写了任何一种上面的算法,应该是高级数据结构学傻了。
将兔子按照 (颜色, 位置) 进行双关键字排序。
操作 1 只需要在数组上二分查找;
操作 2 不会改变同种颜色兔子的相对位置,因此只需找到被交换的兔子改掉坐标即可。
Code
#include<cstdio> #include<vector> #include<algorithm> using namespace std; const int MAXN = 3e5 + 10; int n, m; int col[MAXN]; vector<int> pos[MAXN]; inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int main(){ n = read(), m = read(); for(register int i = 1; i <= n; i++){ col[i] = read(); pos[col[i]].push_back(i); } for(register int i = 1; i <= m; i++){ int opt; opt = read(); if(opt == 1){ int l, r, c; l = read(), r = read(), c = read(); int pos_l = lower_bound(pos[c].begin(), pos[c].end(), l) - pos[c].begin(); int pos_r = upper_bound(pos[c].begin(), pos[c].end(), r) - pos[c].begin(); printf("%d\n", pos_r - pos_l); } else{ int x, y; x = read(), y = x + 1; if(y > n) y = 1; if(col[x] == col[y]) continue; int pos_x = lower_bound(pos[col[x]].begin(), pos[col[x]].end(), x) - pos[col[x]].begin(); int pos_y = lower_bound(pos[col[y]].begin(), pos[col[y]].end(), y) - pos[col[y]].begin(); swap(pos[col[x]][pos_x], pos[col[y]][pos_y]); swap(col[x], col[y]); } } return 0; }
当然,还可以用平衡树,权值线段树,主席树搞过去。再提供一种暴力分块做法,块数在 左右基本上就没啥问题了。
Code
#include<cmath> #include<cstdio> #include<algorithm> #define register using namespace std; const int MAXN = 3e5 + 10, SIZE = 410; int n, m, siz, tot; int col[MAXN]; int belong[MAXN], st[SIZE], ed[SIZE]; unsigned short temp[SIZE][MAXN]; inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } inline void write(int num){ if(!num){ putchar('0'); return; } if(num < 0) putchar('-'), num = -num; int stk[15], top = 0; while(num) stk[++top] = num % 10, num /= 10; while(top) putchar(stk[top--] + 48); } void init(){ if(n > 160000) tot = 225, siz = n / tot; else tot = sqrt(n), siz = n / tot; for(register int i = 1; i <= tot; i++){ st[i] = siz * (i - 1) + 1; ed[i] = siz * i; } ed[tot] = n; for(register int i = 1; i <= tot; i++){ for(register int j = st[i]; j <= ed[i]; j++){ belong[j] = i; temp[i][col[j]]++; } } } int Query_Col(int l, int r, int val){ int ans = 0; if(belong[l] == belong[r]){ for(register int i = l; i <= r; i++) if(col[i] == val) ans++; } else{ for(register int i = l; i <= ed[belong[l]]; i++) if(col[i] == val) ans++; for(register int i = st[belong[r]]; i <= r; i++) if(col[i] == val) ans++; for(register int i = belong[l] + 1; i < belong[r]; i++) ans += temp[i][val]; } return ans; } void Update(int pos){ int x = pos, y = pos + 1; if(y > n) y = 1; if(belong[x] != belong[y]){ temp[belong[x]][col[x]]--; temp[belong[x]][col[y]]++; temp[belong[y]][col[y]]--; temp[belong[y]][col[x]]++; } swap(col[x], col[y]); } int main(){ n = read(), m = read(); for(register int i = 1; i <= n; i++) col[i] = read(); init(); for(register int i = 1; i <= m; i++){ int opt; opt = read(); if(opt == 1){ int l, r, c; l = read(), r = read(), c = read(); write(Query_Col(l, r, c)), putchar('\n'); } else{ int x; x = read(); Update(x); } } return 0; }
C.分组
P3940 分组
时,外循环枚举每一只兔子的颜色值 ,内循环设 从 枚举到 (),看看有没有访问过 这个值。如果访问过,则第i只兔子与当前组内其它兔子有冲突,需要清空访问标记并且单独新建一个分组;如果没访问过,则令第i只兔子加入当前分组。最后再打上访问标记即可。
时,思路和 相似,但要用到关系并查集。如果访问过 ,我们仍然可以试图将 加入当前分组,因为此时我们的分组里允许有两个小团体。我们此时可以使用并查集来维护敌对关系:fa[] 数组开2倍空间, find(1~n) 代表每只兔子所在小团体, find(n+1~2n) 代表每只的兔子所在小团体的敌对小团体。
在上述情况下当访问过 时,我们先判断下颜色值为 的兔子(开动态数组 vector 存下每一只当前分组内相同颜色的兔子编号。)和第 i 只兔子是否已经被规划进入同一小团体中。如果已经被规划入同一小团体,那么便不得不清空访问并新建分组。否则,将第 只兔子与颜色值为 的兔子分别互相加入对方的敌对集合即可。
Code
#include<stack> #include<cstdio> #include<vector> using namespace std; const int MAXN = 140010; int k, n, tot, last; int col[MAXN]; stack<int> ans; vector<int> vis[MAXN << 1]; inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } struct Union_Set{ int fa[MAXN << 1]; void init(int m){ for(register int i = 1; i <= m; i++) fa[i] = i; } int Find(int x){ return fa[x] == x ? x : fa[x] = Find(fa[x]); } }U; void Clear(int &pre, int l){ for(register int i = pre - 1; i > l; i--) vis[col[i]].clear(); pre = l + 1; ans.push(pre - 1); } void Solve1(){ for(register int i = n; i >= 1; i--){ bool IsFind = true; for(register int j = 1; j <= 512; j++){ if(j * j - col[i] >= 0 && vis[j * j - col[i]].size()){ IsFind = false; break; } } if(!IsFind) Clear(last, i); vis[col[i]].push_back(1); } } void Solve2(){ U.init(n << 1); for(register int i = n; i >= 1; i--){ for(register int j = 1; j <= 512; j++) if(j * j - col[i] >= 0 && vis[j * j - col[i]].size()) for(register int k = 0; k < vis[j * j - col[i]].size(); k++){ int t = vis[j * j - col[i]][k]; if(U.Find(i) == U.Find(t)) Clear(last, i); else{ U.fa[U.Find(i + n)] = U.Find(t); U.fa[U.Find(t + n)] = U.Find(i); } } vis[col[i]].push_back(i); } } void Print(){ printf("%ld\n", ans.size() + 1); while(!ans.empty()) printf("%d ", ans.top()), ans.pop(); } int main(){ n = read(), k = read(), last = n + 1; for(register int i = 1; i <= n; i++) col[i] = read(); if(k == 1) Solve1(); else Solve2(); Print(); return 0; }
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16654193.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理