33oj - 2025练习赛2 G
这道题。。。。。。。理解了其实不难。
\(\space\space\space\space\)↑
链接
题面:
33DAI 给他的 \(n\) 只猫咪进行了编号,第 \(i\) 只猫咪的编号为 \(a_i\) ,初始 \(a_i=i\),即编号就是从 \(1∼n\)。猫咪们很不喜欢当前的编号,要求 33DAI 对她们的编号进行修改,她们希望最后编号为 \(n−1\) 个 \(1\) 和一个 \(2\)(\(2\) 在什么位置无所谓)。
33DAI 想到了一个非常有趣的修改方法,他每次可以挑选两只猫咪,然后把其中一只猫咪的编号改为她除以另一只猫咪向上取整后的结果。
更具体的,他每次可以挑选两个 \(1∼n\) 之内的不同的数 \(p,q\),然后把 \(a_p\) 的值变为 \(⌈\frac{a_p}{a_q}⌉\)。现在猫咪给了 33DAI 最多 \(m\) 次修改机会,请你给一个修改方案。当然猫咪非常仁慈,所以你不需要让修改次数最小,只要不超过 \(m\) 即可。
方法1: 30分 (考场想法)
首先可以知道 \(\left \lfloor \frac{a}{a-1} \right \rfloor = 2\)
那么就可以先这样操作:
for (int i = n; i > 1; i--) // 顺序别弄错了。。。要是不理解,仔细想想
{
操作 i 和 i - 1
}
然后这样我们就把数组变成了:1,2,2,2,...,2,2
然后我们又知道 \(\left \lfloor \frac{1或2}{2} \right \rfloor = 1\)
所以:
for (int i = 1; i < n; i++) // 顺序同样别弄错
{
操作 i 和 i + 1
}
那么此时我们就用\(2\times n-2\)次让数组变成了 1,1,1,...,1,1,2
满足要求的结果,就是操作次数太多了,只能拿30分。
代码:
#include <iostream>
using namespace std;
int main()
{
int t;
cin >> t;
while (t--)
{
int n, m;
cin >> n >> m;
cout << 2 * n - 2 << endl;
for (int i = n; i > 1; i--)// 第一次操作,使得数组中有n-1个2和1个1
{
cout << i << ' ' << i - 1 << endl;
}
for (int i = 1; i < n; i++)// 第二次操作,使得数组中有n-1个1和1个2
{
cout << i << ' ' << i + 1 << endl;
}
}
return 0;
}
方法2:60分(考场想法)
首先我们想到可以把\(2\sim n-1\)全部和\(n\)分别做一次操作,可是变成1,1,1,...,1,1,n之后就没法把\(n\)变成\(2\)了。
所以我们要把尽可能多的数字和\(n\)做操作,但是要给\(n\)留一个“退路”。
我们知道$\left \lfloor \frac{a}{\left \lfloor \frac{a}{2} \right \rfloor + 1} \right \rfloor =2 $
所以我们用如下的代码从\(1\sim n\)中提出一个可以让\(n\)变成 \(2\) 的“退路”。
t = n
while (t > 2) // 到2就可以
{
b[t] = true; // 记录这个数字要成为n的“御用退路”
t = t / 2 + 1;
}
这样,我们把“退路”留好了。
然后我们把其他的数字与\(n\)做操作。
for (int i = 3; i <= n; i++)
{
if (!b[i])
{
操作 i 和 n
}
}
接着,我们用把整条“退路”上的数字都变成\(2\)
t = n;
while (t > 2)
{
操作 t 和 t / 2 + 1
t = t / 2 + 1;
}
最后,我们再走一遍,把整条路上的\(2\)都变成\(1\)。
t = n;
while (t > 2)
{
操作 t 和 t / 2 + 1
t = t / 2 + 1;
}
代码:
#include <iostream>
#include <vector>
#include <string.h>
using namespace std;
bool b[200001];
int main()
{
int t;
cin >> t;
while (t--)
{
memset(b, 0, sizeof(b));
int n, m;
cin >> n >> m;
vector<pair<int, int> > ans; // 暂存一下,不然不好算操作次数
int t = n;
while (t > 2) // 走一遍
{
b[t] = true; // 记录
t = t / 2 + 1;
}
for (int i = 3; i <= n; i++) // 操作非退路上的数字
{
if (!b[i])
{
ans.push_back({i, n});
}
}
t = n;
while (t > 2) // 走一遍,全变2
{
ans.push_back({t, t / 2 + 1});
t = t / 2 + 1;
}
t = n;
while (t > 2) // 再走一遍,全变1
{
ans.push_back({t, t / 2 + 1});
t = t / 2 + 1;
}
// 输出方案
cout << ans.size() << endl;
for (pair<int, int> i : ans)
{
cout << i.first << ' ' << i.second << endl;
}
}
return 0;
}
方法3:60分
先把\(3 \sim n - 1\) 的每个数字分别和 \(n\) 操作,是他们都变为1,最后把 \(n\)多次和 \(2\) 操作, 直到 \(n\) 也变成 \(1\)
代码:
#include <bits/stdc++.h>
using namespace std;
int main()
{
int t;
cin >> t;
while (t--)
{
vector<pair<int, int> > ans;
int n, m;
cin >> n >> m;
int k = n; // 由于除以2时每次输出的都是 n(位置),所以存一下实际值
for (int i = 3; i < n; i++)
{
ans.push_back( {i, n} ); // 3~n-1分别操作
}
while (k > 1) // 除到 n 为 1
{
ans.push_back( {n, 2} ); // 操作
k = (k + 1) / 2;
}
// 输出
cout << ans.size() << endl;
for (pair<int, int> i : ans)
{
cout << i.first << ' ' << i.second << endl;
}
}
return 0;
}
方法4:100分
看方法3,我们发现可以优化的地方在:一直除以2。
所以我们要在遍历\(3 \sim n - 1\)的时候每次尝试看看能不能让 \(n\) 和当前值操作几下(始终要满足当前值小于 \(n\) )。
注意:由于如果遍历的顺序是从 \(3\) 到 $n -1 $,那么会出现:当前值小于 \(n\) ,可是后面的值却大于 \(n\),就会出事。
所以:我们的顺序应该是:从 \(n-1\) 到 \(3\)
代码:
#include <bits/stdc++.h>
using namespace std;
int main()
{
int t;
cin >> t;
while (t--)
{
vector<pair<int, int> > ans; // 记录答案
int n, m;
cin >> n >> m;
int k = n; // 另外弄一个n,原因同方法3
for (int i = n - 1; i >= 3; i--) // 从n-1到3的顺序
{
while (i < k / i) // 要始终满足要求
{
k = (k + i - 1) / i; // 改n
ans.push_back( {n, i} ); // 记录答案
}
ans.push_back( {i, n} ); // 记录答案
}
while (k > 1) // 最后在多次除以2
{
ans.push_back( {n, 2} );
k = (k + 1) / 2;
}
// 输出答案
cout << ans.size() << endl;
for (pair<int, int> i : ans)
{
cout << i.first << ' ' << i.second << endl;
}
}
return 0;
}
完结撒花✿✿ヽ(v)ノ✿
好心人给个赞吧

浙公网安备 33010602011771号