C++語法入門刷題筆記(二)
1.acwing725.完全數
一個整數,除了本身以外的其他所有約數的和如果等於該數,那麼我們就稱這個整數為完全數。
例如,6就是一個完全數,因為它的除了本身以外的其他約數的和為 1+2+3 = 6。
現在,給定你N個整數,請你依次判斷這些數是否是完全數。
每個測試用例輸出一個結果,每個結果佔一行。
// 注意這樣時間複雜度太大,會超時 #include <cstdio> using namespace std; int main() { int n; scanf("%d", &n); while (n--) { int x,sum = 0; scanf("%d", &x); for (int i = 1;i < x;i++) { if (x%i == 0) sum += i; } if (sum == x) printf("%d is perfect\n",x); else { printf("%d is not perfect\n",x); } } return 0; } // 優化版本 #include <cstdio> using namespace std; int main() { int n; scanf("%d", &n); while (n--) { int x,sum = 0; scanf("%d", &x); for (int i = 1;i*i <= x;i++)// 減少迴圈次數 { if (x%i == 0) { if (i < x) sum += i; if (i != x/i && x/i < x) sum += x/i; // 關鍵步驟 } } if (sum == x) printf("%d is perfect\n",x); else { printf("%d is not perfect\n",x); } } return 0; }
特別解法
有點數學基礎的人都應該知道100000000內的完全數沒有幾個……
數學部分
100000000100000000內的完全數有6,28,496,8128,335503366,28,496,8128,33550336.所以說多背一點數字是很有用的
既然這道題可以直接O(1)O(1)解決,我們不妨來說一下完全數的各種性質以備於各種毒瘤的演算法競賽.
完全數比較重要的幾個性質
(也是我只知道的幾個性質)
所有完全數都是三角形數
目前截止發現的所有完全數都以66或2828結尾
到現在為止,數學家們一共發現了4848個完全數,且4848個完全數全部是偶數
如果有人們沒有找到的奇完全數,則它一定可以寫成12p+112p+1或36p+936p+9的形式,而且pp是素數
奇完全數一定大於1030010300
完全數的約數的倒數之和為調和數
完全數可以表示成連續奇數的立方和
完全數可以表示成22的連續自然數的次冪之和,且這些自然數的數量必定是素數
完全數計演算法
若2p−1是素數(亦稱其為梅森素數),則2p−1∗(2p−1)是完全數.
時間複雜度
這裡資料小了一點,對於每個資料時間複雜度為O(1)O(1).
資料再大我都不怕,反正現在找到48個不如個map然後對映一個布林類不就好了!
#include <bits/stdc++.h> using namespace std; int main() { int t; cin >> t; while (t--) { int n; cin >> n; if (n == 6 || n == 28 || n == 496 || n == 8128 || n == 33550336) cout << n << " is perfect" << endl; else cout << n << " is not perfect" << endl; } return 0; }
2.錯誤小提示
// 錯誤寫法,第二句不會執行,很容易犯錯
if (...) xxx; xxx;
// 正確寫法
1.if (...) {xxx; xxx;}
2.if (...)
{
xxx; xxx;
}
3.if() xxx,xxx;
3.acwing727.菱形
#include <cstdio>
#include <algorithm>
using namespace std;
int main()
{
int n;
scanf("%d", &n);
for (int i = 0;i < n;i++)
{
for (int j = 0;j < n;j++)
{
if (abs(i-n/2) + abs(j-n/2) <= n/2)// abs函式求絕對值
{
printf("*");
}
else printf(" ");// 漏掉這裡就只能列印右半邊圖形
}
printf("\n");
}
return 0;
}
大致思路:來自於y總講解。根據圖形特點,這是一個正方形中的菱形,離正方形中心的曼哈頓距離<= n/2的點列印*,其他點列印空格。
例如在平面上,座標(x1,y1)的i點與座標(x2,y2)的j點的曼哈頓距離為:
d(i,j)=|X1-X2|+|Y1-Y2|.
4.高精度浮點數運算問題
如果進行了一系列對浮點數的運算,精度可能變得不準確,這時運算之前、之後的浮點數可能不相等,但實際上是相等的,只是因為精度丟失。看例子:
int a = 3;
if (sqrt(3) * sqrt(3) != 3) puts("!!!");
結果我們會發現顯示!!!,說明精度丟失了,但實際結果應該相等。
解決方案如下:
// 當兩個浮點數相差足夠小時,我們可以認為他們相等
const double eps = 1e-6;
int a = 3;
if (fabs(sqrt(3) * sqrt(3) - 3) < eps) puts("相等");
// double fabs(double x),fabs用於求絕對值
這時我們便會發現顯示相等了。
5.陣列的初始化
注意一下:
// 將陣列全部初始化為0的寫法
int f[10] = {0};// 很常用
切記:定義在函式內部(包括main函式)的陣列,如果不初始化,則是隨機的。
知識點:放在函式內部的陣列空間存放在棧裡,如果棧的空間不夠,則可能會出現段錯誤。
但放在函式外部的陣列空間存放在堆裡,只要不超過記憶體限制,可以定的比較大。比如可以放main函式之外。
定義在函式外部的陣列,如果不初始化,則是全部預設為0的。
這就是區域性變數和全域性變數的區別。
注意陣列下標越界的問題,會導致段錯誤!
6.練習題.旋轉陣列
輸入一個n,再輸入n個整數。將這個陣列順時針旋轉k(k <= n)次,最後將結果輸出。
// 一般做法,有兩重迴圈
int a[100];
int n,k;
cin >> n >> k;
for (int i = 0;i < n;i++) cin >> a[i];
while (k--)
{
int t = a[n-1];
for (int i = n-2;i >= 0;i--)
{
a[i+1] = a[i];
}
a[0] = t;
}
for (int i = 0;i < n;i++) cout << a[i] << ' ';
// 更巧妙的做法,只有一重迴圈
//reverse函式用於反轉在[first,last)範圍內的順序(包括first指向的元素,不包括last指向的元素),reverse函式沒有返回值
int a[100];
int n,k;
cin >> n >> k;
for (int i = 0;i < n;i++) cin >> a[i];
reverse(a,a + n);// 翻轉a[0]到a[n-1]的n個數
reverse(a,a + k);// 翻轉a[0]到a[k-1]的k個數
reverse(a + k,a + n);
注意:reverse函式的引數是左閉右開的。
//演示reverse函式
int a[3] = {0,1,2},b[3] = {0,1,2};
reverse(a,a+1);// 翻轉1個數
reverse(b,b+2);// 翻轉2個數
for (int i = 0;i < 3;i++) cout << a[i] << endl;
cout << endl;
for (int i = 0;i < 3;i++) cout << b[i] << endl;
//輸出如下
0
1
2
1
0
2
7.acwing743.陣列中的行
輸入一個二維陣列M[12][12]
,根據輸入的要求,求出二維陣列中某一行的元素的平均值或元素的和。
輸入格式
第一行輸入整數L,表示所求的具體行數(行數從0開始計數)。
第二行包含一個大寫字母,若為’S’,則表示需要求出第 L 行的元素的和,若為’M’,則表示需要求出第 L 行的元素的平均值。
接下來12行,每行包含12個用空格隔開的浮點數,表示這個二維陣列,其中第 i+1 行的第 j+1 個數表示陣列元素M[i][j]
。輸出一個數,表示所求的平均數或元素的和的值,保留一位小數。
#include <iostream>
using namespace std;
int main()
{
int l;
char op;
cin >> l >> op;
double s=0;
for(int i=0;i<12;i++)
{
for(int j=0;j<12;j++)
{
double a;
cin >> a;
if(i==l) s+=a;
}
}
printf("%.1lf",op=='S' ? s : s/12);//簡化操作
}
8.acwing749.陣列的上方區域
#include <iostream>
using namespace std;
int main()
{
char c;
cin>>c;
double a,res=0;
for(int i=0;i<12;i++)
for(int j=0;j<12;j++)
{
cin>>a;
if(j>i&&i+j<11)res+=a;//關鍵是找到綠色方塊的分佈規律,兩塊區域的交集
}
printf("%.1lf",c=='S'?res:res/30);
}
9.acwing753.平方矩陣 I
// 這題不會做,困難
// 主要思路就是求到上下左右四條邊的最小值,找到規律就不難
// y總解法
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int n,up,down,left,right;
while (cin >> n,n)
{
for (int i = 1;i <= n;i++)
{
for (int j = 1;j <= n;j++)
{
up = i;down = n-i+1;left = j;right = n-j+1;// 這四個變數還可以省略
cout << min(min(up,down),min(left,right)) << ' ';
}
cout << endl;
}
cout << endl;
}
return 0;
}
10.acwing756.蛇形矩陣
// 來自於微軟面試題,嘗試寫了一小段,沒完成
// 有一定難度
// y總解題思路
#include <iostream>
using namespace std;
int res[100][100];// 小細節,定義為全域性變數預設初始化為0
int main()
{
int n,m;
cin >> n >> m;
int dx[] = {0,1,0,-1},dy[] = {1,0,-1,0};// 定義橫縱座標偏移量
for (int x = 0,y = 0,d = 0,k = 1;k <= n*m;k++)
// d用於確定前進方向,開始時向右走
{
res[x][y] = k;
int a = x + dx[d],b = y + dy[d];
if (a < 0 || a >= n || b < 0 || b >= m || res[a][b])
// 判斷是否出界,或者已經遍歷過,未遍歷過res[a][b] == 0
// 符合條件則將方向順時針旋轉90度
{
d = (d + 1)%4;
a = x + dx[d],b = y + dy[d];
}
x = a,y = b;
}
for (int i = 0;i < n;i++)
{
for (int j = 0;j < m;j++)
{
cout << res[i][j] << ' ';
}
cout << endl;
}
return 0;
}
// 題解2,紫書
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn = 150;
int a[maxn][maxn];
int main()
{
int n, m;
cin >> n >> m;
memset(a, 0, sizeof(a));// 初始化陣列a為0
// 作用是在一段記憶體塊中填充某個給定的值,它是對較大的結構體或陣列進行清零操作的一種最快方法
int x = 0, y = 0; //初始座標座標,(0,0)
int cnt = 1; //初始化第一個數
a[x][y] = cnt;
while (cnt < n * m )
{
//用下一筆的位置來判斷
//向右, 符合條件,則填入下一筆。____提前預判
while (y + 1 < m && !a[x][y + 1]) a[x][ ++ y] = ++ cnt;
//向下
while (x + 1 < n && !a[x + 1][y]) a[ ++ x][y] = ++ cnt;
//向左
while (y - 1 >= 0 && !a[x][y - 1]) a[x][ -- y] = ++ cnt;
//向上
while (x - 1 >= 0 && !a[x - 1][y]) a[ -- x][y] = ++ cnt;
}
for (int i = 0; i < n; i ++ )
{
for (int j = 0; j < m; j ++ )
{
cout << a[i][j] << " ";
}
cout << endl;
}
return 0;
}