2022.9.2———HZOI【CSP-S模拟1】游寄
$ Write\ In\ Front $
这次考试三道题打的都是暴力。。或者子任务分治一类。。
临近csp-s
但是考场上没 \(A\) 题是很危险的标志啊。。
T1:斐波那契 T2:数颜色 T3:分组
\(成绩综述\)
$ Rank:\ 38 / 42 $,我菜菜
\(T1\) 究极暴力建树
\(T2\) 简单前缀和还挂了\(20pts\)草
\(T3\) 胡了一个 \(XIN队\) 上去好像还打挂了
期望得分 \(70 + 30 + 10 = 110\)
但是最后就如图所示 \(70 + 10 + 8 = 88\)
题
$\mathfrak{T1}\ 斐波那契 $
这个题的数据范围给我的第一印象是打表找规律、数学
似乎确实是这样的,但是并不是 \(O(1)\) 的数学。。。
首先明确一下,\(f(61)\) 就已经达到了 \(10^{12}\) 的级别(我这里的斐波那契数列 \(f_1 = f_2 = 1\))
对于某一个兔子\(x\),我们尝试考虑他的父亲会是谁
把树画出来,我们可以发现神奇的地方(慢慢画,画的比原题给的树多一个月就行),电脑上实在是太难画了,所以各位草稿纸上请
首先胡一个斐波那契数列上去,毕竟题目这么写的
然后仔细观察
(不是特别好看谔谔)
(其中标粗体的是斐波那契数列的数)
然后和树有啥关系呢,我们注意到,有这么一些对父子: 4和1
; $\ \ $ 6和1
,7和2
; $\ \ $ 9和1
,10和2
,11和3
,12和4
\(......\)
然而他栗然地发了大冷。
所以 打表出结论:
-
对于 \(i \ge 5\) ,有:\((f_{i-1}, f_i)\) 这个开区间内的数字,他们的父亲依次是 \(1,2,......,f_i-f_{i-1}-1\)。(我这里的斐波那契数列 \(f_1 = f_2 = 1\) 奥)
-
对于 \(\text{Fibonacci}\) 数的父亲,可以发现对于 \(i \ge 3\), \(f_i\) 的父亲为 \(f_{i-2}\) 。也是看出来的。(请务必注意我这里指的斐波那契数列 \(f_1 = f_2 = 1\))
更准确地描述:
对于一个兔子编号为\(x\) :
-
若 \(x\) 为斐波那契数且不为 \(1\):设 \(plc\) 为 \(x\) 在斐波那契数列中的下标,他的父亲为 \(f_{plc-2}\)
-
若 \(x\) 不为斐波那契数:设 \(plc\) 为 \(x\) 在斐波那契数列中第一个小于 \(x\) 的斐波那契数的下标,则 \(x\) 的父亲为 \(x-f_{plc}\)
按照这个规律将 \(10^6\) 以内的数全部扫一遍然后暴力建树,然后暴力建树的 \(70pts\) 就到手啦~
当然了这个规律也是正解的一部分。正解是 \(a_i\) 和 \(b_i\) 往上跳父亲,然后最先跳到同一个父亲就是答案。正确性显然。
考虑如何实现:
首先找 \(plc\) 我们很容易就能想到对斐波那契数列进行二分查找,因为斐波那契数列是单增的。用数组的 lower_bound
即可实现。手写二分也行。
判断\(x\)是否为斐波那契数的话,直接判断 f[plc] == x
就行,没必要像我这个sb一样开一个 unordered_map
然后 \(T\) 飞
注意到对于每一组询问 \((a_i, b_i)\),不妨设 \(b_i > a_i\)(编号)。
-
如果 \(b_i\) 在 \(a_i\) 的子树上,显然是让 \(b_i\) 往上跳才能跳到正确答案 \(a_i\)。
-
如果不在,那么是谁先跳都无妨。
那么显然我们直接都优先让编号更大的先跳不就完了。。。
至此本题就基本做完了,代码实现也很简单。这个题主要的为了阅读体验我把之前代码和最终代码分开放吧
T1
#include <iostream>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x) freopen(#x ".in", "r", stdin), freopen(#x ".out", "w", stdout);
#define re register int
#define char_phi signed
#define MARK cout << "###"
#define MARKER "@@@"
#define LMARK "!!!~~~"
#define ZY " qwq "
#define _ ' '
#define Endl cout << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL), cerr.tie(NULL);}
/*
向同深度的跳跃。
浮华。
唱,跳,rap,篮球,music.
*/
long long Q, ai, bi, faa, plca;
long long f[65];
void work(){
cin >> Q;
f[1] = f[2] = 1;
for (re i = 3 ; i <= 61 ; ++ i)
f[i] = f[i-1] + f[i-2];
while (Q --){
cin >> ai >> bi;
while (ai != bi){
if (ai < bi)
swap(ai, bi);
plca = lower_bound(f+2, f+61+2, ai)-f-1;
if (ai == f[plca])
plca ++, ai = f[plca-2];
else
ai = ai-f[plca];
}
cout << ai << '\n';
}
}
// #define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(a);
#endif
Fastio_setup();
work();
return GMY;
}
重构1
%:pragma GCC optimize(3)
#include <iostream>
#include <unordered_map>
#include <set>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x) freopen(#x ".in", "r", stdin), freopen(#x ".out", "w", stdout);
#define re register int
#define char_phi signed
#define MARK cout << "###"
#define MARKER "@@@"
#define LMARK "!!!~~~"
#define ZY " qwq "
#define _ ' '
#define Endl cout << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 300005
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL), cerr.tie(NULL);}
char isa, isb;// is_fibonacci
long long Q, ai, bi, plca, plcb, faa, fbb, final_ans;
long long f[65];
unordered_map<long long, bool> mp;
// multiset<long long, greater<long long>>::iterator it;
void work(){
cin >> Q;
f[0] = f[1] = f[2] = 1;
for (re i = 3 ; f[i-1] <= 1000000000000ll ; ++ i)
f[i] = f[i-1]+f[i-2], mp[f[i]] = true;
/* for (re i = 1 ; i <= 61 ; ++ i)
cout << f[i] << '\n';*/
while (Q --){
//if (Q <= 200000)
//cerr << "很勇嘛," << '\n';
// cerr << "您在哪超时的???" << '\n';
cin >> ai >> bi;
if (ai < bi)
swap(ai, bi);
faa = ai, fbb = bi; final_ans = 0;
multiset<long long, greater<long long>> a, b;
// a.clear(), b.clear();
a.insert(ai), b.insert(bi);
// cerr << '\n' << '\n' << '\n';
while (faa != fbb){
// sleep(1), cerr << "Here we are, Nick Of Time!" << '\n';
plca = lower_bound(f+2, f+60+2, ai)-f-1, plcb = lower_bound(f+2, f+60+2, bi)-f-1;
if (mp[ai] == true)
plca ++, faa = f[plca-2];
else
faa = ai-f[plca];
if (mp[bi] == true)
plcb ++, fbb = f[plcb-2];
else
fbb = bi-f[plcb];
sleep(1), cerr << ai << _ << plca << _ << faa << _ << _ << _ << bi << _ << plcb << _ << fbb << '\n';
ai = faa, bi = fbb;
}
while (faa != 1){
plca = lower_bound(f+2, f+60+2, ai)-f-1;
if (mp[ai] == true)
plca ++, faa = f[plca-2];
else
faa = ai-f[plca];
ai = faa;
a.insert(faa);
}
while (fbb != 1){
plcb = lower_bound(f+2, f+60+2, bi)-f-1;
if (mp[bi] == true)
plcb ++, fbb = f[plcb-2];
else
fbb = bi-f[plcb];
bi = fbb;
b.insert(fbb);
}
for (auto it : a)
if (*b.lower_bound(it) == it)
{final_ans = it; break;}
/*for (it = prev(a.end()) ; it != a.begin() ; it = prev(it))
if (b.find(*it) != b.end())
break;*/
if (final_ans == 0)
final_ans = 1;
cout << final_ans << '\n';
}
}
// #define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(a);
#endif
Fastio_setup();
work();
return GMY;
}
重构2
#include <iostream>
#include <unordered_map>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x) freopen(#x ".in", "r", stdin), freopen(#x ".out", "w", stdout);
#define re register int
#define char_phi signed
#define MARK cout << "###"
#define MARKER "@@@"
#define LMARK "!!!~~~"
#define ZY " qwq "
#define _ ' '
#define Endl cout << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 300005
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL), cerr.tie(NULL);}
char isa, isb;// is_fibonacci
long long Q, ai, bi, plca, plcb, faa, fbb, final_ans, id;
long long f[65], a[45];
unordered_map<long long, bool> mp;
// multiset<long long, greater<long long>>::iterator it;
/*
开心地把multiset的T掉之后就来写数组的
思路很明确
开搞
*/
inline int jiangxu_lower_bound(long long x){
long long l(1), r(a[0]), mid;
while (l <= r){
mid = ((l+r)>>1);
// if (mid < 0 or l < 0 or r < 0)
// cerr << "WTF" << '\n';
// if (mid > 40)
// cerr << mid << '\n';
if (x > a[mid])
r = mid-1;
else
l = mid+1;
}
// cout << "Binary " << l << _ << r << _ << mid << '\n';
return r;
}
// 956722026041
// 1548008755920
// 999377885466
// 1000000000000
void work(){
cin >> Q;
f[0] = f[1] = f[2] = 1;
for (re i = 3 ; f[i-1] <= 1000000000000ll ; ++ i)
f[i] = f[i-1]+f[i-2], mp[f[i]] = true;
/*for (re i = 1 ; i <= 61 ; ++ i)
cerr << f[i] << '\n';*/
while (Q --){
// cerr << Q << '\n';
//if (Q <= 200000)
//cerr << "很勇嘛," << '\n';
// cerr << "您在哪超时的???" << '\n';
cin >> ai >> bi;
if (ai < bi)
swap(ai, bi);
// cerr << ai << _ << bi << '\n';
faa = ai, fbb = bi; final_ans = a[0] = 0;
a[++ a[0]] = ai;
while (faa != 1){// 看来这里出现了RE
plca = lower_bound(f+2, f+61+2, ai)-f-1;// yes +61!
// if (Q == 299913)
// cerr << ai << _ << plca << _ << faa << _ << a[0] << '\n';
// if (Q == 299913 and a[0] > 10000)
// cerr << "WTF" << '\n';
if (mp[ai] == true)
plca ++, faa = f[plca-2];
else
faa = ai-f[plca];
ai = faa;
a[++ a[0]] = faa;
// if (a[0] > 40)
// cerr << a[0] << '\n';
}
// if (Q == 299913)
// {sleep(10), cerr << "Ivebenn there" << '\n';}// 嘿嘿,找RE好方法
if (a[0] == 1)
{cout << 1 << '\n'; continue;}
/*MARK;
for (re i = 1 ; i <= a[0] ; ++ i)
cout << a[i] << _;
Endl;*/
id = jiangxu_lower_bound(fbb);
if (a[id] == fbb and id != a[0]+1 and id != 0)
{final_ans = fbb; goto chu;}
while (fbb != 1){
plcb = lower_bound(f+2, f+61+2, bi)-f-1;
if (mp[bi] == true)
plcb ++, fbb = f[plcb-2];
else
fbb = bi-f[plcb];
id = jiangxu_lower_bound(fbb);
if (a[id] == fbb and id != a[0]+1 and id != 0)
{final_ans = fbb; break;}
// cout << "fbb:" << fbb << _ << id << _ << a[id] << '\n';
bi = fbb;
}
chu:{
cout << final_ans << '\n';
}
}
}
// #define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(a);
#endif
Fastio_setup();
work();
return GMY;
}
叨一句我踩的坑,我刚开始是把\(a_i\)和\(b_i\)的全部父亲都分别扔到两个
set
里然后反向遍历找答案,然后\(T\)飞。不过卡卡常仍然可以拿到\(80pts\)的好成绩,再往上就不清楚行不行了
$ \mathfrak{T2}\ 数颜色 $
这个题啊,可以发现如果说暴力的话数组是显然开不下的
想一个能开得下数组的方法
分块。
然后手动开了一下发现 a[550][300005]
还真的开得下,然后浅算一下,660011000B = 629.44MiB
,谔谔,似乎会 \(MLE\),但是据说评测机看的是实际内存占用,显然 \(a\) 数组不会全占满的罢
然后就开始写了
乐
\(\mathscr{UPD}\):\(\text{€€£}\) 好像并不是只看实际内存占用,所以尽量别开这么大!!!
然后就暴力维护了。暴力统计每个块中某个颜色的个数,然后查询的时候直接查就完了
但是发现了一些问题。如果块长为 \(\sqrt{n}\) 的话,是会 \(TLE\) 的
我们尝试扩大块长
然后扩来扩去发现 pow(n, 0.625)
挺不错的然后直接 \(A\) 掉(其实是看到学长的题解这么说的才知道(
T2
%:pragma GCC optimize(3)
#include <iostream>
#include <cmath>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x) freopen(#x ".in", "r", stdin), freopen(#x ".out", "w", stdout);
#define re register int
#define char_phi signed
#define MARK cout << "###"
#define MARKER "@@@"
#define LMARK "!!!~~~"
#define ZY " qwq "
#define _ ' '
#define Endl cout << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 300005
#define SQ 550
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL), cerr.tie(NULL);}
/*
看来我的前缀和想法没错
只不过分一下块
呃呃不对
直接统计块内某个颜色的个数
nsqrt(n)大约过去应该行
*/
int n, Q, blocknum, blocklen;
int L[SQ], R[SQ], col[N], belong[N];
int a[SQ][N];
inline int Query(int x, int y, int z){
int res(0);
if (belong[x] == belong[y]){
for (re i = x ; i <= y ; ++ i)
if (col[i] == z)
res ++;
return res;
}
else {
for (re i = x ; i <= R[belong[x]] ; ++ i)
if (col[i] == z)
res ++;
for (re i = L[belong[y]] ; i <= y ; ++ i)
if (col[i] == z)
res ++;
for (re i = belong[x]+1 ; i <= belong[y]-1 ; ++ i)
res += a[i][z];
return res;
}
}
inline void Update(int x){
if (belong[x] == belong[x+1]){
swap(col[x], col[x+1]);
}
else {
a[belong[x]][col[x]] --, a[belong[x+1]][col[x]] ++;
a[belong[x+1]][col[x+1]] --, a[belong[x]][col[x+1]] ++;
swap(col[x], col[x+1]);
}
}
void work(){
cin >> n >> Q;
for (re i = 1 ; i <= n ; ++ i)
cin >> col[i];// sb了,col都没读呢你怎么加的。。。
blocklen = pow(n, 0.625); blocknum = n/blocklen;
for (re i = 1 ; i <= blocknum ; ++ i){
L[i] = R[i-1]+1, R[i] = L[i]+blocklen-1;
for (re j = L[i] ; j <= R[i] ; ++ j)
belong[j] = i, a[i][col[j]] ++;
// cout << L[i] << _ << R[i] << '\n';
}
if (blocklen*blocknum < n){
blocknum ++, L[blocknum] = R[blocknum-1]+1, R[blocknum] = n;
for (re i = L[blocknum] ; i <= R[blocknum] ; ++ i)
belong[i] = blocknum, a[blocknum][col[i]] ++;
}
// cerr << blocknum << _ << blocklen << '\n';
// cout << blocknum << _ << L[blocknum] << _ << R[blocknum] << '\n';
int opt, x, y, z;
while (Q --){
cin >> opt;
if (opt == 1){
cin >> x >> y >> z;
cout << Query(x, y, z) << '\n';
}
else {
cin >> x;
Update(x);
}
}
}
// #define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(a);
#endif
Fastio_setup();
work();
return GMY;
}
$ \mathfrak{T3}\ 分组 $
这题主要就是一个贪心,但是我贺的题解
贪心我不会证(((
对于 \(K = 1\):
-
为了满足组数最少并且字典序最小,从后往前拓展组数,如果新加进来的一个数和之前这个组里的数有冲突,那么这个位置就该断掉然后新设立一个组。
-
设 \(x\) 为当前选择是否要加入的数,如果 [\(某个平方数-x\)] 这个值存在过,那么当前 \(x\) 加入当前组就不合法。也就是要断掉然后新设立一个组。
-
考虑如何判断是否存在过。考虑极限数据,之前一个数为 \(131072\),当前新加的这个数也是 \(131072\),相加正好为 \(512^2\)。所以(应该是这样 )从 \(1 \sim 512\) 循环枚举平方数 \(j \times j\),判断
used[j*j-col[i]] == true
即可。然后判断完了别忘了加进去used[j*j-col[i]] = true
。
对于 \(K = 2\):
- 这时候每个组都有了一对冲突的机会,也就是说,每个组里可以存在两个\(\mathbf{小团体内部互不冲突}\)的小团体。
“这启发了我”———狗屁不通文章生成器
-
还是从后往前尝试加入元素,但是如果当前元素加入后小组中出现了三个及以上的小团体,那就该在这个位置断开。
-
然后就可以用拓展域并查集维护了。
说实话的,这个题我也是不太懂。。尤其是拓展域并查集这块不会用,所以贺了一篇题解((
T3
#include <iostream>
#include <vector>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x) freopen(#x ".in", "r", stdin), freopen(#x ".out", "w", stdout);
#define re register int
#define char_phi signed
#define MARK cout << "###"
#define MARKER "@@@"
#define LMARK "!!!~~~"
#define ZY " qwq "
#define _ ' '
#define Endl cout << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 131101
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL), cerr.tie(NULL);}
/*
似乎是听懂了
*/
int n, K, lst;// 循环清空数组
char used[N<<1];
int col[N], fa[N<<1];
vector<int> ans, v[N<<1];
inline int find(int x){return ((fa[x] == x) ? (x) : (fa[x] = find(fa[x])));}
void work(){
cin >> n >> K;
for (re i = 1 ; i <= n ; ++ i)
cin >> col[i];
lst = n+1;
if (K == 2)
goto work2;
// K = 1
for (re i = n ; i >= 1 ; -- i){
for (re j = 1 ; j <= 512 ; ++ j)
if (j*j >= col[i])
if (used[j*j-col[i]] == true)
{goto CANT;}
used[col[i]] = true;
continue;
CANT:{
for (re j = i+1 ; j < lst ; ++ j)
used[col[j]] = false;
ans.push_back(i); lst = i+1;
used[col[i]] = true;
}
}
cout << ans.size()+1 << '\n';
for (re i = ans.size()-1 ; i >= 0 ; -- i)
cout << ans[i] << _;
Endl;
return ;
work2:{// K = 2
for (re i = 1 ; i <= (n<<1) ; ++ i)
fa[i] = i;
for (re i = n, tag, F ; i >= 1 ; -- i){
for (re j = 1 ; j <= 512 ; ++ j){
tag = j*j-col[i];
if (tag >= 0){
// cerr << tag << '\n';
if (v[tag].size() != 0){
// cerr << "谁 他 妈 买 R E" << '\n';
for (re k = 0 ; k < v[tag].size() ; ++ k){
F = v[tag][k];
if (find(F) == find(i)){// 这个位置加入后当前小组内出现了三个及以上的敌对团体 这个位置无法加入
for (re h = i+1 ; h < lst ; ++ h)
vector<int>().swap(v[col[h]]);
ans.push_back(i); lst = i+1;
break;
}
else {
fa[find(i+n)] = find(F);// 拓展域并查集
fa[find(F+n)] = find(i);
}
}
}
}
}
v[col[i]].push_back(i);
}
cout << ans.size()+1 << '\n';
for (re i = ans.size()-1 ; i >= 0 ; -- i)
cout << ans[i] << _;
Endl;
}
}
// #define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(a);
#endif
Fastio_setup();
work();
return GMY;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?