[COJ0528]BJOI幸运数

试题描述

输入

见“试题描述

输出

见“试题描述

输入示例

见“试题描述

输出示例

见“试题描述

数据规模及约定

见“试题描述

题解

首先想到一个比较暴力的数位 dp 方法:设 f[i][j][s1][s2] 表示前 i 位中,最高位为 j,其中奇数位的数字和为 s1,偶数位的数字和为 s2,满足这个条件的数的个数。

然而,这样的话每次询问都不得不每位处理时都枚举一下 s1 和 s2,受不了 T = 1000 的数据。

先贴一下这个代码:

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

LL read() {
	LL 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 20
#define maxs 90
#define maxk 110
LL g[maxn][10][maxs][maxs];
int Gcd[maxs][maxs];

int gcd(int x, int y) { return !y ? x : gcd(y, x % y); }

int K, num[maxn];
LL sum(LL x) {
	int cnt = 0; LL tx = x;
	while(x) num[++cnt] = x % 10, x /= 10;
	LL ans = 0;
	int s1 = 0, s2 = 0;
	for(int i = cnt; i; i--) {
		for(int j = 0; j < num[i]; j++)
			for(int t1 = max(s1, 1); t1 < maxs; t1++)
				for(int t2 = max(s2, 1); t2 < maxs; t2++)
					if(Gcd[t1][t2] <= K && g[i][j][t1-s1][t2-s2]) {
						ans += g[i][j][t1-s1][t2-s2];
//						printf("%d %d %d %d: %d\n", i, j, t1-s1, t2-s2, g[i][j][t1-s1][t2-s2]);
					}
		if(i & 1) s1 += num[i]; else s2 += num[i];
	}
	if(Gcd[s1][s2] <= K && s1 && s2) ans++;
//	printf("%lld: %lld\n", tx, ans);
	return ans;
}

int main() {
	g[0][0][0][0] = 1;
	for(int i = 0; i < maxn - 1; i++)
		for(int j = 0; j <= 9; j++)
			for(int s1 = 0; s1 < maxs; s1++)
				for(int s2 = 0; s2 < maxs; s2++)
					if(g[i][j][s1][s2]) {
						for(int k = 0; k <= 9; k++) {
							int S1 = s1, S2 = s2;
							if(i + 1 & 1) S1 += k; else S2 += k;
							g[i+1][k][S1][S2] += g[i][j][s1][s2];
						}
//						printf("%d %d %d %d: %d\n", i, j, s1, s2, g[i][j][s1][s2]);
					}
	for(int i = 0; i < maxs; i++)
		for(int j = 0; j < maxs; j++)
			Gcd[i][j] = gcd(i, j);

	int T = read();
	while(T--) {
		K = read(); LL l = read(), r = read();
		printf("%lld\n", sum(r) - sum(l - 1));
	}

	return 0;
}

对了记得预处理 Gcd[i][j],否则会更慢。。。

现在我们主要目的是优化每次询问所消耗的代价,核心在于不能再每次枚举 s1 和 s2 了。

我们考虑一下求 [1, x] 中答案的过程,我们是一位位确定数字的,所以当前状态可以形象地表示为一下形式:

XXXX____

其中“X”表示已填数字,“_”表示未填数字,我们发现刚才之所以要枚举 s1 和 s2 的原因在于前面填了数字之后,奇偶位上已经有一定的数字和了,所以在这里我们不妨将其设为状态,于是就变成了:

f[i][k][s1][s2] 表示还有 i 位未填,且前面已经填写的数字中,奇数位上数字和为 s1,偶数位上数字和为 s2,现在我们要填满后面那 i 个空位,并且满足填完之后整个数奇数位之和与偶数位之和的最大公约数不超过 k,那么满足上面这一系列条件的填法有多少种。

显然初始状态 f[0][1~maxk][1~maxs][1~maxs] = 1,其中 maxk 表示输入的 k 的最大值,maxs 表示可能出现的最大数字和;

转移:

1. f[i][k][s1][s2] -> f[i+1][k][s1+j][s2] (0 ≤ j < 10),i+1 为奇数

2. f[i][k][s1][s2] -> f[i+1][k][s1][s2+j] (0 ≤ j < 10),i+1 为偶数

这样询问的负担就减轻不少了。

这题还得卡卡常数。

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

LL read() {
	LL 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 19
#define maxs 90
#define maxk 101
LL f[maxn][maxk][maxs][maxs];
int Gcd[maxs][maxs];

int gcd(int x, int y) { return !y ? x : gcd(y, x % y); }

int num[maxn];
LL sum(LL x, int k) {
	int cnt = 0;
	while(x) num[++cnt] = x % 10, x /= 10;
	int s1 = 0, s2 = 0;
	LL ans = 0;
	for(int i = cnt; i; i--) {
		for(int j = 0; j < num[i]; j++) {
			ans += f[i-1][k][s1][s2];
			if(i & 1) s1++; else s2++;
		}
	}
	ans += f[0][k][s1][s2];
	return ans;
}

int main() {
	for(int i = 0; i < maxs; i++)
		for(int j = 0; j < maxs; j++) Gcd[i][j] = gcd(i, j);
	for(int k = 1; k < maxk; k++)
		for(int i = 1; i < maxs; i++)
			for(int j = 1; j < maxs; j++)
				if(Gcd[i][j] <= k) f[0][k][i][j]++;
	for(int i = 0; i < maxn - 1; i++)
		for(int k = 1; k < maxk; k++) {
			int mxs = (maxn - i >> 1) * 9;
			for(int s1 = 0; s1 <= mxs; s1++)
				for(int s2 = 0; s2 <= mxs; s2++) if(f[i][k][s1][s2])
					for(int j = 0; j <= 9; j++) {
						int t1 = s1, t2 = s2;
						if(i + 1 & 1) t1 -= j; else t2 -= j;
						if(t1 < 0 || t2 < 0) break;
						f[i+1][k][t1][t2] += f[i][k][s1][s2];
					}
		}

	int T = read();
	while(T--) {
		int k = read(); LL l = read(), r = read();
		printf("%lld\n", sum(r, k) - sum(l - 1, k));
	}

	return 0;
}
时间: 2024-08-03 03:37:04

[COJ0528]BJOI幸运数的相关文章

【蓝桥杯】历届试题 幸运数

  历届试题 幸运数   时间限制:1.0s   内存限制:256.0MB 问题描述 幸运数是波兰数学家乌拉姆命名的.它采用与生成素数类似的“筛法”生成. 首先从1开始写出自然数1,2,3,4,5,6,.... 1 就是第一个幸运数. 我们从2这个数开始.把所有序号能被2整除的项删除,变为: 1 _ 3 _ 5 _ 7 _ 9 .... 把它们缩紧,重新记序,为: 1 3 5 7 9 .... .这时,3为第2个幸运数,然后把所有能被3整除的序号位置的数删去.注意,是序号位置,不是那个数本身能否

蓝桥杯 - 幸运数 (打表)

历届试题 幸运数 时间限制:1.0s   内存限制:256.0MB 问题描述 幸运数是波兰数学家乌拉姆命名的.它采用与生成素数类似的"筛法"生成 . 首先从1开始写出自然数1,2,3,4,5,6,.... 1 就是第一个幸运数. 我们从2这个数开始.把所有序号能被2整除的项删除,变为: 1 _ 3 _ 5 _ 7 _ 9 .... 把它们缩紧,重新记序,为: 1 3 5 7 9 .... .这时,3为第2个幸运数,然后把所有能被3整除的序号位置的数删去.注意,是序号位置,不是那个数本身

51nod 1230:幸运数

51nod 1230:幸运数 题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1230 题目大意:如果一个数各个数位上的数字之和是质数,并且各个数位上的数字的平方和也是质数,则称它为幸运数.例如:120是幸运数,因为120的数字之和为3,平方和为5,均为质数,所以120是一个幸运数字.给定x,y,求x,y之间( 包含x,y,即闭区间[x,y])有多少个幸运数. 数位DP 代码如下: 1 #include <cs

幸运数的各位和[Codeforces-109A]

Codeforces Beta Round #84 (Div. 1 Only)  时间限制2000ms,内存限制256MB 问题大意:定义幸运数为十进制表示只包含4.7两个数码的数.例如47.744.4都是幸运数,而5.17.467不是. 已知一个幸运数的各位之和,求满足条件的幸运数的最小值.输入保证$1≤n≤10^6$. 这题用暴力方法就可以做.需要注意题目中的几个信息: 1)输入可能达到$10^6$,因此原数必然超过$10^6÷9>10^5$,因此无论用32位还是64位整数是不可能表示的下的

[51NOD1230]幸运数(数位DP)

题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1230 dp(l,s,ss)表示长度为l的数各位和为s,各位平方和为ss的幸运数的个数. 1 #include <bits/stdc++.h> 2 #pragma comment(linker, "/STACK:10240000,10240000") 3 using namespace std; 4 5 typedef long long

曦皓的幸运数

[题目描述] 仅包含4或7的数被称为幸运数. 一个序列的子序列被定义为从序列中删去若干个数,剩下的数组成的新序列.两个子序列被定义为不同的当且仅当其中的元素在原始序列中的下标的集合不相等.对于一个长度为N的序列,共有2^N个不同的子序列(包含一个空序列). 一个子序列被称为不幸运的,当且仅当其中不包含两个相同的幸运数. 对于一个给定序列,求其中长度恰好为K的不幸运子序列的个数,答案 mod (10^9+7)输出. [输入描述] 第一行两个正整数N.K,表示原始序列的长度和题目中的K: 接下来一行

京东笔试之幸运数问题:4,7

题目描述 4和7是两个幸运数字,我们定义,十进制表示中,每一位只有4和7两个数的正整数都是幸运数字. 前几个幸运数字为:4,7,44,47,74,77,444,447... 现在输入一个数字K,输出第K个幸运数. 输入 第一行一个数字T(T<=1000)表示测试数据的组数.对于每组测试数据,输出一个数K 输出 每组数据输出一行,第K个幸运数. 样例输入 351001000000000 样例输出 74744747 77477744774747744747444444447 思路 首先把4和7化为0

幸运数

文字描述:将队列法所描述得序列拿来比较会发现一个规律,如下:初始序列: 4 , 7 4,7 得序列2: 44 , 47 , 74 , 77 得序列3: 444 , 447 , 474 , 477 , 744 , 747 , 774 , 777 规律:每个序列是包含当前位数得所有幸运数,而且相较前一个序列是以2的倍数递增,每个序列得前一半以 4 开头,后一半是以 7 开头,当把头位去掉以后,剩下得幸运数正好是上一个序列,例如序列3包含所有位数为 3 3 得幸运数,而且前 4 个幸运数是以 4 开头

51nod 1230 幸运数

题目大意 如果一个数各个数位上的数字之和是质数,并且各个数位上的数字的平方和也是质数,则称它为幸运数. 例如:120是幸运数,因为120的数字之和为3,平方和为5,均为质数,所以120是一个幸运数字. 给定x,y,求x,y之间( 包含x,y,即闭区间[x,y])有多少个幸运数. Input 第1行:一个数T,表示后面用作输入测试的数的数量.(1 <= T <= 10000) 第2 - T + 1行:每行2个数,X, Y中间用空格分割.(1 <= X <= Y <= 10^18