BZOJ 2909——Bipartite Numbers【数论】

题目传送门


题目描述

Bipartite Number是这样的一个正整数,他只能由两段相同的数组成,如44444411,10000000, 5555556,41,而4444114,44444则不是。
现给你一个N,让你找到最小的Bipartite NumberX,使得X=NK(K是正整数),由于答案X可能很大,你需将其缩写转换为4个数字输出,即(高位数字长度,高位数字,低位数字长度,低位数字),如44444411为6 4 2 1,88888888888800000为12 8 5 0


输入格式

若干行,每行一个数字,
最后以0结束结尾。


输出格式

对于每一行输入输出4个数字用一个空格隔开,行末无多余空格。


样例输入

125
17502
2005
0

样例输出

1 5 2 0
4 7 4 8
3 2 3 5


100%:N<=10^5 每个测试点不会超过20组数据

题解:

预先计算好两个数列。

数列1:a[1]=1,a[i+1]=(a[i]*10+1) mod N,i = 1,2,3,… a[i]就是连续i个1除以N的余数。
数列2:b[0]=1,b[i+1]=b[i]*10 mod N,i = 0,1,2,3,… b[i]就是10的i次方除以N的余数。

有了这两个数列,只需用O(1)的时间就可以计算任意二段数除以N的余数。假设二段数是由
m个s和n个t组成,二段数m,s,n,t除以N的余数等于(a[m]*b[n]*s+a[n]*t) mod N。

接下来只要枚举m,s,n,t就可以了。按照(m+n)的值从小到大枚举,(m+n)确定后枚举m,则n
可以直接计算出来,不需要枚举。m和n确定之后枚举s和t。一旦找到解,后面的(m+n)值就不
需要继续枚举下去了。为了减少不必要的枚举,可以先进行判断。例如,N是10的倍数时,t只
能取0。


AC-Code:

#include<cstdio>  
#include<cstring>  
#include <algorithm>
#define maxn 100100
#define rint register int
#define inf 30000
using namespace std;

short mxva[21][maxn * 9];
short mnva[21][maxn * 9];
int n, tn;
int p1[maxn * 21];
int p2[maxn * 21];
int p_cnt = 0;
int tmp[11];
int tmp_cnt = 0;
int a, b, c, d;
int i, j, x, y;
inline bool check() {
	return !(x - y == a && i == b && y == c && j == d);
}
int main() {
	while (scanf("%d", &n)) {
		if (n == 0) break;
		tmp_cnt = 0;
		int ttn = n;
		while (n) tmp[++tmp_cnt] = n % 10, n /= 10;
		bool fl = 0;
		a = 1, b = tmp[tmp_cnt], c = 0, d = 0;
		for (i = tmp_cnt - 1; i >= 1; --i) {
			if (fl && tmp[i] != tmp[i + 1]) { a = -1, b = -1, c = -1, d = -1; break; }
			else if ((!fl) && tmp[i] != tmp[i + 1]) fl = 1, d = tmp[i];
			if (fl) c++;
			else a++;
		}
		if (!fl) a = -1, b = -1, c = -1, d = -1;
		n = ttn; tn = n * 9;
		int now = 1;
		for (x = 1;; x++) {
			now = now * 10 % tn;
			for (i = 1; i < 10; i++) {
				int nowj = 11, nowy = -1;
				for (j = 0; j < i; j++) {
					int tmp = (i * now - j + tn) % tn;
					if (mxva[i - j + 10][tmp]) {
						y = mxva[i - j + 10][tmp];
						if (!check()) continue;
						if (y > nowy || (y == nowy && j < nowj)) nowj = j, nowy = y;
					}
				}
				if (nowj != 11) { y = nowy, j = nowj; goto find; }
				nowy = inf;
				for (j = i + 1; j < 10; j++) {
					int tmp = (i * now - j + tn) % tn;
					if (mnva[i - j + 10][tmp]) {
						y = mnva[i - j + 10][tmp];
						if (!check()) continue;
						if (y < nowy || (y == nowy && j < nowj)) nowj = j, nowy = y;
					}
				}
				if (nowj != 11) { y = nowy, j = nowj; goto find; }
			}
			for (i = -10; i <= 10; i++) {
				int tt = (i * now % tn + tn) % tn;
				mxva[i + 10][tt] = max((short)x, mxva[i + 10][tt]);
				if (!mnva[i + 10][tt]) mnva[i + 10][tt] = inf;
				mnva[i + 10][tt] = min((short)x, mnva[i + 10][tt]);
				p1[++p_cnt] = i + 10;
				p2[p_cnt] = tt;
			}
		}
	find:
		printf("%d %d %d %d\n", x-y, i, y, j);
		for (i = 1; i <= p_cnt; i++) mxva[p1[i]][p2[i]] = 0, mnva[p1[i]][p2[i]] = 0;
		p_cnt = 0;
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值