實驗一 集合的表示與操作算法設計
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