Table of Contents

1. P1088 火星人

思路:

这道题的本质是将给定排列顺序的数组按照字典序改变排列顺序m次后输出。

STL版本就是调用next_permutaion(nums, nums+n)函数m次就可以了。

STL版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<bits/stdc++.h>

using namespace std;
int n, m, nums[100010];

inline int read(int &res){
int w = 1;
char ch = ' ';
while(ch != '-' && (ch < '0' || ch >'9'))
ch = getchar();
if(ch == '-')
w = -1;
else
res = ch - '0';
for(ch = getchar(); ch >= '0' && ch <= '9'; ch = getchar())
res = res * 10 + ch - '0';
res *= w;
return res;
}
int main(){
read(n), read(m);
for(int i = 0; i < n; i++)
read(nums[i]);
while(m--)
next_permutation(nums, nums+n);
for(int i = 0; i < n; i++)
cout<<nums[i]<<" ";
return 0;
}


非STL版本比较简单的思路就是自己手写一个排列顺序的函数。

2. P1045 麦森数

思路:

这道题要我们输出两个问题的解:

  1. $2^p-1$的位数;
  2. $2^p-1$后500位的具体数值。

此处P的范围在1000到3100000。我们只能一个一个问题来解决。

首先,考虑到2^p的末尾不可能是0,所以2^p-12^p位数一定是相同的。这时我们很容易就得到2^p的位数是$log_{10}{2^p}+1=1+p*log_{10}2$。取整即可。

其次,这道题首先会想到快速幂。但是高精度的快速幂真的好难啊,,,好在我们只需要考虑后500位的数据。

最后的结果如下:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include<bits/stdc++.h>

const int N = 1010;
const int BASE = 10;
using namespace std;

int p, nums[N], cp[N], res[N], ans[N];

void multi_array(int nums1[N], int nums2[N]){
memset(res, 0, sizeof(res));
res[0] = 1000;
for(int i = 1; i <= 500; i++)
for(int j = 1; j <= 500; j++){
res[i+j-1] += nums1[i] * nums2[j];
}
for(int i = 1; i < res[0]; i++){
res[i+1] += res[i] / BASE;
res[i] %= BASE;
}
while(res[0] <= N && res[res[0]] >= BASE){
res[res[0] + 1] += res[res[0]] / BASE;
res[res[0]] %= BASE;
res[0]++;
}
memset(nums1, 0, sizeof(nums1));
for(int i = 0; i <= res[0]; i++)
nums1[i] = res[i];
}

void fast_pow(int nums[N], int p, int ans[N]){
if(p == 0){
memset(ans, 0, sizeof(0));
ans[0] = ans[1] = 1;
return;
}
if(p == 1){
for(int i = 0; i <= nums[0]; i++)
ans[i] = nums[i];
return;
}
memset(ans, 0, sizeof(ans));
ans[0] = ans[1] = 1;
while(p){
if(p & 1){
multi_array(ans, nums);
}
p >>= 1;
multi_array(nums, nums);
}
return;
}


int main(){
scanf("%d", &p);
cout<<(int)(p*log10(2) + 1)<<endl;
nums[0] = 1, nums[1] = 2;
fast_pow(nums, p, ans);
ans[1] -= 1;
for(int i = 500; i >= 1; i--)
if(i != 500 && i % 50 == 0)
printf("\n%d",ans[i]);
else printf("%d",ans[i]);
return 0;
}

注意这里只处理后500位。

3. P1403 [AHOI2005]约数研究

思路:

把每个正数N的约数的个数用 f(N) 来表示,求$\sum_{i=1}^{n}f(i)$的值。

考虑思路:逆向思维,如果我们去枚举1到N的每个数的约数个数相加,必然时间复杂度要爆炸。反过来想,对于一个数N,它的约数必定在1到N之间。于是,我们遍历1到N之间的每个数,每个数都必然属于一个或多个数的约数集合中。比如1,它是1,2,3…N所有数的约数,而2则是2,4,6..的约数。对每个正整数$i$,它属于$N/i$个正整数的约数集合中。所以我们枚举每一个数,累加它属于多少个正整数的约数中即可得到答案。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<bits/stdc++.h>

using namespace std;

int n;

int main(){
scanf("%d", &n);
int ans = 0;
for(int i = 1; i <= n; i++)
ans += n/i;
cout<<ans<<endl;
return 0;
}

4. P1017 进制转换

思路:

这道题给定一个十进制整数,给定一个负数,将这个十进制整数转换为以这个负数为基本进制的表示形式。

这道题最开始想歪了,我想的是从高位依次求到低位,十分复杂。实际上的做法是从低位不断求余数和商,将得到的的商继续递归地去求余数。

比如:101在十进制中,第一次求余数是1,商是10;再把10对10求余数是0,商是1;再把1对10求余是1,商是0;倒过来,101在十进制中的表示就是101。

但是在负数的情况下要注意一点,我们希望余数是正数(最后表示的结果每一位都是正数),否则如果是负数的话就可以继续减去一个除数,商比原来加1。

(商+1)×除数+余数-除数=原来的被除数。

最终代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<bits/stdc++.h>

using namespace std;

int n, base, pos;
char nums[100];
char dict[20] = {'0', '1', '2', '3', '4', '5','6', '7', '8', '9',
'A', 'B','C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'};


void transfer(int n, int base){
if(n == 0)
return;
int remain = n % base;
if(remain < 0){
remain -= base;
n += base;
}
int div= n / base;
nums[pos++] = dict[remain];
transfer(div, base);
}
int main(){
scanf("%d%d", &n, &base);
transfer(n, base);
cout<<n<<"=";
for(int i = pos-1; i >= 0; i--)
cout<<nums[i];
cout<<"(base"<<base<<")"<<endl;
return 0;
}

5. P1147 连续自然数和

思路:

这道题有很多做法:

1. 枚举左右端点

枚举左右端点,得到两个端点之间的自然数和,与目标值进行比较,然后更新左右端点。O(N)复杂度。

2. 前缀和数组

前缀和数组也可以通过枚举左右端点的方式来枚举前缀和。原理是一样的。难点在于数据大小的限制,有可能造成数组访问越界和加和溢出的问题。因此最好使用long long进行求和。

3. 解二次方程

因为sum(L, R) = (L+R)(R-L+1)/2,所以可以令sum(L, R) = M,令K1 = L+R,K2 = R - L + 1,解出L和R关于K1和K2的关系式。L=(K2-K1+1)/2, R=(K1+K2-1)/2。不过有一种特殊情况,就是L=R的情况,这种情况是不允许的。然后遍历即可。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<bits/stdc++.h>

using namespace std;
long long n, nums[2000001];
int getArray(int n){
for(int i = 1; i <= 2000001 && nums[i] <= 2 * n; i++){
nums[i] = nums[i-1] + i;
}
}

void findAns(int n){
for(int left = 0, right = 0; left < n/2;){
if(nums[right] - nums[left] < n)
right++;
else if(nums[right] - nums[left] == n){
cout<<left+1<<" "<<right<<endl;
left++;
}else{
left++;
}
}
}
int main(){
scanf("%d", &n);
getArray(n);
findAns(n);
return 0;
}

6. P1029 最大公约数和最小公倍数问题

思路:

这道题给出x,y,要求符合以x为最大公约数以y为最小公倍数的数对P,Q的个数。

tips: P*Q = x * y

这里一定要注意,两个数的乘积就是这两个数的最大公约数和最小公倍数的乘积。

然后可以暴力枚举即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<bits/stdc++.h>

using namespace std;
#define ll long long

int x, y, ans;

ll gcd(ll m, ll n){
if(n == 0) return m;
return gcd(n, m%n);
}
int main(){
scanf("%d%d", &x, &y);
ll p = 1ll * x * y;
for(int i = 1; i <= sqrt(p); i++){
if(p % i == 0 && gcd(i, p/i) == x){
ans+=2;
if(1ll*i*i == p)
ans--;
}
}
cout<<ans<<endl;
return 0;
}

注意gcd函数的写法!!!!