51nod 冲刺题

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

进阶习题: 

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 的康托展开值。

未出现且小于当前值的数展开值
31,2=>𝑎5=21𝑎5×4!48
41,2=>𝑎4=21𝑎4×3!12
1无  =>𝑎3=0𝑎3×2!0
52=>𝑎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&lt;=100000 0&lt;=ai&lt;=100000000 q&lt;=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 ​​ 的幂。

//一个思路

  1. (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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值