1 条题解
-
0
自动搬运
来自洛谷,原作者为

Alarm5854
Stop Smoking!搬运于
2025-08-24 21:25:00,当前版本为作者最后更新于2020-01-31 21:23:00,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
这道题目是一道数论题。需要分类讨论。
当 时,显然答案是 ,这是比较容易的,每一次想办法把它平均分成3份,若无法平均分成3份,就将其分为 $\lceil n/3\rceil,\lceil n/3\rceil,n-2\lceil n/3\rceil$,最坏情况下称量次数为。
但是别想当然,当 时,答案可不是 (我曾经这样想过,结果就听取WA声一片了),而是 ,具体怎么证明如下(我参考了一位大佬写的博客):
假如称一次就要找到有问题的那枚硬币,显然不可能,因为硬币要大等于3枚才可以称量。
假如称两次,只能3枚(左1,右1,余1),4枚硬币就不行了(左1,右1,余2,若左右两端平衡,则用左右两端的硬币和剩下的两枚硬币比较,先比较出轻重,接下来还要一次比较哪枚硬币出问题,要3次)。
假如称3次,左右两端最多可以各放4枚而不是3枚,因为只要有3枚标准硬币,就可以2次找到4枚硬币的问题所在,方法如下:左边放3枚标准硬币,右边放3枚可能存在问题的硬币,剩下一枚可能存在问题的硬币,若天平平衡,则问题显然出在那一枚硬币中,和标准硬币比较轻重即可,否则,可先知道轻重,问题转为知道轻重。所以3次最多可称12枚。
类似的,12枚标准硬币可以3次找到13枚硬币的问题,方法如上,所以称4次可称39枚硬币。以此类推,称 次可找到 枚硬币,解 ,可得 ,由于要称整数次,所以要称 次。然而,知道方法了,也不一定能 A(Python 党请无视),因为它还卡高精,暴力地运算的单次时间复杂度为 ,看上去可以卡过 ,但是这个毒瘤的出题人还设置了多组数据,总时间复杂度为 ,就是这个可恶的 ,导致暴力地运算只能得 40~60 分(虽然 ),只能另外想办法。我不会其他办法,就用压位高精,每一个压 9 位,这样优化了常数,时间复杂度为 ,别小看这个 ,它可将速度提升了 倍,使得这样做可以通过本题,当然,未吸氧时最慢的点跑了0.9s。上代码(有点压行)。
#include<bits/stdc++.h> #define int long long//压9位,不开LL见祖宗 using namespace std; const int base = 1000000000LL; int T;//每一位都小于10^9 const int pow18 = 387420489LL;//进一步优化常数,预处理出最接近10^9的3的n次幂的数,即3^18 FILE *fin, *fout; struct bigint { int a[1122];//ceil(10000.0/9)=1112,所以数组稍微开大一点即可,a[0]储存位数 bigint(){memset(a, 0, sizeof(a));} inline int& operator [](int n) {//重载[]运算符,之后方便写 return a[n]; } inline friend bool operator <(bigint a, int x) {//比较bigint和int的大小 if (a[0] > 2) return 0; int y = 0;//由于这里出现的最大整数不超过10^18,所以可以这样做节省时间 for (int i = a[0]; i; --i) y = y * base + a[i];//最多两位 return y < x; } inline friend bigint operator +(bigint a, int x) {//高精加低精 int i; a[i = 1] += x;//相当于i = 1, a[i] += x; while (a[i] >= base) ++a[i + 1], a[i] -= base, ++i;//处理进位 if (i > a[0]) a[0] = i; return a; } inline friend bigint operator *(bigint a, int x) {//高精乘低精 for (int i = 1; i <= a[0]; ++i) a[i] *= x;//先暂时不管进位,乘完以后再处理进位 for (int i = 1; i <= a[0]; ++i) a[i + 1] += a[i] / base, a[i] %= base;//处理进位 while (a[a[0] + 1]) ++a[0], a[a[0] + 1] += a[a[0]] / base, a[a[0]] %= base;//可能出现新的一位 while (!a[a[0]]) --a[0]; return a; } inline friend bigint operator /(bigint a, int x) {//高精除低精 for (int i = a[0]; i; --i) { if (i > 1) a[i - 1] += a[i] % x * base; a[i] /= x;//这一位除以x,下一位加上余数的10^9倍 }while (!a[a[0]]) --a[0]; return a; } inline friend int operator %(bigint a, int x) {//高精对低精取余,返回值int即可 int res = 0; for (int i = a[0]; i; --i) res = (res * base + a[i]) % x; return res; } }; inline int read(int &x) {//快读和快写 char c = 0; int f = x = 0; while (c < 48 || c > 57) { if (!~c) return 0; if (c == '-') f = 1; c = fgetc(fin); } while (c > 47 && c < 58) x = (x << 3) + (x << 1) + (c & 15), c = fgetc(fin); if (f) x = -x; return 1; } inline int read(string &s) { s = ""; char c = 0; while (c == 32 || c == 10 || c == 13 || c == 0) c = fgetc(fin); if (c == -1) return 0; while (!(c == 32 || c == 10 || c == 13 || c == 0 || c == -1)) s += c, c = fgetc(fin); return 1; } inline int read(bigint &x) {//压位高精最困难的地方就是读入,一定要小心,我有一次WA过 string s; read(s); int len = s.length();if (s == "") return 0; x[0] = len / 9; if (len % 9) {//如果有多出来的一位,还要单独处理 for (int i = 0; i < len % 9; ++i) x[x[0] + 1] = x[x[0] + 1] * 10 + s[i] - 48; s.erase(0, len % 9);//删掉前len%9位 } for (int i = 0; i < x[0]; ++i) for (int j = 0; j < 9; ++j) x[x[0] - i] = x[x[0] - i] * 10 + s[i * 9 + j] - 48;//注意这里的下标是x[x[0]-i],而不是x[i] if (len % 9) ++x[0]; return 1;//多出来的一位也要加上 } template<class T, class... Args> inline int read(T &x, Args&... args) { return read(x) + read(args...); } inline void write(int x) { if (x < 0) return putchar(45), (void)write(-x); if (x > 9) write(x / 10); fputc((x % 10) | 48, fout); } inline void write(char c) { fputc(c, fout); } inline int solve() { int k, p, flag = 0, res = 0; bigint n; read(k, p, n); if (!p) n = n * 2 + 3;//若p=0,则n=2n+3 while (!(n < pow18)) res += 18, flag |= n % pow18, n = n / pow18;//先18个18个加,这里没有定义过>=,只能用!(n<pow18) while (!(n < 3)) ++res, flag |= n % 3, n = n / 3;//剩余部分一个一个加 if (!(n < 2)) ++res; else if (flag) ++res; return res;//三行压成一行,其实也很容易理解,若最终结果大于1或有出现余数的,答案要加一 } signed main() { #ifdef ONLINE_JUDGE//在评测机上使用标准输入输出,在本机上使用文件输入输出,方便读入 fin = stdin; fout = stdout; #else fin = fopen("P1431.in", "rb"); fout = fopen("P1431.out", "wb"); #endif read(T); while (T--) write(solve()), write('\n');//这里再次压行,对于每组数据都要处理 return 0; }
- 1
信息
- ID
- 425
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者