题解 P1431 【找出伪币】
这道题目是一道数论题。需要分类讨论。
当
但是别想当然,当
假如称一次就要找到有问题的那枚硬币,显然不可能,因为硬币要大等于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 党请无视),因为它还卡高精,暴力地运算的单次时间复杂度为
#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;
}