CSP-S模拟1 [斐波那契,数颜色,分组]
CSP-S 模拟1
洛谷上原题,不挂题面了。
A.斐波那契
P3938 斐波那契
观察上图,可发现规律:一个数的父亲等于这个数减去最大的小于它的斐波那契数。特殊的,如果这个数是斐波那契数,设这个数为 \(x\),\(x = fib(i)\),那它的父亲为 \(fib(i - 2)\)。
数据最大到 \(1e12\),打表发现,\(fib(60) > 1e12\)。所以预处理出来 \(fib(1) ... fib(60)\),再二分查找。
找两个点的最近公共祖先,用类似树剖的思想,一个个的往上跳。这棵树的最大深度是 \(60\),查到根总计 \(60\),均摊为 \(O(1)\)。我们把找父亲的复杂度记作 \(O(findfatℎer)\)。
总复杂度:\(O(30m×O(findfatℎer))\)。
最优复杂度:\(O(60m)\)。
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;
}
当然,还可以用平衡树,权值线段树,主席树搞过去。再提供一种暴力分块做法,块数在 \(230\) 左右基本上就没啥问题了。
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 分组
\(k = 1\) 时,外循环枚举每一只兔子的颜色值 \(a_i\),内循环设 \(x\) 从 \(1\) 枚举到 \(512\)(\(=\sqrt{131072×2}\)),看看有没有访问过 \((x^2-col_i)\) 这个值。如果访问过,则第i只兔子与当前组内其它兔子有冲突,需要清空访问标记并且单独新建一个分组;如果没访问过,则令第i只兔子加入当前分组。最后再打上访问标记即可。
\(k = 2\) 时,思路和 \(k = 1\) 相似,但要用到关系并查集。如果访问过 \((x^2-col_i)\),我们仍然可以试图将 \(a_i\) 加入当前分组,因为此时我们的分组里允许有两个小团体。我们此时可以使用并查集来维护敌对关系:fa[] 数组开2倍空间, find(1~n) 代表每只兔子所在小团体, find(n+1~2n) 代表每只的兔子所在小团体的敌对小团体。
在上述情况下当访问过 \((x^2-col_i)\) 时,我们先判断下颜色值为 \((x^2-col_i)\) 的兔子(开动态数组 vector 存下每一只当前分组内相同颜色的兔子编号。)和第 i 只兔子是否已经被规划进入同一小团体中。如果已经被规划入同一小团体,那么便不得不清空访问并新建分组。否则,将第 \(i\) 只兔子与颜色值为 \((x^2-col_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