演算法設計與分析實驗報告
實驗一 集合的表示與操作演算法設計
https://www.cnblogs.com/31415926535x/p/10963938.html
實驗目的
通過這次實驗瞭解體會並掌握基本的遞迴分治演算法以及貪心演算法的思想,並有能力解決一些具體的問題,通過c++來實現解題的過程,進一步的熟悉演算法的流程。
實驗內容
實驗大致分為三部分: 概述 、 遞迴與分治策略 、 貪心演算法 。對於每一類問題,選擇至少一道題目進行思考並用程式碼驗證演算法的正確性,分析演算法的可行性。
概述
統計數字問題
題目
問題描述: 一本書的頁碼從自然數1 開始順序編碼直到自然數n。書的頁碼按照通常的習慣編排,每個頁碼都不含多餘的前導數字0。例如,第6 頁用數字6 表示,而不是06 或006 等。數字計數問題要求對給定書的總頁碼n,計算出書的全部頁碼中分別用到多少次數字0,1, 2,…,9。 程式設計任務: 給定表示書的總頁碼的10 進位制整數n (1≤n≤109) 。程式設計計算書的全部頁碼中分別用到多少次數字0,1,2,…,9。 資料輸入: 輸入資料由檔名為input.txt 的文字檔案提供。每個檔案只有1 行,給出表示書的總頁碼的整數n。 結果輸出: 程式執行結束時,將計算結果輸出到檔案output.txt 中。輸出檔案共有10 行,在第k 行輸出頁碼中用到數字k-1 的次數,k=1,2,…,10。 輸入檔案示例 input.txt 11 輸出檔案示例 output.txt 1 4 1 1 1 1 1 1 1 1
演算法思路
使用一個10位大小的整型陣列儲存每一個數字出現的次數,對於 \(1 \sim n\) 中出現的每一個數,求出最後一位數,個數增一,最後輸出所有的 \(0 \sim 9\) 數字的個數即可。
實驗程式
#include <bits/stdc++.h> using namespace std; const int maxn = 10; int a[maxn]; void solve(int n) { while(n) { ++a[n % 10]; n /= 10; } return; } int main() { freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); int n; scanf("%d", &n); memset(a, 0, sizeof a); for(int i = 1; i <= n; ++i)solve(i); for(int i = 0; i <= 9; ++i)printf("%d\n", a[i]); }
測試結果
input:
11
output:
1
4
1
1
1
1
1
1
1
1
最多約數問題
題目
問題描述: 正整數x 的約數是能整除x 的正整數。正整數x 的約數個數記為div(x) 。例如,1,2, 5,10 都是正整數10 的約數,且div(10)=4 。設a 和b 是2 個正整數,a≤b,找出a 和b 之間約數個數最多的數x。 程式設計任務: 對於給定的2 個正整數a≤b,程式設計計算a 和b 之間約數個數最多的數。 資料輸入: 輸入資料由檔名為input.txt 的文字檔案提供。檔案的第1 行有2 個正整數a 和b。 結果輸出: 程式執行結束時,若找到的a 和b 之間約數個數最多的數是x,將div(x)輸出到檔案output.txt 中。 輸入檔案示例 輸出檔案示例 input.txt output.txt 1 36 9
演算法思路
預處理出所有 \(1 \sim maxn\) 的每一個數的因數的個數,然後對每一個測試遍歷 \(a \sim b\) 找出因數最多的數,輸出即可。
實驗程式
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int a[maxn];
void init()
{
for(int i = 2; i <= maxn; ++i)
for(int j = i; j <= maxn; j += i)
++a[j];
return;
}
int main()
{
// freopen("input.in", "r", stdin);
// freopen("output.out", "w", stdout);
init();
int l, r;
while(~scanf("%d%d", &l, &r))
{
int ans = 0;
for(int i = l; i <= r; ++i)
ans = max(ans, a[i]);
printf("%d\n", ++ans);
}
}
測試結果
input.txt output.txt
1 36 9
字典序問題
題目
問題描述:
在資料加密和資料壓縮中常需要對特殊的字串進行編碼。給定的字母表A 由26 個小寫英文字母組成A={a,b,…,z}。該字母表產生的升序字串是指字串中字母按照從左到右出現的次序與字母在字母表中出現的次序相同,且每個字元最多出現1 次。例如,a,b,ab,bc,xyz 等字串都是升序字串。現在對字母表A 產生的所有長度不超過6 的升序字串按照字典序排列並編碼如下。
1 2
…
26 27 28
…
a b
…
z ab ac
…
對任意長度不超過6 的升序字串,迅速計算出它在上述字典中的編碼。
程式設計任務:
對於給定的長度不超過6 的升序字串,程式設計計算出它在上述字典中的編碼。
資料輸入:
輸入資料由檔名為input.txt 的文字檔案提供檔案的第一行是一個正整數k,表示接下來共有k 行接下來的k 行中,每行給出一個字串。
結果輸出:
程式執行結束時,將計算結果輸出到檔案output.txt 中。檔案共有k 行,每行對應於一
個字串的編碼。
輸入檔案示例 輸出檔案示例
input.txt output.txt
2 1
a 2
b
演算法思路
法1.找規律遞迴分段求和
手推出幾個字串的序號後可以看出,要求一個字串的編號,可以通過 \(求出k-1長的字串的最後一個的編號+與當前要求字串第一個字元字典序小的、長度一致的所有字串的數量+長度減一且對應字元相同的剩餘字串的個數的和\) 來求得待求字串的編號,顯然三次計算都有一個公共操作: 求一個串長為len且開頭為a的所有字串的數量 , 定義 \(sum(a, k)\) 表示以字元a開頭的長度為k的字串的數量, 顯然可以通過所有長度為 \(k - 1\) 開頭字元為大於a的字元與a的拼接可以得到,所以我們求出長度為 \(k - 1\) 開頭為 \(a + 1 \sim 26\)的所有字串的數量和便可以得到長度為 \(k\) 且開頭為a的字串的數量,也就是說: \(sum(a, k) = \sum_{i = a + 1}^{26}sum(i, k-1)\) ,這個可以通過遞迴的方式得到。
於是整個問題的結果流程為:
- 計算出所有長度為k-1的字串的數量(此時開頭字母為所有)呼叫26次sum()函式
- 計算出開頭字元小於待求字串開頭字元的所有長度為k的字串:呼叫 \(a-1\) 次sum()函式
- 計算長度為 \(k-1\) 且後面幾位的字元小於待求字串對應的那一位的字串的和
例如:
s: cefvz
- 先求出所有長度小於5的數量,即: \('a' \sim \ 'wxyz'\) 的數量
- 求出 \('abcde' \sim \ 'bwxyz'\) 的數量,這一段就是求長度為5的,開頭小於'c'的字串的數量
- 求出 \('cdefg' \sim \ 'cefvz'\) 的數量,這一段就是求長度為k-1=4的從'defg'到'efvz'的所有字串的數量
法2.暴力深搜儲存字典hash
因為題目給出的要求字串最大長度僅為6,所以可以深搜出所有的符合條件的字串,(dfs序即為字串的序號),然後對於每一個字串給定一個hash值,最後對於每一個詢問根據字串的hash便可得出序號。(通過這個方法可以生成測試資料)
實驗程式
法1
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5;
char s[10];
int sum(int i, int j)
{
int ret = 0;
if(j == 1)return 1;
else
{
for(int k = i + 1; k <= 26; ++k)
ret += sum(k, j - 1);
}
return ret;
}
void solve()
{
int ans = 0;
int len = strlen(s);
for(int i = 1; i <= len - 1; ++i)
for(int j = 1; j <= 26; ++j)
ans += sum(j, i);
// cout << ans << "--" << endl;
for(int i = 1; i <= s[0] - 'a' + 1 - 1; ++i)
ans += sum(i, len);
// cout << ans << "--" << endl;
for(int i = 1; i <= len - 1; ++i)
{
for(int j = s[i - 1] - 'a' + 1 + 1; j <= s[i] - 'a' + 1 - 1; ++j)
ans += sum(j, len - i);
}
printf("%d\n", ++ans);
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
int t;scanf("%d", &t);
while(t--)
{
scanf("%s", &s);
solve();
}
return 0;
}
法2
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5;
char s[10];
const int p = 1e3+7;
unordered_map<int, int> mp;
inline int gethash(int len)
{
int ret = 1;
for(int i = 0; i <= len - 1; ++i)
ret += ret * p + s[i] - 'a';
return ret;
}
int tot = 0;
void print(int len, int n)
{
if(n == len)
{
// printf("%s", s);
// for(int i = 0; i < len; ++i)printf("%c", s[i]);
// printf("\n");
mp[gethash(len)] = ++tot;
return;
}
if(n == 0)
{
for(int i = 0; i < 26; ++i)
{
s[0] = (char)(i + 'a');
print(len, n + 1);
}
}
else
{
for(int i = 0; i < 26; ++i)
{
if(s[n - 1] < i + 'a')
{
s[n] = (char)(i + 'a');
print(len, n + 1);
}
}
}
return;
}
void init()
{
tot = 0;
for(int i = 1; i <= 6; ++i)
print(i, 0);
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
init();
int t;scanf("%d", &t);
while(t--)
{
scanf("%s", &s);
printf("%d\n", mp[gethash(strlen(s))]);
}
return 0;
}
測試結果
input:
4
abc
bciop
acfuxz
uvwxyz
output:
352
31577
98306
313911
貪心演算法
程式儲存問題
題目
演算法實現題 程式儲存問題
問題描述:
設有n 個程式{1,2,…, n }要存放在長度為L 的磁帶上。程式i 存放在磁帶上的長度是li,1≤i≤n 。程式儲存問題要求確定這n 個程式在磁帶上的一個儲存方案,使得能夠在磁帶上儲存儘可能多的程式。
程式設計任務:
對於給定的n 個程式存放在磁帶上的長度,程式設計計算磁帶上最多可以儲存的程式數。
資料輸入:
由檔案input.txt 給出輸入資料。第一行是2 個正整數,分別表示檔案個數n 和磁帶的長度L。接下來的1 行中,有n 個正整數,表示程式存放在磁帶上的長度。
結果輸出:
將程式設計計算出的最多可以儲存的程式數輸出到檔案output.txt 。
輸入檔案示例輸出檔案示例
input.txt output.txt
6 50 5
2 3 13 8 80 20
演算法思路
因為要最大化儲存的數量,所以優先考慮儲存空間小的。
排序後取前面佔用空間和剛好小於等於最大空間的個數。
實驗程式
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5;
int a[maxn];
void solve()
{
int n, l;
scanf("%d%d", &n, &l);
for(int i = 1; i <= n; ++i)scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);
int ans = 0;
int sum = 0;
for(int i = 1; i <= n; ++i)
{
sum += a[i];
if(sum <= l)++ans;
else break;
}
printf("%d\n", ans);
return;
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
solve();
return 0;
}
測試結果
input:
6 50
2 3 13 8 80 20
output:
5
汽車加油問題
題目
演算法實現題 汽車加油問題
問題描述:
一輛汽車加滿油後可行駛n 公里。旅途中有若干個加油站。設計一個有效演算法,指出應在哪些加油站停靠加油,使沿途加油次數最少。並證明演算法能產生一個最優解。
程式設計任務:
對於給定的n 和k 個加油站位置,程式設計計算最少加油次數。
資料輸入:
由檔案input.txt 給出輸入資料。第一行有2 個正整數n 和k,表示汽車加滿油後可行駛n 公里,且旅途中有k 個加油站。接下來的1 行中,有k+1 個整數,表示第k 個加油站與第k-1 個加油站之間的距離。第0 個加油站表示出發地,汽車已加滿油。第k+1 個加油站表示目的地。
結果輸出:
將程式設計計算出的最少加油次數輸出到檔案output.txt 。如果無法到達目的地,則輸出”No Solution”。
輸入檔案示例輸出檔案示例
input.txt output.txt
7 7 4
1 2 3 4 5 1 6 6
演算法思路
貪心考慮加油,噹噹前剩餘油量不足以到達下一站是,在這一個站加油,否則一直走下去。
實驗程式
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5;
int n, k, a[maxn];
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
cin >> n >> k;
for(int i = 0; i <= k; ++i)cin >> a[i];
int ans = 0;
for(int i = 1; i <= k; ++i)
{
int sum = 0;
int nxt = i;
for(nxt = i; nxt <= k; ++nxt)
{
sum += a[nxt];
if(sum > n)break;
}
if(i == nxt)
{
cout << "No Solution" << endl;
return 0;
}
i = nxt - 1;
++ans;
if(nxt == k + 1)break;
}
cout << ans - 1 << endl;
return 0;
}
測試結果
input:
7 7
1 2 3 4 5 1 6 6
output;
4
最優分解問題
題目
演算法實現 最優分解問題
問題描述:
設n 是一個正整數。現在要求將n 分解為若干個互不相同的自然數的和,且使這些自然數的乘積最大。
程式設計任務:
對於給定的正整數n,程式設計計算最優分解方案。
資料輸入:
由檔案input.txt 提供輸入資料。檔案的第1 行是正整數n。
結果輸出:
程式執行結束時,將計算出的最大乘積輸出到檔案output.txt 中。
輸入檔案示例 輸出檔案示例
input.txt output.txt
10 30
演算法思路
若 \(n = i + j\) , 當 \(ij\) 很接近時,此時 \(i * j\) 最大,所以先將n分解為 \(a={2 + 3 + 4 + ... + k}\) 且 \(\sum a <= n\) ,對於多出來的一部分數,倒著為每一位加一,保證最後的乘積的最大。
實驗程式
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int a[maxn];
int n;
void solve()
{
for(int i = 1; i * i / 4 <= n; ++i)
a[i] = i;
int sum = 0;
int last;
for(int i = 2; i * i / 4 <= n; ++i)
{
sum += a[i];
if(sum >= n)
{
sum -= a[i];
sum = n - sum;
last = i - 1;
break;
}
}
while(sum)
{
++a[last];
--sum;
if(!sum)break;
for(int i = last; i >= 2; --i)
{
--sum;
++a[i];
if(!sum)break;
}
}
int ans = 1;
for(int i = 2; i <= last; ++i)ans *= a[i];
cout << ans << endl;
return;
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
while(~scanf("%d", &n))
solve();
return 0;
}
測試結果
intut:
10
output:
30
遞迴與分治策略
眾數問題
題目
演算法實現題 眾數問題
問題描述:
給定含有n 個元素的多重集合S,每個元素在S 中出現的次數稱為該元素的重數。多重集S 中重數最大的元素稱為眾數。
例如,S={1,2,2,2,3,5}。
多重集S 的眾數是2,其重數為3。
程式設計任務:
對於給定的由n 個自然陣列成的多重集S,程式設計計算S 的眾數及其重數。
資料輸入:
輸入資料由檔名為input.txt 的文字檔案提供。檔案的第1 行多重集S 中元素個數n;接下來的n 行中,每行有一個自然數。
結果輸出:
程式執行結束時,將計算結果輸出到檔案output.txt 中。輸出檔案有2 行,第1 行給出眾數,第2 行是重數。
輸入檔案示例 輸出檔案示例
input.txt output.txt
6 2
1 3
2
2
2
3
5
演算法思路
排序後二分查詢到每一個數的出現次數,記錄下來取最大值即可。
實驗程式
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5;
int a[maxn];
int ans, ansn;
int n;
void solve()
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i)scanf("%d", &a[i]);
int l, r;
l = r = 1;
int t;
sort(a + 1, a + 1 + n);
ans = ansn = 0;
for(int i = 1; i <= n; ++i)
{
l = lower_bound(a + 1, a + 1 + n, a[i]) - a - 1;
r = upper_bound(a + 1, a + 1 + n, a[i]) - a - 1;
t = r - l;
if(t >= ansn)
{
ans = a[i];
ansn = t;
i = r;
}
}
printf("%d\n%d\n", ans, ansn);
return;
}
int main()
{
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
solve();
return 0;
}
測試結果
input:
6
1
2
2
2
3
5
output:
2
3
整數因子分解問題
題目
演算法實現題 整數因子分解問題
問題描述:
大於1 的正整數n 可以分解為:n=x1*x2*…*xm。
例如,當n=12 時,共有8 種不同的分解式:
12=12;
12=6*2;
12=4*3;
12=3*4;
12=3*2*2;
12=2*6;
12=2*3*2;
12=2*2*3 。
程式設計任務:
對於給定的正整數n,程式設計計算n 共有多少種不同的分解式。
資料輸入:
由檔案input.txt 給出輸入資料。第一行有1 個正整數n (1≤n≤2000000000)。
結果輸出:
將計算出的不同的分解式數輸出到檔案output.txt 。
輸入檔案示例 輸出檔案示例
input.txt output.txt
12 8
演算法思路
暴力遞迴尋找出每一個可能分解出的因式,計數即可。(對於大於1e7的某些整數這樣的做法可能超過1s)
實驗程式
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll ans, n;
void solve(ll x)
{
if(x == 1)
++ans;
else
for(int i = 2; i <= x; ++i)
if(!(x % i))
solve(x / i);
}
int main()
{
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
while(cin >> n)
{
ans = 0;
solve(n);
cout << ans << endl;
}
return 0;
}
測試結果
intput:
12
100
233
123456
10000000
output:
8
26
1
2496
3112896