进阶习题:
http://www.51nod.com/Question/Index.html#questionId=1481数数字
http://www.51nod.com/Challenge/Problem.html#problemId=2151队列复原
https://www.51nod.com/Challenge/Problem.html#problemId=3593小明的数字表 vector-STL
http://www.51nod.com/Question/Index.html#questionId=8238小明爱排队
http://www.51nod.com/Question/Index.html#questionId=3217合法括号序列 V1
和为k的连续区间
位运算:
一个奇数次
授勋
区间xor
数学方面:
(斐波那契数列)
骨牌覆盖
ProjectEuler 2
跳房子
机器人走方格(dp初步)
机器人走方格 V5
质数篇章:
ProjectEuler 7
ProjectEuler 37
1到N的最小公倍数
分解质因数
ProjectEuler 12
取模运算:
n^n的末位数字
求递推序列的第N项
关于康托展开需要谈一下的:
康托展开
康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。
康托展开运算
𝑋=𝑎𝑛(𝑛−1)!+𝑎𝑛−1(𝑛−2)!+...𝑎10!
其中 𝑎𝑖 为整数,并且满足 0≤ai<i,1≤i≤n 。
𝑎𝑖 表示原数的第 i 位在当前未出现的元素中排在第几。
例如:在 12345 的排列组合中,计算 34152 的康托展开值。
值 | 未出现且小于当前值的数 | 展开值 | |
---|---|---|---|
3 | 1,2=>𝑎5=21 | 𝑎5×4! | 48 |
4 | 1,2=>𝑎4=21 | 𝑎4×3! | 12 |
1 | 无 =>𝑎3=0 | 𝑎3×2! | 0 |
5 | 2=>𝑎2=12 | 𝑎2×1! | 1 |
2 | 无 =>𝑎1=0 | 𝑎1×0! | 0 |
所以比 34152 小的组合有 61 个,即 34152 是排第 62 。
康托展开逆运算
康托展开是一个全排列到一个自然数的双射,因此是可逆的。即对于上述例子,在给出 61 可以算出起排列组合为 3415234152 。由上述的计算过程逆推回来,具体过程如下:
61/4!=2 余 13,说明,说明比首位小的数有 2 个,所以首位为 3 。
13/3!=2余 1,说明,说明在第二位之后小于第二位的数有 2 个,所以第二位为 4 。
1/2!=0余 1 ,说明,说明在第三位之后没有小于第三位的数,所以第三位为 1 。
用 1/1!=1 余 0 ,说明,说明在第四位之后小于第四位的数有 1 个,所以第四位为 5 。
最后一位自然就是剩下的数 2 。通过以上分析,所求排列组合为 34152。
数数字
统计一下 a*a*a ⋯ a*a*a(n个a) × b的结果里面有多少个数字d,a,b,d均为一位数。
样例解释:
3333333333*3=9999999999,里面有10个9。
输入格式
多组测试数据。 第一行有一个整数T,表示测试数据的数目。(1≤T≤5000) 接下来有T行,每一行表示一组测试数据,有4个整数a,b,d,n。 (1≤a,b≤9,0≤d≤9,1≤n≤10^9)
输出格式
对于每一组数据,输出一个整数占一行,表示答案。
输入样例
2
3 3 9 10
3 3 0 10
输出样例
10
0
思路:这题 b 也只能是个位数,就很简单了
一些例子:22222 * 6 = 133332 (2*6=12, 1与2之间填充3)
22222 * 7 = 155554 (2*7=14, 1与4之间填充5)
22222 * 8 = 177776 (2*8=16, 1与6之间填充7)
22222 * 9 = 199998 (2*9=18, 1与8之间填充9)
33333 * 4 = 133332 (3*4 =12, 1与2之间填充3)
所以我们会发现:随着 n 增加,只有中间那个数字的个数会变多
n<=9 的时候暴力做就可以了;n>9的时候,暴力算一下 n=9 的时候中间那个数是多少,两边的数是多少,然后增加中间那个数的个数就行
官方题解代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int cnt(ll v, int d) {
int ans = 0;
while (v) {
if (v % 10 == d)
ans++;
v /= 10;
}
return ans;
}
ll getNum(int n) {
ll m = 1;
for (int i = 1;i < n;i++)
m = m * 10 + 1;
return m;
}
int ssz() {
int a, b, d, n, ct;
ll v;
cin >> a >> b >> d >> n;
v = getNum(min(n, 11));
v *= a * b;
ct = cnt(v, d);
if (n < 11)
return ct;
if (ct > 6)
ct = n - 11 + ct;
return ct;
}
int main() {
int n;
cin >> n;
for (int i = 0;i < n;i++) {
cout << ssz() << endl;
}
}
我的思路:
分为三种情况:
1.a * b < 10,那么所有的数不需要进位,如果a*b=d,那么d的个数就是n个,如果≠就是0个,这种情况最简单了
2.a * b >= 10,那么a*b就需要向前进位,注意这里要考虑两种情况:
(a * b)/10 + (a * b)%10 < 10,比如6666 * 2 = 13332,(6 * 2)/10 + (6 * 2)%10 = 3,也就是向前进位一次即可。
(a * b)/10 + (a * b)%10 >= 10,比如6666 * 8 = 53328,(6* 8)/10 + (6 * 8)%10 = 4 + 8 = 12,那这里又需要一次进位,也就是进位两次(不可能进位三次,因为最大也就是9+9=18,1向前进位,8落到原位,1 + 8 = 9 < 10)
找规律具体措施:
当n >= 3的时候,比如a=6,n=3,b=8,那么就是666*8,结果是5328,当n=4的时候,其他一样,结果是53328,那么就可以发现规律了,除了最左一位、最右一位,右边数第二位,除了这几个是没有规律的,中间的n-3项都是一样的,比如这里的:5 = (6*8)/10 + 1 --- 最左一位8 = (6*8)%10 --- 最右一位2 = ((6*8)/10 + (6*8)%10)%10 = (4+8)%10 --- 右边数第二位 3 = ((6*8)/10 + (6*8)%10)%10 + 1 = (4+8)%10+1 --- 中间的n-3项,后面的那个+1,就是((6*8)/10 + (6*8)%10)/10 = (4+8)/10 = 1
下面上代码!
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int t,a,b,d,n;
cin >> t;
while(t--)
{
cin >> a >> b >> d >> n;
int ans = 0;
if(a*b % 10 == d)
ans++;
if(n == 1 && a*b/10 == d && a*b >= 10)
ans++;
if(n >= 2)
{
if((a*b+a*b/10) % 10 == d)
ans++;
if((a*b + (a*b+a*b/10)/10) % 10 == d)
ans += n-2;
if((a*b + (a*b+a*b/10)/10) >= 10 && (a*b + (a*b+a*b/10)/10) /