P1431 找出伪币

# 找出伪币

## 题目描述

给你一个装有 $n$ 枚硬币的袋子。$n$ 枚硬币中有一个是伪造的,并且那个伪造的硬币和真的硬币重量不一样。你的任务是找出这枚伪造的硬币。

为了帮助你完成这一任务,将提供一台可用来比较两组硬币重量的仪器,比如天平。利用这台仪器,可以知道两组硬币的重量是否相同。

## 输入格式

第一行,一个正整数 $T$,表示有 $T$ 组数据。

接着 $T$ 行,每行三个正整数 $k,p,n$。

$p=-1$:伪币偏轻;$p=1$:伪币偏重;$p=0$:你不知道伪币与真币的重量对比关系。

$n$ 为一个 $k$ 位十进制正整数(没有前导 $0$)。

## 输出格式

输出 $T$ 行,每行一个整数 $m$,表示至少要称量 $m$ 次就一定能找出伪币。

## 样例 #1

### 样例输入 #1

```
2
1 1 6
1 0 6
```

### 样例输出 #1

```
2
3
```

## 提示

对于 $40\%$ 的数据,$n\leq 10^5$。

对于 $100\%$ 的数据,$k\leq 10^4$,$3\lt n\lt 10^{10001}$,$1\leq T\leq 40$。

当 $p=0$ 时,你还需要知道伪币与真币相比是轻是重

#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;
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值