[UOJ#221][BZOJ4652][Noi2016]循环之美

试题描述

牛牛是一个热爱算法设计的高中生。在他设计的算法中,常常会使用带小数的数进行计算。牛牛认为,如果在 k 进制下,一个数的小数部分是纯循环的,那么它就是美的。现在,牛牛想知道:对于已知的十进制数 n 和 m,在 k 进制下,有多少个数值上互不相等的纯循环小数,可以用分数 x/y 表示,其中 1≤x≤n,1≤y≤m,且 x,y是整数。一个数是纯循环的,当且仅当其可以写成以下形式:a.c1˙c2c3…cp-1cp˙其中,a 是一个整数,p≥1;对于 1≤i≤p,ci是 kk 进制下的一位数字。例如,在十进制下,0.45454545……=0.4˙5˙是纯循环的,它可以用 5/11、10/22 等分数表示;在十进制下,0.1666666……=0.16˙则不是纯循环的,它可以用 1/6 等分数表示。需要特别注意的是,我们认为一个整数是纯循环的,因为它的小数部分可以表示成 0 的循环或是 k?1 的循环;而一个小数部分非 0 的有限小数不是纯循环的。

提示

这部分将提供一个将分数化为对应的小数的方法,如果你已经熟悉这个方法,你不必阅读本提示。

分数可以通过除法,用分子除以分母化为对应的小数。有些分数在除法过程中无法除尽,这样的分数在不断进行的除法过程中余数一定会重复出现。从商数的个位所对应的余数起,设第一次重复出现的余数前两次出现的位置所对应的商数位分别是小数点后第 aa 位和小数点后第 bb 位(特殊地:如果其中一个对应的商数位是个位,则认为 a=0;不妨设 a<b),则其循环部分可以用小数点后第 a+1 位到小数点后第 b 位的循环来表示。

例如:在十进制下,将 5/11 转化为小数时,个位开始的商数依次为 4,5,4,…,对应的余数分别为 6,5,6,…。余数第一次重复出现的位置是个位和小数点后第 2 位,那么 a=0,b=2 即其循环部分可以用小数点第 1 位到第 3 位来表示。表示为:5/11=0.45454545…=0.4˙5˙。

在十进制下,将 1/6 转化为小数时,个位开始的商数依次为 1,6,6,…,对应的余数分别为 4,4,4,…。余数第一次重复出现的位置是小数点后第 1 位和小数点后第 2 位,即其循环部分可以用小数点后第 2 位来表示。表示为:16=0.1666……=0.16˙。

需要注意的是:商数重复出现并不代表进入了循环节。

输入

只有一行,包含三个十进制数N,M,K意义如题所述,保证 1≤n≤10^9,1≤m≤10^9,2≤k≤2000

输出

一行一个整数,表示满足条件的美的数的个数。

输入示例

2 6 10

输出示例

4

数据规模及约定

见“输入

题解

根据它的提示,我们可以列一列式子:(令商第 i 位后的余数为 pi

联立得到

又有 (x, y) = 1,所以得到 km mod y = 1,即 (k, y) = 1。

那么现在题目就是在求:

纯暴力 24 分可以拿到了。

接下来,学习了莫比乌斯反演,我们知道它可以变形

交换一下枚举顺序,得到

那么如果最右边那个Σ能够 O(1) 得到,枚举 d 即可 O(nlogk) 求解,那个Σ求法如下

所以我们只需要预处理出 i = 1, 2, ..., k 时 f(i) 的值即可。至此我们拿到了 84 分。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = getchar(); }
	return x * f;
}

#define maxn 20000001
#define maxk 2010
#define LL long long

bool vis[maxn];
int cp, prime[maxn], mu[maxn];
void init() {
	mu[1] = 1;
	for(int i = 2; i < maxn; i++) {
		if(!vis[i]) prime[++cp] = i, mu[i] = -1;
		for(int j = 1; i * prime[j] < maxn && j <= cp; j++) {
			vis[i*prime[j]] = 1;
			if(i % prime[j] == 0){ mu[i*prime[j]] = 0; break; }
			mu[i*prime[j]] = -mu[i];
		}
	}
	return ;
}

int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }

int f[maxk];
int calc(int n, int k) {
	return n / k * f[k] + f[n%k];
}

int main() {
	init();

	int n = read(), m = read(), k = read();
	for(int i = 1; i <= k; i++) f[i] = f[i-1] + (gcd(i, k) == 1);

	LL sum = 0;
	for(int d = 1; d <= n; d++) if(gcd(d, k) == 1) sum += (LL)mu[d] * (n / d) * calc(m / d, k);
	printf("%lld\n", sum);

	return 0;
}

接着推式子

如果我们能快速求出 g(i, k) 的值,那么可以给后面 [n / d] 以及 f([m / d]) 的值分成 2(sqrt(n) + sqrt(m)) 类并最终高效地求得答案。

考虑 k 的一个质因数 p,那么 k = ptq,[gcd(d,k)=1] 的部分 = [gcd(d,q)=1] 的部分 - [gcd(d,p)>1][gcd(d,q)=1]的部分,所以得到

显然 [gcd(pd,q)=1] = [gcd(p,q)=1][gcd(d,q)=1] = [gcd(d,q)=1]

又因为 [gcd(d,q)=1][gcd(d,p)=1] = [gcd(d,pq)=1],所以,把上式接着变化

这样,我们就可以递归求 g(i, k) 了,每次要么 k 除掉一个它的质因数(除干净),要么 i 变成 [i / p],所以状态数会非常少。

递归边界:i = 0 时 g(i, k) = 0;k = 1 时 g(i, k) 就是莫比乌斯函数的前缀和,学习了杜教筛,就迎刃而解了。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = getchar(); }
	return x * f;
}

#define maxn 1000001
#define maxk 2010
#define MOD 1000007
#define LL long long
#define oo 2147483647

bool vis[maxn];
int cp, prime[maxn], mu[maxn], smu[maxn];
void init() {
	mu[1] = 1; smu[1] = 1;
	for(int i = 2; i < maxn; i++) {
		if(!vis[i]) prime[++cp] = i, mu[i] = -1;
		for(int j = 1; i * prime[j] < maxn && j <= cp; j++) {
			vis[i*prime[j]] = 1;
			if(i % prime[j] == 0){ mu[i*prime[j]] = 0; break; }
			mu[i*prime[j]] = -mu[i];
		}
		smu[i] = smu[i-1] + mu[i];
	}
	return ;
}

int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }

int f[maxk];
int calc(int n, int k) {
	return n / k * f[k] + f[n%k];
}

struct Hash {
	int ToT, head[MOD], nxt[maxn], num[maxn], num2[maxn], val[maxn];
	Hash() { ToT = 0; memset(head, 0, sizeof(head)); }
	void Insert(int x, int v) {
		int u = x % MOD;
		nxt[++ToT] = head[u]; num[ToT] = x; val[ToT] = v; head[u] = ToT;
		return ;
	}
	void Insert2(int x1, int x2, int v) {
		int u = ((LL)x1 * 233 + x2) % MOD;
		nxt[++ToT] = head[u]; num[ToT] = x1; num2[ToT] = x2; val[ToT] = v; head[u] = ToT;
		return ;
	}
	int Find(int x) {
		int u = x % MOD;
		for(int e = head[u]; e; e = nxt[e]) if(num[e] == x) return val[e];
		return 0;
	}
	int Find2(int x1, int x2) {
		int u = ((LL)x1 * 233 + x2) % MOD;
		for(int e = head[u]; e; e = nxt[e]) if(num[e] == x1 && num2[e] == x2) return val[e];
		return oo;
	}
} hh, hh2;

int getsum(int n) {
	if(n < maxn) return smu[n];
	if(hh.Find(n)) return hh.Find(n);
	int sum = 1;
	for(int i = 2, lst; i <= n; i = lst + 1) {
		lst = n / (n / i);
		sum -= getsum(n / i) * (lst - i + 1);
	}
	hh.Insert(n, sum);
	return sum;
}

int fir_p[maxk], lst_q[maxk];
int Find(int n, int k) {
	if(!n) return 0;
	if(k == 1) return getsum(n);
	if(hh2.Find2(n, k) < oo) return hh2.Find2(n, k);
	int p = fir_p[k], q = lst_q[k];
	int tmp = Find(n, q) + Find(n / p, p * q);
	hh2.Insert2(n, k, tmp);
	return tmp;
}

int main() {
	init();

	int n = read(), m = read(), k = read();
	for(int i = 1; i <= k; i++) f[i] = f[i-1] + (gcd(i, k) == 1);

	for(int K = 2; K <= k; K++)
		for(int i = 1; i <= cp; i++) if(K % prime[i] == 0) {
			fir_p[K] = prime[i];
			lst_q[K] = K; while(lst_q[K] % fir_p[K] == 0) lst_q[K] /= fir_p[K];
			break;
		}
	LL sum = 0;
	for(int i = 1, lst; i <= min(n, m); i = lst + 1) {
		lst = min(n / (n / i), m / (m / i));
		sum += (LL)(Find(lst, k) - Find(i - 1, k)) * (n / i) * calc(m / i, k);
	}
	printf("%lld\n", sum);

	return 0;
}

顺便补一下杜教筛的核心公式:

然后我们就可以通过对 [n / i] 的值分类递归求出 F(n) 了(第二行第一个等号画画表理解吧。。。)

其实杜教筛适用于所有狄利克雷卷积非常好算的数论函数。

时间: 2024-10-14 10:25:38

[UOJ#221][BZOJ4652][Noi2016]循环之美的相关文章

bzoj4652 [NOI2016]循环之美

Description 牛牛是一个热爱算法设计的高中生.在他设计的算法中,常常会使用带小数的数进行计算.牛牛认为,如果在 k 进制下,一个数的小数部分是纯循环的,那么它就是美的.现在,牛牛想知道:对于已知的十进制数 n 和 m,在 kk 进制下,有多少个数值上互不相等的纯循环小数,可以用分数 xy 表示,其中 1≤x≤n,1≤y≤m,且 x,y是整数 .一个数是纯循环的,当且仅当其可以写成以下形式:a.c1˙c2c3-cp-1cp˙其中,a 是一个整数,p≥1:对于 1 ≤i≤p,ci是 kk

UOJ #221 【NOI2016】 循环之美

题目链接:循环之美 这道题感觉非常优美--能有一个这么优美的题面和较高的思维难度真的不容易-- 为了表示方便,让我先讲一下两个符号.$[a]$表示如果$a$为真,那么返回$1$,否则返回$0$: $a \perp b$表示$a$与$b$互质. 首先,我们需要考虑一个分数要成为纯循环小数需要满足什么条件. 我们先来回想一下,我们是怎样使用除法来判断一个分数$\frac{x}{y}$是否是纯循环小数的.显然我们是一路除下去,什么时候出现了相同的余数,那么这个数就是一个循环小数.如果第一个重复的余数是

[UOJ#220][BZOJ4651][Noi2016]网格

试题描述 跳蚤国王和蛐蛐国王在玩一个游戏. 他们在一个 n 行 m 列的网格上排兵布阵.其中的 c 个格子中 (0≤c≤nm),每个格子有一只蛐蛐,其余的格子中,每个格子有一只跳蚤. 我们称占据的格子有公共边的两只跳蚤是相邻的. 我们称两只跳蚤是连通的,当且仅当这两只跳蚤相邻,或存在另一只跳蚤与这两只跳蚤都连通. 现在,蛐蛐国王希望,将某些(0 个,1 个或多个)跳蚤替换成蛐蛐,使得在此之后存在至少两只跳蚤不连通. 例如:我们用图表示一只跳蚤,用图表示一只蛐蛐,那么图 1 描述了一个 n=4,m

[UOJ#223][BZOJ4654][Noi2016]国王饮水记

试题描述 跳蚤国有 n 个城市,伟大的跳蚤国王居住在跳蚤国首都中,即 1 号城市中.跳蚤国最大的问题就是饮水问题,由于首都中居住的跳蚤实在太多,跳蚤国王又体恤地将分配给他的水也给跳蚤国居民饮用,这导致跳蚤国王也经常喝不上水.于是,跳蚤国在每个城市都修建了一个圆柱形水箱,这些水箱完全相同且足够高.一个雨天后,第 i 个城市收集到了高度为 hi 的水.由于地理和天气因素的影响,任何两个不同城市收集到的水高度互不相同.跳蚤国王也请来蚂蚁工匠帮忙,建立了一个庞大的地下连通系统.跳蚤国王每次使用地下连通系

东北育才10天大总结

老师们 Scanf的嗓门照例是最大的.恩. “我是山里的孩子……小的时候背书,整个山头都听得见……” 有一个哈师大附中的竞赛教练很……怎么说呢?接地气好了. Scanf说东北人很耿直,似乎确实是这样的.衡水的教练早就被遣返了…… “他啊,监考去了!” 虽然他不在,但还是不还手机.让衡水的人天天在电脑上颓废…… Scanf不在,你看我们就很老实.他到处“乱”玩,甚至跑到了国境线边,连火车票都忘了买,坐高铁去,乘绿皮火车回,路过长白山就去玩了一趟,结果暴风雪逼得他去吃“暴辣”的烤鱿鱼. “我看<三八

YCB 的暑期计划

前言 YCB现在很弱(TAT) 暑假有一个月,赶快狂补一下. 大概的计划如下: 首先前期会以数据结构为主,毕竟代码能力太弱,涉及内容:线段树分治.二进制分组.KD-Tree. 等数据结构做到没有智商的时候加入一波数论,内容为 杜教筛.min_25筛. 然后中途小清新一下,做一些 组合博弈与构造题. 接着继续练代码能力,顺便学一些神奇的暴力:启发式合并.dsu on tree . 然后图论也忘的差不多了,就回过头去学点新东西,大概会有spfa判负环.0/1分数规划.差分约束. 估计这个时候也没有什

数学模型

Catalan 卡特兰数 - 计数的映射方法的伟大胜利 [AHOI2012]树屋阶梯 鸡蛋饼 [SCOI2010]生成字符串 Stirling 斯特林数 容斥 容斥原理(翻译) UVA10325 The Lottery(状压+容斥) UVA11806 Cheerleaders SP4191 MSKYCODE - Sky Code [CQOI2015]选数(容斥+递推) [SCOI2010]幸运数字 莫比乌斯反演与筛法 \[g(n)=\sum_{d|n}f(d)\] \[f(n)=\sum_{d|

NOI2016 高中OI生涯的最后一站

你乘坐的航班XXX已经抵达终点站——四川绵阳. “呼——”机舱外的天空灰沉沉的,不禁有些压抑与紧张. 一出机场,就看见南山中学的牌子,黄色衣服的志愿者们,还有热情的老师们. 感觉刚才的情绪又一扫而空了,转而迎来的是一种兴奋与激动. 学长和教练都曾说过:就当做一次展现自己实力的机会.从来不要给自己太大压力. 这样的话大多也埋在心里了吧,潜移默化的影响着自己的心情. 那就开心的去面对这几场考试好了. 首先领好东西,还去签名版上签了个名字,发现湖南参赛的选手果然好多呀...不过不知道今年能不能翻身成强

PHP 天巡机票接口

一个旅游网站项目,网站需要机票预订接入了天巡机票接口,获取机票信息,不搞不知道,一搞吓一跳比较麻烦. 搜索机票信息需要分2步,首先POST获得一个SESSION,2秒之后,根据这个SESSION,从一个URL GET 数据 ,并且需要多次GET,这里就用AJXA轮询, 直到返回的GET数据状态为 完成,即可停止轮询. 轮询获取数据之后,还需要自己整理才可显示在网页上, 上PHP代码,,THINKPHP框架代码 1 <?php 2 namespace Home\Controller; 3 use