NWPU-演算法設計理論作業
1.二分查詢
描述
給定一個單調遞增的整數序列,問某個整數是否在序列中。
輸入
第一行為一個整數n,表示序列中整數的個數;第二行為n(n不超過10000)個整數;第三行為一個整數m(m不超過50000),表示查詢的個數;接下來m行每行一個整數k。
輸出
每個查詢的輸出佔一行,如果k在序列中,輸出Yes,否則輸出No。
輸入樣例
5
1 3 4 7 11
3
3
6
9
輸出樣例
Yes
No
No
思路:
方法一:
按照題目要求進行二分查詢,由於a[]是單增數列,所以對於每一個輸入查詢x,我們先設定左邊界l=1,有邊界r=n,ans儲存可能的位置,進行二分查詢,每一次二分查詢時mid=(l+r)/2。如果a[mid]>=x,說明如果存在x,那麼x一定在左邊界(包括mid),那麼此時r=mid-1,ans=mid;如果a[mid]<x,則說明x一定在有邊界。二分查詢完之後,要驗證a[ans]是否等於x,因為二分只是由於它是有序序列進行二分,不斷查詢與x最接近的數字,但它不一定能夠是x。
這個方法時間複雜度為O(nlogn).
程式碼:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=50000+50;
int n,a[maxn],m;
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
cin>>m;
while(m--){
int x;
cin>>x;
int l=1,r=n,ans=0;
while(l<=r){
int mid=(l+r)/2;
if(a[mid]>=x){
r=mid-1;
ans=mid;
}
else{
l=mid+1;
}
}
if(a[ans]==x){
cout<<"Yes"<<endl;
}
else{
cout<<"No"<<endl;
}
}
return 0;
}
方法二:
題目要求很明確,就是問x是否在a[]中存在,那就把a[]中出現過的數字進行標記就可以了,這裡對 a[]中的每一個數字的範圍解釋不夠清楚,所以我們可以用map進行對映處理。當然其實也可以vis[]陣列進行標記,vis[]大小設定為5e5,這樣讓vis[a[i]]=1,然後查詢vis[x]是否等於1就可以了,或者用map標記方法也一樣。這種方法的時間複雜度O(n)(直接陣列標記)或者O(nlogn)(map)。
程式碼:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
const int maxn=50000+50;
map<int,int>mp;
int n,a[maxn],m;
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],mp[a[i]]=1;
cin>>m;
while(m--){
int x;
cin>>x;
if(mp[x]==1){
cout<<"Yes"<<endl;
}
else{
cout<<"No"<<endl;d
}
}
}
2.求解投骰子游戲問題
問題描述:玩家根據骰子的點數決定走的步數,即骰子點數為1時可以走一步,點數為2時可以走兩步,點數為n時可以走n步。求玩家走到第n步(n≤骰子最大點數且投骰子方法唯一)時總共有多少種投骰子的方法。
輸入描述:輸入包括一個整數n(1≤n≤6)。
輸出描述:輸出一個整數,表示投骰子的方法數。
輸入樣例:6
輸出樣例:32
思路:
方法一:
看到這個題的第一個思路是用遞推/動態規劃。很容易看出來對於狀態i(當前走到第i步)可以由狀態0,1,…,i-1得到(即走i步,走i-1步,…,走1步),設dp[0]=1(即處於原始位置的方式只有一種),那麼dp[i]=dp[i-1]+dp[i-2]+…+dp[0].所以n^ 2列舉即可,當然為了時候的優化,我們還可以進行字首和優化(即每一次得到dp[i-1]都在pre之中,對於dp[i+1]我們就可以O(1)求得),當然這裡n<=6,就不必這麼複雜了。所以寫了一個時間複雜度為O(n^2)的程式碼。
程式碼:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=20;
int n,dp[maxn];
int main(){
cin>>n;
dp[0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=i-1;j++){
dp[i]+=dp[j];
}
}
cout<<dp[n]<<endl;
return 0;
}
執行結果:
方法二:
老師出這個題應該是為了考察dfs,所以這樣寫一下dfs的方法:
對於x,與方法一的解釋一樣,它肯定是由0,1,…,x-1得到的,所以我們再dfs求走到第0,1,…,x-1步的方法,dfs的邊界設定為 if(x==0)return 1;這樣也是可以求得結果的,只是時間複雜度是指數級別。但是如果我們進行記憶化搜尋,即用dp[]進行記錄每一個狀態的答案,一旦存在就不再dfs,這樣的時間複雜度可以優化到O(n^2).
程式碼:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n;
int dfs(int x){
if(x==0)return 1;
int res=0;
for(int i=0;i<=x-1;i++)res+=dfs(i);
return res;
}
int main(){
cin>>n;
cout<<dfs(n)<<endl;
return 0;
}
3.0-1揹包
描述
需對容量為c 的揹包進行裝載。從n 個物品中選取裝入揹包的物品,每件物品i 的重量為wi ,價值為pi 。對於可行的揹包裝載,揹包中物品的總重量不能超過揹包的容量,最佳裝載是指所裝入的物品價值最高。
輸入
多個測例,每個測例的輸入佔三行。第一行兩個整數:n(n<=10)和c,第二行n個整數分別是w1到wn,第三行n個整數分別是p1到pn。n 和 c 都等於零標誌輸入結束。
輸出
每個測例的輸出佔一行,輸出一個整數,即最佳裝載的總價值。
輸入樣例
1 2
1
1
2 3
2 2
3 4
0 0
輸出樣例
1
4
思路:
方法一:
0-1揹包的裸題,那就可以直接寫一個01揹包的動態轉移方程:dp[j]=max(dp[j],dp[j-w[i]]+p[i])。dp[j]的意思是:當揹包已裝j的重量的物品時的最大價值。那麼它可以由揹包已裝j-w[i]時最大的價值進行轉移,即由dp[j-w[i]]+p[i]得到。注意每一次要將dp[]設定為0,因為揹包此時無價值。當狀態方程列舉結束後,我們再從 dp[]陣列中找一遍,求得答案maxx=max{dp[i]}(i from 0 to c),輸出答案maxx。這種動態規劃的方法的時間複雜度為O(n^2).
ps:0-1揹包也可以寫成二維dp[][],只是這樣寫成滾動陣列可以更加節省空間。
程式碼:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn= 2000+50;
int n,c,w[maxn],dp[maxn],p[maxn];
int main(){
int i,j;
while(1){
scanf("%d%d",&n,&c);
if(n==0&&c==0)break;
for(i=1;i<=n;i++)cin>>w[i];
for(i=1;i<=n;i++)cin>>p[i];
memset(dp,0,sizeof(dp));
for(i=1;i<=n;i++){
for(j=c;j>=1;j--){
if(j-w[i]>=0&&dp[j]<dp[j-w[i]]+p[i]){
dp[j]=dp[j-w[i]]+p[i];
}
}
}
int maxx=0;
for(i=0;i<=c;i++)
if(maxx<dp[i])
maxx=dp[i];
cout<<maxx<<endl;
}
return 0;
}
方法二:
除了直接寫0-1揹包的動態轉移方程,還可以直接寫dfs,每一個揹包無非就是取和不取兩個狀態,如果要取則要求揹包容量 res>=w[now]。分別用ans1,ans2表示取當前物品,不取當前物品的最大價值,dfs返回max(ans1,ans2),dfs的終止條件是now ==n+1。時間複雜度(2^n)。
ps:方法二相較於方法一思維上更加簡單,容易想到,但是程式碼就相對麻煩,並且時間複雜度不夠優秀,當然如果加上記憶化搜尋後時間複雜度和動態規劃是相當的。我個人更喜歡方法一。
程式碼:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn= 2000+50;
int n,c,w[maxn],p[maxn];
int dfs(int now,int res){
if(now==n+1)return 0;
int ans1=0,ans2=0;
if(res>=w[now]){
ans1=dfs(now+1,res-w[now])+p[now];
}
ans2=dfs(now+1,res);
if(ans1>=ans2)return ans1;
return ans2;
}
int main(){
int i,j;
while(1){
scanf("%d%d",&n,&c);
if(n==0&&c==0)break;
for(i=1;i<=n;i++)cin>>w[i];
for(i=1;i<=n;i++)cin>>p[i];
cout<<dfs(1,c)<<endl;
}
return 0;
}
4.求解組合問題
編寫一個實驗程式,採用回溯法輸出自然數1~n中任取r個數的所有組合。
思路:
用回溯法求組合,n個數字選r個,每一個數字有兩種選擇,要麼選,要麼不選。dfs(int now)表示當前的決策數字是now,則dfs的邊界條件是now=n+1。對於數字now,可以選擇,也可以不選擇。選擇的話a[++a[0]]=now,a[0]記錄當前選擇的數的數量,回溯的時候記得a[0]–,再進行不選擇now的dfs搜尋。當now==n+1的時候表明當前已經選擇完了,如果a[0]==r,則當前的選擇組合剛好是滿足條件的,輸出即可。時間複雜度O(2^n)
程式碼:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100+50;
int n,r,a[maxn];
void dfs(int now){
if(now==n+1){
if(a[0]==r){
for(int i=1;i<=a[0];i++)cout<<a[i]<<' ';cout<<endl;
}
return ;
}
a[++a[0]]=now;
dfs(now+1);
a[0]--;
dfs(now+1);
}
int main(){
cin>>n>>r;
dfs(1);
return 0;
}
5.最小重量機
設某一機器由n個部件組成,每一種部件都可以從m個不同的供應商處購得。設 wij 是從供應商j處購得的部件 i 的重量, cij 是相應的價格。試設計一個演算法,給出總價格不超過 c 的最小重量機器設計。
輸入:第一行3個整數n,m,cost,接下來n行輸入wij (每行m個整數),最後n行輸入cij (每行m個整數),這裡n>=1, m<=100.
輸出:第一行包括n個整數,表示每個對應的供應商編號,第2行為對應的重量。
輸入樣例:
337
123
321
232
123
542
212
輸出樣例:
131
4
思路:
這裡用dfs進行搜尋,每找到一個符合條件的情況,就更新bestw和x[]。bestw儲存最小的重量,x[i]表示部件i所對應的供應商,nowx[i]表示dfs過程中部件i選擇的供應商。w[i][j] 表示從供應商j處購得的部件i的重量 ,c[i][j]表示相應的價格。dfs(int now,int noww,int nowp)表示搜尋第now個部件時的當前重量為noww,耗費nowp,對於第now個物品它有m個供應商可以選擇,當nowp+c[now][i]<=cost時則符合條件可以進行dfs(now+1,noww+w[now][i],nowp+c[now][i]);dfs的邊界條件時now==n+1,此時如果bestw>noww,則要更新bestw,以及x[]。最後輸出bestw和x[].
ps:如果PE的話,把最後的空格去掉(輸出x[]的時候,x[n]後面不要跟空格),並且加上printf("\n").
程式碼:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=200+50;
int n,m,cost,w[maxn][maxn],c[maxn][maxn],bestw=0x3f3f3f3f,nowx[maxn],x[maxn];
void dfs(int now,int noww,int nowp){
int i,j;
if(now==n+1){
if(bestw>noww){
bestw=noww;
for(i=1;i<=n;i++)x[i]=nowx[i];
}
return ;
}
for(i=1;i<=m;i++){
if(nowp+c[now][i]<=cost){
nowx[now]=i;
dfs(now+1,noww+w[now][i],nowp+c[now][i]);
}
}
}
int main(){
cin>>n>>m>>cost;
int i,j;
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
cin>>w[i][j];
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
cin>>c[i][j];
dfs(1,0,0);
for(i=1;i<=n;i++){
cout<<x[i];
if(i!=n)cout<<' ';
}
cout<<endl;
cout<<bestw<<endl;
return 0;
}
6.最長公共子序列
描述
一個給定序列的子序列是在該序列中刪去若干元素後得到的序列。確切地說,若給定序列X=<x1, x2,…, xm>,則另一序列Z=<z1, z2,…, zk>是X的子序列是指存在一個嚴格遞增的下標序列 <i1, i2,…, ik>,使得對於所有j=1,2,…,k有:
Xij = Zj
如果一個序列S即是A的子序列又是B的子序列,則稱S是A、B的公共子序列。
求A、B所有公共子序列中最長的序列的長度。
輸入
輸入共兩行,每行一個由字母和數字組成的字串,代表序列A、B。A、B的長度不超過200個字元。
輸出
一個整數,表示最長各個子序列的長度。
格式:printf("%d\n");
輸入樣例
programming
contest
輸出樣例
2
思路:
像LCS這樣的問題,它具有重疊子問題的性質,因此:用遞迴來求解就太不划算了。因為採用遞迴,它重複地求解了子問題。採用動態規劃時,並不需要去一 一 計算那些重疊了的子問題 。對於**dp[i][j]**表示 (s1,s2…si) 和 (t1,t2…tj) 的最長公共子序列的長度 。
當i== 0|| j== 0 時,dp[i][j]=0;
當i,j>0,si==tj時, dp[i][j]=dp[i-1][j-1]+1;
當i,j>0,si!=tj時,dp[i][j]=max{dp[i][j-1],dp[i-1][j]};
時間複雜度:
由於只需要填一個m行n列的二維陣列,其中m代表第一個字串長度,n代表第二個字串長度
所以時間複雜度為O(m*n)
程式碼:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000+50;
char s[maxn],t[maxn];int lens,lent,dp[maxn][maxn];
int max(int x,int y){
if(x>y)return x;
return y;
}
int main(){
scanf("%s%s",s+1,t+1);
lens=strlen(s+1);
lent=strlen(t+1);
for(int i=1;i<=lens;i++){
for(int j=1;j<=lent;j++){
if(s[i]==t[j]){
dp[i][j]=dp[i-1][j-1]+1;
}
else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
printf("%d\n",dp[lens][lent]);
return 0;
}
ps:這個NOJ就離譜,我寫成maxn=1e3+50,執行時錯誤,刷屏了一頁,最後才發現編譯器無法識別1e3,這編譯器是有多垃圾???
7.活動安排問題
問題:有n個活動的集合A={1,2,…,n},其中每個活動都要求使用同一資源,如演講會場等,而在同一時間內只有一個活動能使用這一資源。
求解:安排儘量多項活動在該場地進行,即求A的最大相容子集。
設待安排的11個活動的開始時間和結束時間按結束時間的升序排列如下:
將此表資料作為實現該演算法的測試資料。
i 1 2 3 4 5 6 7 8 9 10 11
s[i] 1 3 0 5 3 5 6 8 8 2 12
f[i] 4 5 6 7 8 9 10 11 12 13 14
思路:
方法一:
題意很明顯希望參加的活動數目儘量多。對於活動安排問題可以採取動態規劃的策略:
首先將活動的結束時間按照第一關鍵字排序(由小到大),再將活動的開始時間作為第二關鍵字排序(由小到大)
定義dp[i]表示在前i場比賽中最多可以參加幾場比賽,
由此得出方程:dp[i]=max(dp[i-1],dp[temp]+1);
temp指從dp[i-1]向前找到的第一個允許參加第i場活動的活動編號,由它推匯出dp[i]=dp[temp]+1;
由於每次迴圈時都向前找一次temp會浪費太多時間,又因為活動開始或結束時間是單調遞增的,
故可以令temp在迴圈時逐步遞增,這樣時間複雜度就降到了O(n).
這裡只是講解一下動態規劃的想法。就不寫程式碼了,動態規劃的程式碼和下面的貪心方法相似。只是這種動態規劃的思路是基於貪心的思想來實現的。
方法二:
這個問題可以抽象為在一個數軸上有n條線段,現要選取其中k條線段使得這k條線段兩兩沒有重合部分,問最大的k為多少。
最左邊的線段放什麼最好?
顯然放右端點最靠左的線段最好,從左向右放,右端點越小妨礙越少。
其他線段放置按右端點排序,貪心放置線段,即能放就放。
以上兩種方法的時間複雜度都是O(nlogn),快速排序的時間複雜度是O(nlogn),而動態規劃或者貪心執行更新策略的時間複雜度是O(n).
程式碼:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=2e3+50;
int n;
struct Node{
int s,f;
}a[maxn];
bool cmp(Node x,Node y){
if(x.f==y.f)return x.s<y.s;
return x.f<y.f;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i].s>>a[i].f;
sort(a+1,a+1+n,cmp);
int ans=1,now=1,opt=2;
while(opt<=n){
if(a[opt].s>=a[now].f){
ans++;
now=opt;
}
opt++;
}
cout<<ans<<endl;
return 0;
}
相關文章
- 演算法分析與設計 - 作業1演算法
- 演算法分析與設計 - 作業2演算法
- 演算法分析與設計 - 作業3演算法
- 演算法分析與設計 - 作業5演算法
- 演算法分析與設計 - 作業6演算法
- 計算機作業系統|作業系統引論計算機作業系統
- 自學程式設計方法論:怎麼學作業系統?程式設計作業系統
- 設計模式總結(理論篇)設計模式
- State設計模式上篇(理論篇)設計模式
- 分散式設計理論之CAP分散式
- 計算機導論作業系統計算機作業系統
- 演算法設計與分析---論序演算法
- 3D遊戲的照明設計理論(二):正式的照明設計方法,用燈光作為深度3D遊戲
- Spring理論基礎-面向切面程式設計Spring程式設計
- 演算法·理論:Manacher 筆記演算法筆記
- 演算法·理論:KMP 筆記演算法KMP筆記
- 初識“六邊形”架構設計理論架構
- 多執行緒程式設計相關理論執行緒程式設計
- 正交分析法設計理論及實踐
- 程式設計作業——系統管理程式設計
- 作業系統課程設計——處理機和程式排程演算法及記憶體分配回收機制作業系統演算法記憶體
- 計算理論導論筆記筆記
- 電腦科學和Python程式設計導論(一) 計算機相關理論Python程式設計計算機
- 遊戲設計與理論化研究(2)——黑箱方法論的運用遊戲設計
- 作業系統2—作業系統概論(下)作業系統
- 作業系統1—作業系統概論(上)作業系統
- 基於TRIZ理論的筷子儲存盒設計
- 基於TRIZ理論設計新型立體車庫
- 決策樹演算法-理論篇演算法
- Python 第一週程式設計作業Python程式設計
- UI設計培訓之如何將設計理論與實踐相結合UI
- LOTO示波器動作程式設計功能(命令批處理)程式設計
- 作業系統導論(1)作業系統
- 響應式程式設計與MVVM架構—理論篇程式設計MVVM架構
- 服務設計入門篇--理論與方法(摘抄滴滴)
- Java分散式系統設計:CAP定理與BASE理論Java分散式
- 基於TRIZ理論優化設計杏核開口機優化
- TRIZ理論在洗碗機設計中應用探討