进阶习题:
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) / 10 == d)
ans++;
}
cout << ans << endl;
}
return 0;
}
队列复原2
小瓜现在让 1 到 n 这 n 个整数排成一列,但是他只告诉你每个整数的后面那个数是什么(最后一个整数的后面那个数是 0 )。此外,他还打算在这个队列中插入 m 个整数,他将告诉你这 m 个整数插入的位置。请你帮忙复原插入 m
个整数之后的队列。
输入格式
第一行两个整数n(n<=100000)和m(m<=100),表示有n个整数,后续又将插入m个整数。 接下来n行,每行两个数i,j,表示排在整数i后面的那个数是j。 接下来m行,每行两个数a,b,表示在下标为a的数后面插入编号为b的数(保证 n+1 <= b <= n+m)
输出格式
n+m行,表示复原后的队列。
输入样例
4 2
1 3
3 4
4 2
2 0
1 5
2 6
输出样例
1
5
3
4
2
6
思路:
首先,这个题先插入整数,后面还有一个按编号插入,存储时就正常存储,之后插入的时候,改变下标即可
首先输出结果的时候,先找到队首元素
b数组用来标记 ,j是后面的元素b[j] = 1;//把非队首元素标记成1,之后队首元素一定是0~n之间的
#include<iostream>
using namespace std;
const int N = 100020;
//int a[N];
//int pd[N];
int a[N*3],pd[N+1]={0};
int n,m;
int main()
{
cin>>n>>m;
int i,j,t;
for(int k = 1;k <=n;k++)
{
cin>>i>>j;
a[i] = j;
pd[j] = 1;
}
for(int k=1; k<=m; k++)
{
cin>>i>>j;
t=a[i];
a[i]=j;
a[j]=t;
}
for(int k = 0;k <= n;k++)
{
if(pd[k] == 0)
{
t = k;
break;
}
}
cout<<t<<endl;
for(int k = 1;k <= m+n-1;k++)
{
cout<<a[t]<<endl;
t = a[t];
}
return 0;
}
3593.小明的数字表 vector-STL
小明现在爱上了查询,他遇到了这样一个查询问题,给出 n 个数 (n<=100000),
有 q 个查询 (q<=100000),每组查询 3 个数:u v w,表示查找第 u 位 (个位为第1位,十位为第2位,依此类推)为数字 v 的所有数字中,第 w
小的数,如果这个数字不存在,输出“-1”。小明不会做这个题,聪明的你可以帮助小明解决这个问题吗?
注:不考虑所有数字中的前导 0
。
输入格式
第一行输入一个n(n<=100000),表示数列的长度; 第二行n个正整数ai(0<=ai<=100000000); 第三行一个数字q(q<=100000)表示询问的次数; 下面q行,每行给出三个数字u,v,w,表示查找第u位为数字v的所有数字中,第w小的数。
输出格式
每个询问输出一个数字,查询不到输出“-1”。
输入样例
4
1 21 22 3
3
2 2 2
1 1 2
2 2 3
输出样例
22
21
-1
数据范围
<p>n<=100000 0<=ai<=100000000 q<=100000</p>
样例解释
第2位数字为2的数字包括 (21,22) 第 2 小的数字为22;
第1位数字为1的数字包括 (1,21) 第2小的数字为21;
第2位数字为2的数字包括 (21,22) 第3小的数字不存在,输出“-1”。
思路:直接暴力
#include<bits/stdc++.h>
using namespace std;
vector<int> soso[100][100];
int n,x;
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
scanf("%d",&x);
if(x == 0)
{
soso[1][0].push_back(0);
}
int t = x;
for(int u = 1;u <= 9;u++)
{
while(x)
{
soso[u][x%10].push_back(t);
x/=10;
break;
}
}
}
for(int i = 1;i <=9;i++)
{
for(int j = 0;j <= 9;j++)
{
sort(soso[i][j].begin(),soso[i][j].end());
}
}
int q;
scanf("%d",&q);
while(q--)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
if(w > soso[u][v].size())
puts("-1");
else
cout<<soso[u][v][w-1]<<endl;
}
return 0;
}
队列问题:
小明爱排队
小明这天拿到一个排队题目,题意如下,一堆人排队,有几种操作,
a v
1.如果v不在队中,则表示v来了,排在队尾;
2.如果v在队中,则表示v走了。
b
接待队首的一客,输出顾客是谁。
如果不存在输出“-1”。
对于小明来说,这个问题他不会做,聪明的你可以帮助小明解决这个问题吗?
输入格式
第一行输入一个n(1<=n<=100000),表示操作数; 之后n行,如果第一个字符是a,那么后面继续输入一个数字v,具体意义如下: 1.如果v不在队中,则表示v来了,排在队尾; 2.如果v在队中,则表示v走了。 如果第一个字符是b,那么表示询问队首的顾客,输出顾客是谁。
输出格式
对于每个询问b,如果存在答案则输出顾客的编号,否则输出“-1”。
输入样例
7
a 1
a 1
a 2
a 1
b
b
b
输出样例
2
1
-1
数据范围
1<=n<=100000 0<=v<=10000
样例解释
操作a 1,编号为1的人进入队列,当前队列为1;
操作a 1,编号为1的人退出队列,当前队列为空;
操作a 2,编号为2的人进入队列,当前队列为2;
操作a 1,编号为1的人进入队列,当前队列为2 1;
操作b,队首为2,编号为2的人退出,当前队列为1;
操作b,队首为1,编号为1的人退出,当前队列为空;
操作b,当前队列为空,输出“-1”。
思路:维护一个排队的序列嘛
比如我 A 刚开始不在序列里,然后我1 A,表示A排到队尾了,表示A排到队尾了,那么比如现在我队列里是BACDE,然后我1 A,表示A不排队了,那么就变成BCDE,如果又来一个1 A 就变成BCDEA
合法括号序列 V1
有一个括号序列,现在要检测一下它是否是合法的括号序列
合法括号序列的定义是:
1.空序列是合法括号序列。
2.如果S是合法括号序列,那么(S)是合法括号序列。
3.如果A和B都是合法括号序列,那么AB是合法括号序列。
输入格式
输入一行,长度为N的括号序列S(0<=N<=50000,S只包括()这2种字符)
输出格式
输出一行,1表示括号序列合法,0表示括号序列不合法
输入样例
(())(
输出样例
0
思路:按要求模拟栈即可(STL和手写都行)
#include <bits/stdc++.h>
using namespace std;
int main()
{
stack<char> soso;
string nums;
cin >> nums;
for(int i = 0; i < nums.size(); i++)
{
if(nums[i] == '(')
{
soso.push(nums[i]);
}
else
{
if(soso.empty())
{
cout << "0" << endl;
return 0;
}
else
{
soso.pop();
}
}
}
if(soso.size() == 0)
cout << "1" << endl;
else
cout << "0" << endl;
return 0;
}
Map的使用方式:
//map解决红黑树这类问题
//不要自己手写啊
//map相当于映射,有自动排序功能,访问复杂度logN级别的啊!!!!
#include<iostream>
#include<map>
using namespace std;
//查找
map<string,string>::iterator it;
int main()
{
it = dataMap.find(key);
if(it != dataMap.end())//key存在
{
it = dataMap.find(key);
string valueStr = it->second;
}
//遍历map
for(it = s.begin();it != s.end();it++)
{
cout<< it->first;//first 表示key
}
return 0;
}
和为k的连续区间
一整数数列a[1], a[2], ... , a[n](有正有负),以及另一个整数k,求一个区间[i, j],(1 <= i <= j <= n),使得a[i] + ... + a[j] = k。
输入格式
第1行:2个数n,k。n为数列的长度。k为需要求的和。(2 <= n <= 10000,-10^9 <= k <= 10^9) 第2 - n+1行:a[i](-10^9 <= a[i] <= 10^9)。
输出格式
如果没有这样的序列输出No Solution。 输出2个数i, j,分别是区间的起始和结束位置。如果存在多个,输出i最小的。如果i相等,输出j最小的。
输入样例
6 10
1
2
3
4
5
6
输出样例
1 4
思路:做前缀和,把每个前缀和的下标丢进map里,map[sum] = i
如果 sum - k 是存在的,就表示 map[sum - k] + 1 到 i 的和等于 k
记录下合法区间,从小到大排序
为什么加一
5 12
1 2 3 4 5
15(1到5) - 3(1到2) = 12
2不在区间内,所以要+1
ans:3 5
#include <bits/stdc++.h>
const int N = 100010;
using namespace std;
int n, k, sum, a[N];
map<int, int> soso;
vector<pair<int, int> > alaso;
int main() {
scanf("%d %d", &n, &k);
for(int i = 1; i <= n; i ++)
scanf("%d", &a[i]);
M[0] = 0;
for(int i = 1; i <= n; i ++) {
sum = sum + a[i];
map<int, int>::iterator it;
it = soso.find(sum);
if(it == soso.end())
soso[sum] = i;
it = soso.find(sum - k);
if(it != soso.end())
alaso.push_back( make_pair(it -> second + 1, i) );
}
sort(alaso.begin(), alaso.end());
if(alaso.size() == 0)
printf("No Solution");
else printf("%d %d", alaso[0].first, alaso[0].second);
}
位运算篇章:
用途n&(n−1)==0 判断一个数是否为2 的幂
n&(−n) 获得一个数二进制的最后一个为 1 的 bit
n&((1<<x)−1) 求 n mod 2x
(x xor y)>=0判断两个数符号是否相同
思考题: 已知 n&(n−1)==0 可以用来判断一个数是否为 2的幂,设计一个 O(1) 的方法,判断一个数是否为 4 的幂。
//一个思路
-
(n & (n - 1) == 0) && (n % 3 == 1)
单选题
一个奇数次
输入一个长度为n的数组,考虑所有不同的数字,有且只有一个数字出现了奇数次。
比如对于1 2 3 1 2 3 1,我们考虑所有不同的数字1 2 3,有且只有1出现了奇数次(3次)
输出这个出现了奇数次的数字。
1 <= n <= 100000
1 <= a[i] <= 10^9
输入格式
第一行一个整数n, 接下来一行n个整数,表示输入的数字。
输出格式
一行一个数字,表示出现了奇数次的数字。
输入样例
7
1 2 3 1 2 3 1
输出样例
1
思路:我们根据一个规律:
axor 0 = a;
a xor a = 0
这是咱们总结出来的规律,直接套上即可
#include<iostream>
using namespace std;
int main()
{
int n,x;
cin>>n;
int ans = 0;
for(int i = 0;i < n;i++)
{
cin>>x;
ans ^= x;
}
cout<<ans<<endl;
return 0;
}
授勋
历经旷日持久的战争之后,百纳瑞王国(The Kingdom of Binary)终于迎来了胜利的曙光。于是国王决定在胜利日这一天为在战争中奋战的将领们授勋。
已经需要为N位将领授勋,他们每人有一个功勋值p[i]。国王准备了不同种类的勋章,它们分别代表1,2,4,8,16......(即2的幂次)的功勋值。国王将用与每位将领功勋值对等数值的勋章授予他们,并且每位将领只会被授予一枚同种勋章。
现在请你帮助国王算出,对于每一位将领,他需要准备多少枚勋章?
输入格式
第一行输入一个数N,表示将领人数; 之后N行,每行输入一个数,分别表示每位将领的功勋值。
输出格式
输出N行,每行一个数表示需要授予该将领的勋章数。
输入样例
3
15
1
22
输出样例
4
1
3
数据范围
对于20%的数据,2≤N≤10,1≤p[i]≤200; 对于50%的数据,2≤N≤1000,1≤p[i]≤500000; 对于100%的数据,2≤N≤100000,1≤p[i]≤10^9;
样例解释
样例中,
第一位将领功勋值为15,授予8,4,2,1,共4枚勋章;
第二位将领功勋值为1,授予1,共1枚勋章;
第三位将领功勋章为22,授予16,4,2,共3枚勋章。
思路:位运算即可
#include<iostream>
using namespace std;
int main()
{
int n;
cin>>n;
for(int i = 0;i < n;i++)
{
int x;
cin>>x;
int cnt = 0;
for(int j = 0;j < 31;j++)
if(x & (1<<j))cnt++;
cout<<cnt<<endl;
}
return 0;
}
区间xor
给出区间(a,b),b >= a,求a xor (a+1) xor (a+2).....xor b。
输入格式
输入2个数:a b,中间用空格分隔(1 <= a <= b <= 10^9)
输出格式
输出一个答案
输入样例
3 8
输出样例
11
#include<iostream>
using namespace std;
#define ll long long
ll a,b,c;
int main()
{
cin>>a>>b;
for(int i = a;i <= b;i++)
c ^= i;
cout<<c<<endl;
return 0;
}
思路:可以先算前缀区间,再疑惑可得两两之间为0,相邻两项异或为1

可以参考上面的纸算模拟
或者这样理解:
由于 a,b 范围 10^9 ,暴力是不行的。考虑 xor 的特性,设 F(n)=1 xor 2...xor n
存在:
a xor (a+1) xor (a+2).....xor b=F(b) xor F(a−1)
如何快速计算 F(n) 呢?我们考虑, 0 xor 1=1,2 xor 3=1,4 xor 5=1...
所以如果 n 为奇数,存在F(n)=0(n%4=3) , 𝐹(𝑛)=1(𝑛%4 =1)
如果 n 为偶数,F(n)=F(n−1) xor n 。问题又转为了奇数的情况。
骨牌覆盖
在2*N的一个长方形方格中,用一个1*2的骨牌排满方格。
问有多少种不同的排列方法。
例如:2 * 3的方格,共有3种不同的排法。(由于方案的数量巨大,只输出 Mod 10^9 + 7 的结果)

输入格式
输入N(N <= 1000)
输出格式
输出数量 Mod 10^9 + 7
输入样例
3
输出样例
3
直接上代码,注意MOD的书写
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int MOD = 1e9+7;
const int MAXN = 1010;
int fib[MAXN];
void init()
{
fib[0] = 1;
fib[1] = 1;
for(int i = 2;i < MAXN;i++)
{
fib[i] = (fib[i-1]+fib[i-2])% MOD;
}
}
int main()
{
init();
int n;
cin>>n;
cout<<fib[n]<<endl;
return 0;
}
ProjectEuler 2
Fibonacci数列的每一项是之前两项的和。
Fibonacci数列以1和2开始,前10项是1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
输入一个n,求所有小于等于n,且为偶数的Fibonacci数之和。
输入格式
第一行输入组数T, 接下来T行,每行一个整数n。 (1 <= T <= 23,1 <= N <= 4000000)
输出格式
对于每组数据,输出一个数,表示所有小于等于n,且为偶数的Fibonacci数之和。
输入样例
3
10
100
4000000
输出样例
10
44
4613732
这题判断一下是否为偶数即可,我们定一个变量为a,b表示第一项和第二项,c存储他们a+b的和 ,然后每次都往右移一个数去寻找有无偶数的fib值。(我们选用中间数b来记录我们每次遍历的数)
#include<bits/stdc++.h>
using namespace std;
const int MOD = 1e9+7;
const int N = 1e5+10;
int main()
{
int n,t;
for(int i = 0;i < t;i++)
{
cin>>n;
int sum = 0;
int a = 1,b = 2,c = 1;
if(n < 2)sum = 0;
while(b <= n)
{
if(b % 2 == 0)sum += b;
c = a+b;
a = b;
b = c;
}
cout<<sum<<endl;
}
return 0;
}
跳房子
小华正在和她的小伙伴玩跳房子游戏。这是一个加强版的跳房子,每一行的格子数量可能超过 2 个。
这个游戏需要在地面上画了n排格子,其中第i排包含a[i]个格子。(保证两端的这两排仅有一个格子)
)

之后规定两端的这两个格子分别为起点和终点,小朋友从起点出发,每次可以跳到下一排或下两排的某个格子,直到抵达终点.
现在小华想知道从起点到终点共有多少种跳法,你能回答她的问题吗?答案对1000000007取模。
输入格式
第一行输入一个正整数n,其中(3 <= n <= 100000)。 第二行输入n个正整数,分别表示从起点到终点每排的格子数a[i],以空格隔开。(1 <= a[i] <= 1000,保证起点和终点所在的排格子数为1)。
输出格式
输出一个正整数表示跳法的数量,答案对1000000007取模。
输入样例
6
1 1 1 1 2 1
输出样例
13
数据范围
对于100%的数据,3 <= n <= 100000,1 <= a[i] <= 1000;
样例解释
第一步:跳到第二排,有1种跳法;
第二步:跳到第三排,有2种跳法;
第三步:跳到第四排,有3种跳法;
第四步:跳到第五排,有10种跳法;
第五步:跳到第六排,有13种跳法。
思路:还是像上台阶那题类似,只是多了组合过程,利用乘法分布即可,乘上a[i]就是每一条路到达的最终位置!!
#include<iostream>
using namespace std;
#define ll long long
ll MOD = 1000000007;
const int N = 100010;
ll f[N],a[N];
int main()
{
int n;
cin>>n;
for(int i = 0;i < n;i++)
{
cin>>a[i];
}
f[0] = 1;
f[1] = a[1];//a[ ]表示起点哦
for(int i = 2;i < n;i++)
{
f[i] = (f[i-1]+f[i-2]) * a[i] % MOD;
}
cout<<f[n-1]<<endl;
return 0;
}
ProjectEuler 7
前6个质数是2, 3, 5, 7, 11, 13。第6个质数是13。
输入n,输出第n个质数。
输入格式
第一行输入组数T, 接下来T行,每行一个整数n。 (1 <= T <= 100,1 <= N <= 10001)
输出格式
对于每组数据,输出一个数,表示第n个质数。
输入样例
3
3
6
10001
输出样例
5
13
104743
思路:预先处理出前 n 个质数,然后针对每一个询问,直接输出对应的质数即可。
#include<iostream>
using namespace std;
const int N = 10001;
int p[N];
bool is_prime(int x)
{
for(int i = 2;i * i <= x;i++)
{
if(x % i == 0)return false;
}
return true;
}
int main()
{
int T;
cin>>T;
//需要预处理
int x = 2;
int cnt = 0;
while(cnt <= N)
{
if(is_prime(x)) p[++cnt] = x;
x++;
}
while(T--)
{
int n;
cin>>n;
int y = p[n];
cout<<y<<endl;
}
return 0;
}
ProjectEuler 37
3797有一个有趣的性质,他自己是一个质数,当我们从左向右依次删去每一位数字的时候,剩下的部分依然是质数:3797, 797, 97 和 7。
同样的,我们从右向左依次删去每一位数字,剩下的部分依然是质数:3797, 379, 37 和 3。这样的质数我们称之为“可截断的质数”。
输入n,问小于等于n的所有可截断的质数的和是多少。(仅一位的质数2, 3, 5 和 7 不被认为是可截断的质数。)
输入格式
第一行输入组数T, 接下来T行,每行一个整数n。 (1 <= T <= 23,1 <= n <= 1000000)
输出格式
对于每组数据,输出一个数,表示小于等于n的所有可截断的质数的和是多少。
输入样例
2
100
1000000
输出样例
186
748317
#include<iostream>
#include<map>
using namespace std;
map<int,bool>mp1;
map<int,bool>mp2;
bool is_prime(int x)
{
if(x == 1)return 0;
if(x == 2 || x == 3)return 1;
for(int i = 2;i * i<= x;i++)
{
if(x % i == 0)return false;
}
return true;
}
void dfs(int x,int p)
{
if(x > 1000010)return;
if(x > 10)//如果 x 超过了我要求的范围,那就返回,因为题目说了仅一位的质数不被认为是可截断的质数,所以不能算一位数
{
mp1[x] = 1;
}
for(int i = 1;i <= 9;i++)
{
int val = i * p + x;
if(is_prime(val)) dfs(val,p * 10);
}
}
void dfs2(int x)
{
if(x > 1000010)return;
if(x > 10)
{
mp2[x] = 1;
}
for(int i = 1;i <= 9;i++)
{
int val = x * 10 + i;
if(is_prime(val)) dfs2(val);
}
}
int main()
{
dfs(0,1);
dfs2(0);
int T;
cin>>T;
while(T--)
{
int n,cnt = 0;
cin>>n;
for(auto i : mp1)
{
if(mp2[i.first] && i.first <= n)
{
cnt += i.first;
}
}
cout<<cnt<<endl;
}
return 0;
}
1到N的最小公倍数
这一天小明学习了最小公倍数的知识,于是他想知道,1到一个数N之间所有整数的最小公倍数是多少呢?
聪明的你想要帮助小明解决这个问题,但老师提醒道,这个数可能会非常大,于是你决定将它对1000000007取模。
输入格式
输入一个正整数N,表示数字的上界。其中2≤N≤10000。
输出格式
输出一个数,表示这个最小公倍数取模后的结果。
输入样例
4
输出样例
12
数据范围
对于10%的数据,2≤N≤5; 对于30%的数据,2≤N≤100; 对于100%的数据,2≤N≤10000;

思路就是这样(上图是从1~10开始的最小公倍数) 当然百度也有
#include<iostream>
using namespace std;
#define ll long long
ll MOD = 1000000007;
bool is_prime(int x)
{
for(int i = 2;i * i <= x;i++)
{
if(x % i == 0) return false;
}
return true;
}
int main()
{
ll n,ans = 1;
cin>>n;
for(int i = 2;i < n;i++)
{
if(is_prime(i))
{
int val = i;
while(val <= n)
{
val *= i;
ans = (ans * i) % MOD;
}
}
}
cout<<ans<<endl;
return 0;
}
分解质因数
请你帮小瓜将正整数n分解质因数,并从小到大输出所有的质因数(如果一个质因数出现多次,则输出多次)。
输入格式
一行一个正整数n,保证1<=n<=10^8。
输出格式
若干行,每行表示n的一个质因数。按从小到大的顺序输出质因数。
输入样例
12
输出样例
2
2
3
#include<iostream>
using namespace std;
int main()
{
int m;
cin>>m;
//枚举根号m
for(int i = 2;i * i <= m;i++)
{
while(m % i == 0)
{
cout<<i<<endl;
m /= i;
}
}
if(m > 1)cout<<m<<endl;
return 0;
}
ProjectEuler 12
输入一个自然数n。问最小的,包含大于n个约数的三角数,是多少。
所谓三角数,是形如i*(i+1)/2的数,比如1,3,6,10,15,21,28,……
输入格式
第一行输入组数T, 接下来T行,每行一个整数n。 (1 <= T <= 50,0 <= N <= 500)
输出格式
对于每组数据,输出一个三角数,表示答案。
输入样例
6
0
1
2
3
4
500
输出样例
1
3
6
6
28
76576500
题解思路:
一个有 500 个约数的数字,可能很大,如果我们逐一计算所有三角数的约数数量 d(n) ,可能会出现超时。实际上我们可以利用三角数的某些特性,快速的计算 d(n) 。
F(n)=i∗(i+1)/2 。
而 i 同 i+1 是互质的。
如果 i 是偶数,i/2 与 i+1 互质。
如果 i 是奇数,i 与(i+1)/2 互质。
如果 2 个数是互质的,那么d(i∗j)=d(i)∗d(j) ,所以我们只需要预处理出较小范围内,每个数的约数,就可以快速计算三角数的约数数量。
写出来其实就是枚举+check!!
先附上超时代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 630;
int ans[N];
int get(int n) {
int cnt = 0;
for (int i = 1; i * i <= n; ++i) {
if (n % i == 0) {
++cnt;
if (n / i != i) ++cnt;
}
}
return cnt;
}
void solve() {
int n; cin >> n;
for (int i = 1; ; ++i) {
int tri = i * (i + 1) / 2;
int div = get(tri);
if (div > n) {
ans[i] = tri;
cout << tri << endl;
return ;
}
}
}
int main(){
int t; cin >> t;
while (t--) {
solve();
}
}
代码优化:加个记忆化,就是你每次算出来一个数的因子数,然后就把他存起来,下次就不用算了(也就是每次询问他是不是会调用很多次重复的 div(1), div(2), div(3)...直接把他存起来)所以,lazt [ ] 是计算出来的值,dis 有没有被算
我们枚举的 i 是第 i 个三角数,可以用 int S = i * (i + 1) / 2 表示,然后我们记它的因子数目是 div,如果没有计算过第 i 个三角数的因子数,也就是说 vis[i] = 0,那就计算
注意:我们这里不能先判断dis>n哦,因为我们的dis属于未知数
所以优化以后的代码就是:
#include<iostream>
using namespace std;
const int N = 1e6+10;
int lazt[N];//把递归的结果存下来
bool dis[N];//判断是否被标记
int is_prime(int n)
{
int cnt = 0;
for(int i = 1;i * i <= n;i++)
{
if(n % i == 0)
{
cnt++;
if(n / i != i)cnt++;
}
}
return cnt;
}
void volve()
{
int n;
cin>>n;
for(int i = 1;;i++)
{
int S = i * (i + 1) / 2;
int div;
if(dis[i] == 0)
{
dis[i] = 1;
div = is_prime(S);
lazt[i] = div;
}
else
{
div = lazt[i];
}
if(div > n)
{
cout<<S<<endl;
return;
}
}
}
int main()
{
int T;
cin>>T;
while(T--)
{
volve();
}
return 0;
}
完美AC!
n^n的末位数字
给出一个整数N,输出N^N(N的N次方)的十进制表示的末位数字。
输入格式
一个数N(1 <= N <= 10^9)
输出格式
输出N^N的末位数字
输入样例
13
输出样例
3
思路:
我们观察 0−9 的幂 %10 的结果。
| 幂%10 | |
|---|---|
| 0 | [0] |
| 1 | [1] |
| 2 | [2,4,8,6] |
| 3 | [3,9,7,1] |
| 4 | [4,6] |
| 5 | [5] |
| 6 | [6] |
| 7 | [7,9,3,1] |
| 8 | [8,4,2,6] |
| 9 | [9,1] |
都会出现循环,并且循环的长度只有 1,2,4 共 3 种。他们都是 4 的约数,因此无论多大的 n 总可以 转为 4 以内的一个数来进行计算。我们令 a=n%10,b=n%4 。就可以快速计算结果了。
#include<iostream>
using namespace std;
int main()
{
int n,a,b,ans = 1;
cin>>n;
a = n % 10,b = (n-1) % 4 + 1;
for(int i = 0;i < b;i++)
ans *= a;
cout<<ans % 10<<endl;
return 0;
}
求递推序列的第N项
有一个序列是这样定义的:f(1) = 1, f(2) = 1, f(n) = (A * f(n - 1) + B * f(n - 2)) mod 7.
给出A,B和N,求f(n)的值。
输入格式
输入3个数:A,B,N。数字之间用空格分割。(-10000 <= A, B <= 10000, 1 <= N <= 10^9)
输出格式
输出f(n)的值。
输入样例
3 -1 5
输出样例
6
思路:模拟即可
但是需要注意:
1,首先,对7 去余那么循环节的长度最长为49(7*7)(关于这里可以自己好好想一下,提升一下自己的思维)
2,其次,mod运算是求一个正数的值,而c++里面则会算出负值,因此取mod之后加上mod再取mod
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
ll v[50];
int main()
{
ll n,m,k,i;
cin>>n>>m>>k;
v[1] = 1;v[2] = 1;
for( i = 3;i<=49;i++)
{
v[i] = ((n*v[i-1] + m*v[i-2])%7 + 7)%7;
if(v[i] == 1 && v[i-1] ==1) break;
}
i -= 2;v[0] = v[i];
// for(int j = 0;j<=i;j++){
// printf("%d ",v[j]);
// }printf("\n");
printf("%lld",v[k%i]);
return 0;
}
机器人走方格
M * N的方格,一个机器人要从左上格子的正中央走到右下格子的正中央。它每步可以从一个格子正中央移动到相邻格子正中央,且只能向右或向下走。请问有多少种不同的走法?由于方法数量可能很大,只需要输出Mod 10^9 + 7的结果。
输入格式
第1行,2个数M,N,中间用空格隔开。(2 <= m,n <= 1000)
输出格式
输出走法的数量。
输入样例
2 3
输出样例
3
思路:直接dp即可,找到上一个目标
调了半小时,哪知道是模数的问题(多打了一个零)
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
const int N = 1010;
ll MOD = 1000000007;
ll a[N][N];
void chushihua(int n,int m)
{
for(int i = 1;i <= m;i++)
a[1][i] = 1;
for(int i = 1;i <= n;i++)
a[i][1] = 1;
}
int main()
{
ll n,m;
cin>>n>>m;
chushihua(n,m);
for(int i = 2;i <= n;i++)
{
for(int j = 2;j <= m;j++)
{
a[i][j] += (a[i-1][j] + a[i][j-1]) % MOD;
}
}
cout<<a[n][m]<<endl;
return 0;
}
机器人走方格 V5
M∗N的方格,一个机器人从左上的格子走到右下的格子,即从初始位置 (1,1) ,走到结束位置 (m,n)。每步只能向右一格、或向下一格、或向右下一格走。有多少种不同的走法?
由于方法数量可能很大,只需要输出mod 10^9+7的结果。
输入格式
第一行输入两个数M,N,以空格隔开。(2≤m,n≤1000)
输出格式
输出一个数,走法的数量。
输入样例
2 3
输出样例
5
思路:跟上题一样,只不过多加了一个条件
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
const int N = 1010;
ll MOD = 1000000007;
ll a[N][N];
void chushihua(int n,int m)
{
for(int i = 1;i <= m;i++)
a[1][i] = 1;
for(int i = 1;i <= n;i++)
a[i][1] = 1;
}
int main()
{
ll n,m;
cin>>n>>m;
chushihua(n,m);
for(int i = 2;i <= n;i++)
{
for(int j = 2;j <= m;j++)
{
a[i][j] += (a[i-1][j] + a[i][j-1] + a[i-1][j-1]) % MOD;
}
}
cout<<a[n][m]<<endl;
return 0;
}

本文主要介绍了51nod网站上的多个算法题目,包括递推序列的计算、康托展开的运算及逆运算,并提供了相关的题目链接。内容涵盖数学、位运算、数据结构等多个方面,如数数字问题、队列复原、合法括号序列等。同时,文章探讨了如何解决和为k的连续区间问题,以及涉及斐波那契数列、质因数分解、前缀和等算法的应用。此外,还讨论了数组、栈、队列和位运算在不同问题中的应用策略,旨在帮助读者提高算法解题能力。

1205

被折叠的 条评论
为什么被折叠?



