編程學習筆記---8月

08.03

1、《王道機試》—— 動態規劃
搬宿舍
步驟:
(1)將所有物品重量遞增排序
(2)用二維數組dp[i][j]記錄在前j件物品中選擇i件的最小疲勞度。
j和之前的是否為一組。
(3)初始dp[0][j] = 0.說明這時還沒有開始選擇。


7C2FD763-2C17-4C58-8C26-5750745DB554.png
  • n的最大值為2000,k的最大值為1000,
  • 輸入可以用普通的scanf("%d,%d",&n,&k),也可
while(scanf("%d,%d",&n,&k)!=EOF){
...
}
  • 下標從1開始了
  • 對于數組list排序,只用包含頭文件 #include <algorithm>,并且使用語句sort(list+1,list+1+n)即可排好序了。
  • 將dp序列進行初始化
  • 根據邏輯關系遞推 求狀態
    外層循環:k次循環,i從1到k,表示選取2k件物品。
    內層循環是j從2k開始,但是j要小于n。

08.04

1、接上題
要搬的是2k件,i就從1開始,遍歷k之前的所有情況
j是從2i到n.
如果j>2*i,那么就說明前面的物體已經可以配成對了。為了使得疲勞度最小,還是搬輕的物品吧。
設定初始值,再更新。

2、放橘子
找出能夠得到的兩邊橘子的最大量
首先輸入t,是輸入的數據有t種情況。
對每種測試數據,先輸入n,是橘子的數量,再輸入每個橘子的重量
輸出每種測試用例的一邊橘子的最大數量
dp[i][j]表示 前i個柑橘被選擇后,第一堆比第二堆重j時,兩堆的最大重量總和。


11F7D81E-DC92-4BE2-B1FC-E6A8DFAFA0A1.png

這是在三種情況中取最大值,表示當前狀態可以由哪一種轉移而來。三種現有的情況分別是:差值為[j - list],差值為[j+list] ,或原本的差值是j,即不放入。 看這三種哪種情況下dp重量最大。

判斷時的限制條件為:

  • j(現有差值)+list[i] < 2000
  • dp[i - 1][j + list[i] + OFFSET] !=-INF)

計算三種情況各自的值,保存下來最大的那個在dp[i][j]中。
我們最后要的結果就是 dp[n][0] / 2的值。

3、背包問題( 0-1 ????、完全????和多重背包?? ??????????問題)

  • 0-1背包

08.05

1、接上,0-1背包
目的:使得總價值最大
最優解中:每個物體只有兩種情況,在背包中和不在背包中。
dp[i][j]表示:總體積不超過j的情況下,前i件物品所能達到的最大值。
每個狀態有兩個來源:與前一個值相等,或前一個狀態新放入一個。


image.png

數組結構體賦值:&list[i].w

外層循環對的是每個物體,內層循環從時間T開始往下減。
每行都只與上一行有關,與本行的次序無關。因此可以將原來的二維數組優化為一維數組。可以把i省略掉,每次保存本行的值為最后的結果。
狀態轉移更新時倒序進行,是為了使得在??定 dp[j]的值??時,dp[j - list[i].w]的????值未??修改。

2、0-1背包變形
要求最后恰好能夠裝滿背包
dp[i][j]的概念改變即可,表示前i件物品恰好總體積為j時的最大價值。初試值改變一下,其他內容不改變。

3、完全背包
每個物品的數量是可以無限增加的。
把無限的情況歸結為有限的情況。

但是使用單純的0-1背包問題的復雜度比較高。
遍歷時j是順序從小到大遍歷的。
0-1背包逆序是因為每個物品最多被選擇一次,這里順序是因為物品可以多次選擇。

例題:給出儲蓄罐中??的總重量和每種錢的重量,求出最少的錢數。
每次輸入E和F,求出錢重量
給出所用的硬幣的種類N
下面N行,分別給出每種硬幣的價值和重量。

先算出來錢的重量。s是當前儲蓄罐的重量,計算之后s為錢的數量。
如果dp[s]不為無窮,說明這種情況存在。如果為無窮,說明這種情況不存在。

4、多重背包
物品的數量為k
把k進行二進制拆分

08.06

1、dp例題---買大米
錢可以不正好用完。多重背包。
將數量k按照二進制拆分。同時重量k和價格p也會按照本組的數量進行更改。

2、PAT-TOP-Business
需要保證:
(1)規定時間之前完成 (2)獲利最大

dp[i][j]表示:以i工程結尾,并且在j時間之前完成了,這樣即可往前遞推。

首先將工程按照截止時間升序排列

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cmath>
#include<queue>
#include<cstring>
#include<vector>
#include<stack>
#include<map>
#define MAX 100005
#define INF 0x3f3f3f3f
typedef long long ll;
using namespace std;

int n,dp[MAX];

struct node{
    int val;
    int cost;
    int ddl;
    bool operator < (const node &a) const{
        return ddl<a.ddl;
    }
}t[MAX];  //定義每個工程的時間、ddl、時長等。


int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d%d%d",&t[i].val,&t[i].cost,&t[i].ddl);
    }
    sort(t,t+n);  //沒懂這里按照什么排序的。實驗結果是按照ddl從小到大排序了。
    int sum=t[n-1].ddl;//sum為最大的截止時間
    for(int i=0;i<n;i++){
        int tot=t[i].ddl;
        for(int j=tot;j>=t[i].cost;j--){
            dp[j]=max(dp[j],dp[j-t[i].cost]+t[i].val);
        }
    }
    int maxl=0;
    for(int i=0;i<=sum;i++){
        maxl=max(maxl,dp[i]);
    }
    printf("%d",maxl);
    return 0;
}

3、Universal Travel Sites
猜測是考察圖的知識。
《王道》圖論
用鄰接矩陣和鄰接表來表示。
鄰接鏈表

08.08

1、《王道——圖論》

08.09

1、鄰接鏈表的實現
使用STL中的std::vector

#include <vector>
using namespace std;

struct Edge{
  int nextNode;
  int cost;
}

vector<Edge> edge[N]; 

為每個節點建立單鏈表,即存儲每個節點相鄰的邊信息。
相應操作:

for( i = 0; i < N; i ++){
  edge[i].clear(); //清空
  
  //添加信息
  Edge tmp;
  tmp.nextNode = 3;
  tmp.cost = 38;
  edge[i].pushback(tmp);
}

查詢

  //查詢
for(int i = 0; i < edge[2].size(); i++ ){
  int nextNode = edge[2][i].nextNode;
  int cost = edge[2][i].cost;
}

當不用clear,即不全部清除,需要部分清除時:
edge[1].erase(edge[1].begin+i, dge[1].begin+i + 1);
這會刪除第i個

2、并查集
表示樹:雙親節點表示法,即每個節點需要記錄其父節點。可用數組表示。
合并:修改根節點。有可能會使得樹退化,樹高較長。解決方法:路徑壓縮。
代碼編寫,數組表示,記錄雙親

int Tree[N];

int fintRoot(int x){
  if(Tree[x] == -1)
    return x;
  else
    return findRoot[Tree[x]];
}

如果查找的過程需要路徑優化
else中后面加一句

else{
  int tmp = findRoot(Tree[x]);
  Tree[x] = tmp;
  return tmp;
}

例題:暢通道路
即所有的節點都能連接起來,根節點是相同的。即查找連通分量。
操作是:合并集合,看最后還剩多少集合。
如果多個用例輸入,需要while(scanf("%d",&n)!= EOF)
初始時,Tree[i] = -1;

3、例題3: 朋友關系,求人數最大的集合
令開一個數組,在樹的根節點記錄該集合包含的元素的個數。

4、最小生成樹

08.10

1、MST
概念:子圖連通且不存在回路。若帶權:權重最小。
Kruskal 算法
思想:選擇權重最小的邊,看他所連接的節點是否在一個集合中。如果頂點不在同一個集合中,就加入這條邊,即包含這條邊。

在結構體中重載符號的方法:

struct{
  ...
  bool operator < (const Edge & A) const{
    return cost < A.cost;
  }
}

練習:freckles
double型的輸入需要 scarf("%lf",&n);

08.11

1、例題
給出坐標,求最小生成樹
頭文件

#include <stdio.h>
#include <algorithm> //sort用
#include <math.h> //sqrt用
using namespace std;

定義一些初始值

#define N 101
int Tree[N];

函數findRoot

int findRoot(int x){
    if(Tree[x] == -1)
        return x;
    else{
        int tmp = findRoot(x);
        Tree[x] = tmp;
        return tmp;
    }
}

需要自己定義邊的結構體

struct Edge{
    int a,b; //包含兩個節點的編號
    double cost; //cost花費/代價
    bool operator < (const Edge & A) const{ //定義排序的操作,背住
        return cost < A.cost;
    }
}edge[6000];

由于題目給出的是點的信息,定義點的結構體

struct Point{
    double x,y;  //需要表示點的兩個坐標
    double getDistance(Point A){  //根據這個點,定義一個計算與另一個點距離的函數。
        double tmp;
        tmp = (x - A.x)*(x - A.x) + (y-A.y)*(y-A.y);
        return sqrt(tmp); //需要用到math.h
    }
}list[N]; //最多有N個點

main函數如下
首先輸入n,double類型的用lf,共有n個點,把它輸入到Point結構體中去

for(int i = 1;i<=n;i++){
            scanf("%lf,%lf",&list[i].x,&list[i].y);
        }

size保存邊數。

        for(i=1;i<=n;i++){
            for(j=i+1;j<=n;j++){
                edge[size].a = i;
                edge[size].b = j;
                edge[size].cost = list[i].getDistance(list[j]);
                size++;
            }
        }
sort(edge,edge+size);

先給每個可能存在的點都設定一個邊,再填入具體的數值。保存在結構體edge中。兩個頂點分別是i,j,邊的cost是用i中的getDistance函數計算。算完之后sort排序,Tree初始化。

ans用來記錄最終結果,遍歷每條邊,看結果是否應該包含這條邊。

 for (i=0;i<size;i++){
            int a = findRoot(edge[i].a);
            int b = findRoot(edge[i].b);
            if(a!=b){
                Tree[a] = b;
                ans+=edge[i].cost;
            }
        }

2、最短路徑問題
算法1:弗洛伊德算法
(1)鄰接矩陣保存原圖
(2)考慮從節點i到節點j只能經過節點1(或什么節點都不經過)。
如果由于加入節點1出現了最短路徑,則更新我們的記錄矩陣ans[k][i][j]。
(3)依次加入其他節點。

代碼實現:k,i,j循環

  • 先判斷在k-1的情況下i,j分別能否與k連通。若不能連通,就不改變
  • 如果(原來的路徑沒有連通)或者(加入k節點,可以更新最短路徑),則更新。否則,保持原來的值。

這樣就可以求的所有節點間的最短路徑。

由于我們最后只要最終的數組,也可以將原來的三位數組轉化為2維,直接表示[i][j]之間的距離。

3、弗洛伊德算法例題:最短路
由于是無向圖,矩陣初始值賦值操作要執行兩次。
3次循環
(1)加上k能否連通,不能連通則不改變,continue (2)如果可以連通,那么<1>原來不能連通 <2>值更新, 這兩種情況都會更新值

4、狄杰斯特拉算法
只能求的某個節點到其他所有節點的距離,1對n,不是n到n。
步驟如下:
(1)初始化1到所有節點的最短路徑長度都是無窮,1到1的距離為0
(2)K表示求出最短路徑的。
(3)求出下一個最短距離以及將相應的節點加入K
(4)重復上述操作,直到所有節點都加入了

代碼實現
頭文件

#include <stdio.h>
#include <vector>
using namespace std;

定義一個結構體,代表鏈表中的結構體
包含鄰接的節點以及相應的邊的權值。

08.12

1、
定義結構體,以及需要預先用來存儲的變量

struct E{
    int next;
    int c;
};

vector<E> edge[101];
bool mark[101];
int Dis[101];

main函數中,每條邊都輸入相關信息,存到edge中,用push_back

        while (m--) {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            E tmp;
            tmp.c = c;
            tmp.next = b;
            edge[a].push_back(tmp);
            
            tmp.next = a;
            edge[b].push_back(tmp);
            
        }

加入節點前的初始化操作

for(i=1;i<=n;i++){
            Dis[i] = -1;
            mark[i] = false;
        }
        Dis[1]=0;
        mark[1]=true;
        int newP = 1;

開始循環進行更新操作
外層循環表示還需要加入n-1個節點(已經加入了1)
內層表示的是要遍歷新加入節點相鄰的所有的邊(下一個節點)
先找到要比較的下一個節點t和所對應的邊c
這時候表示下一個節點可達了。如果之前沒距離,就加入距離,如果之前距離較大,就對距離進行更新。
全部的都更新完之后,來進行一次比較。找出所有重新可達的節點中距離最小的值。

for(i=1;i<n;i++){
            for(j=0;j<edge[newP].size();j++){
                int t = edge[newP][j].next;
                int c = edge[newP][j].c;
                
                if(mark[t] == true) continue;
                if(Dis[t] == -1 || Dis[t] > Dis[newP] + c)
                    Dis[t] = Dis[newP] + c;
            }
            int min = 123123123;
            for(j=1;j<=n;j++){
                if(mark[j] == true) continue;
                if (Dis[j] == -1) continue;
                if(Dis[j]<min){
                    min = Dis[j];
                    newP = j;
                }
            }
            mark[newP] = true;
        }
        printf("%d",Dis[n]);

2、最短路徑例題
但是每條邊有長度d,求最短距離,順帶記錄其花費。

最后輸出的是自己隨便定義的兩個節點的最小距離.但是和原來的是一樣的,只不過加入節點時S 是第一個加入的,不是1第一個加入了。

3、拓撲排序
針對有向無環圖DAG。
判斷是否為有向無環圖,可以用拓撲排序
算法
首先選擇入度為0的節點,并將其從圖中刪去
再選擇下一個入度為0的節點

例題:判斷是否為有向無環圖。使用隊列
當出現入度為0的節點時,將其放入隊列之中。

頭文件

#include <stdio.h>
#include <vector>
#include <queue>
using namespace std;

保存圖的信息都是用臨界鏈表,所以還需要用到vector。

vector<int> edge[501];
queue<int> Q;

main函數結構如下:

int inDegree[501];

n=0,m=0判斷是否成立;

初始化inDegree, edge.clear(),隊列Q也清空[while(Q.empty()==false )Q.pop()]

輸入邊的信息。edge[a].push_back(b) , inDegree[b]++

初始統計,將所有起始節點入隊列
for(i=0;i<n;i++){
    if(inDegree[i]==0) Q.push(i);
}

將隊列中現有的分別取出來,進行尋找下一個節點。

去除節點時,需要把入度減少

while(Q.empty()==false){
    int nowP = Q.front;
    Q.pop();
    cnt++;
    for(i=0;i<edge[nowP].size();i++){
        inDegree[edge[nowP][i]]--;
        if(inDegree[edge[nowP][i] ==0]){
            Q.push(edge[nowP][i]);
        }
    }
}

節點入隊列的時機為:(1)最開始(2)去除邊更新信息之后

4、PAT——Universal Travel Sites
考察的知識點是:搜索中的最大流問題

是第六章的內容,繼承第二章,故先看第二章內容

08.13

1、排序

  • 冒泡排序
    代碼實現

頭文件 stdio.h
main函數

用n保存數字個數,buf[i]分別存儲各個數字

排序主體,i = 0~n-1 , j = 0~n-1-i
比較j 和 j+1的大小,每次把較大的數放在后面

如果i = 1~n , j = i+1~n  
比較i和j的大小,每次把小的放在i的位置

排序完成,輸出
  • 冒泡排序
    輸入n個數,進行排序(2006華科)
#include <iostream>

using namespace std;

int main(){
    int n = 0,i = 0,j = 0, temp = 0;
    int sorting[100] = {0};
    cin >> n;
    for(i = 0;i<n;i++){
        cin >> sorting[i];  //輸入
    }
    
    for(i = 0;i < n; i++){
        for(j = i+1 ;j< n; j++){
            if(sorting[i] > sorting[j]){
                temp = sorting[i];
                sorting[i] = sorting[j];
                sorting[j] = temp;
            }
        }
    }
    
    for(i = 0;i<n;i++){
        cout << sorting[i] <<" ";
    }
    
    return 0;
}

上面這種方法想當于每次都把最小的放在前面。真正的應該是把相鄰的比較,然后每次都把現有的最大的放到最后面。如下所示。可以把n理解為已經拍好序的個數。

    for(i = 0;i < n; i++){
        for(j = 0 ;j< n-1-i; j++){
            if(sorting[j] > sorting[j+1]){
                temp = sorting[j];
                sorting[j] = sorting[j+1];
                sorting[j+1] = temp;
            }
        }
    }

補充:scanf返回成功賦值的變量的個數。

如果n較大,需要使用快速排序、歸并排序等

  • 快速排序
    若n較大,超過了時間復雜度,使用快速排序,C++中有直接的sort()函數,默認的是快速排序升序形式
#include <stdio.h>
#include <iostream>
#include <algorithm>

using namespace std;

int main(){
    int n,i;
    int buf[10000];
    while(scanf("%d",&n)!= EOF){
        //這句,需要保證輸入了n,如果沒輸入,則重新輸入
        for(i = 0;i<n;i++){
            cin >> buf[i];//輸入數據
        }
        
        sort(buf,buf+n); //排序函數。對于數組buf,輸入的是buf,buf+n
        
        for(i = 0;i<n;i++){
            cout << buf[i] << " ";
        }
    }
    return 0;
}
  • 降序排序
    1)按照上面升序排好后,倒著輸出
    2)
頭文件
#include <stdio.h>
#include <algorithm>
using namespace std;

定義函數cmp
bool cmp (int x,int y) { //定義排序規則
    return x > y;
}
int main () {
int n;
int buf[100];
while (scanf ("%d",&n) != EOF) {
    for (int i = 0;i < n;i ++) {
        scanf ("%d",&buf[i]);
}
sort (buf,buf + n,cmp); //使用該重載形式,我們表明將要使用自己定義的排列規則
    for (int i = 0;i < n;i ++) {
        printf("%d ",buf[i]);
}
    printf("\n");
}
return 0;
}

cmp函數自己定義排序規則 return的是x>y,所以最后降序排列了


  • 補充一個比較雜的內容(6.30)
    如果圖形顯示,在界面中會有光標,C語言中可以隱藏光標,代碼如下:
void HideCursor()//隱藏光標
{
    CONSOLE_CURSOR_INFO cursor_info = {1, 0};
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

int main(){
  HideCursor();
  return 0;
}

制作圖形界面,使用Visual studio + easyX


整體的結構

#include <stdio.h>
#include <algorithm>
using namespace std;
bool cmp() //自己定義排序規則

int main(){
    while //輸入
    sort(but,buf + n,cmp)
    for //輸出;

}

2、第二章的五——查找
最簡單的找數:數組中查找特定數字
代碼實現
頭文件
#include <stdio.h>
main函數如下

定義n和buf[],輸入相關數據

一次遍歷數組,看x與buf[i]是否相等

對于線性數組,可以用二分法查找
移動的時候新的查找集變為中中間節點前一個或后一個
查找失敗的標記是:起始點大于結束點。

代碼實現
頭文件

#include <stdio.h>
#include <algorithm> //sort排序
#include <string.h> //需要輸入字符串
using namespace std;

根據題目信息,定義結構體

struct Student{
char no[100]; //學號 
char name[100];
char sex[5];
int age;

bool operator < (const Student & A) const{
  return strcmp(no,A.no) < 0;  //sort排序,按照no從小到大
}
} buf[1000];

main函數如下

輸入學生的信息
scanf("%s%s%s%d",buf[i].no,buf[i].name,buf[i].sex,&buf[i].age);
注意這里只有age用了&符號
按照學號升序排序
sort(but,buf+n);
輸入要查詢的t組信息,進行t次循環,用while
用x來表示待查找序號
定義top和base,分別用來標示數組起始位置和結束位置的下標
循環的條件是top >= base
計算mid
tmp = strcmp(buf[mid].no, x);
tmp > = < 0,分3種情況,分別處理
若查找失敗,輸出失敗
若查找成功,printf("%s %s %s %d\n", buf[ans].no, buf[ans].name, buf[ans].sex, buf[ans].age );

時間復雜度: O(n*logn排序 + m*logn查找)

二分查找還可以用于定界
根據目標數字把數組的數值分為兩部分
最后,buf[top]就是這個目標數字本身

其實本質上還是二分查找

看完之后,看第六章

3、第六章——搜索
搜索方式:枚舉(需考慮時間復雜度)
代碼實現

#include <stdio.h>

x: 0-100
y:0-100-x
z: 100 - x - y

如果計算的時候有小數,最好都乘一個數??,變為整數

根據題目要求進行判斷

輸出

廣度優先搜索BFS
遠遠不止圖的遍歷應用

例題
三維空間
要首先把問題轉化一下,如這里需要考慮到時間,于是定義了四元組(x,y,z,t)

構造解答樹??:包含所有狀態。BFS表現為:層次遍歷。過程中需要剪枝
本題中是去掉了不是最短時間t的分枝。

在樹中每個坐標狀態出現一次即可,多了也沒用。

為了實現先進先出,需要用到隊列。/ 節點用結構體表示

用mark[x][y][z]記錄已經到達了的點的坐標。
代碼實現
頭文件

#include <stdio.h>
#include <queue>
using namespace std;

初始化信息

mark
maze
結構體N
queue<N> Q;  //其實模擬的是解答樹
變換數組坐標go
int go[][3] = {
..... //共六行,表示變換的六種情況
}

函數存儲結構如下

image.png

定義BFS函數

有的數據是a,b,c

當隊列Q中還有數據時,進行循環

N now = Q.front(); Q.pop();

依次擴展其6個相鄰節點
i=0~6
3種情況會丟次此坐標(continue)
<1>坐標在立方體之外,超過了范圍。
<2>maze = 1,即為墻
<3>mark = true,即此點已經出現過

得到的新的狀態tmp入隊列Q, Q.push(tmp);
標記坐標已經出現過,mark[nx][ny][nz] = true;

判斷新的到的狀態是否已經得到了最終結果,入股得到了,返回tmp.t

如果搜索完所有的空間還是沒有最終的結果,說明不存在這種方案,返回-1

main函數如下

輸入測試次數T,對于每次測試進行while循環

輸入a.b.c.t,即時間最多不超過t

maze, mark 進行初始化

如果Q不空,將Q清空
while(Q.empty() == false) Q.pop();

標記起點:mark[0][0][0]
N tmp; 初始化
將tmp放入隊列 Q.push(tmp);

進行廣度優先搜索rec = BFS(a,b,c)

根據rec的結果進行分類輸出。

4、例題:非常可樂
S = N + M,考慮能否平分呢?
如果能平分,輸出倒可樂的最少次數

還是用四元組來表示。
(x,y,z,t)分別 表示三個杯子中的重量 和 需要傾倒的次數

與上題不同的是:需要自己根據題意定義一個變換函數。
上題是mark和go()

void AtoB(int &a,int sa,int &b,int sb){ //如果需要全局改變值的,定義的是&a, &b,即可以理解為會把更新的值傳回去。只用來計數,不需要變值的,用sa,sb. 
  分為能完全倒完和不能完全倒完兩種情況
  如果可以完全倒完
  if(sb - b >= a)
    b +=a;
    a= 0;
  else
    倒sb-b
    a -= sb-b;
    b= sb;
}

再定義BFS

有的數據是s,m,n,即3個杯子的大小

用a,b,c臨時保存杯子中可樂的體積

AtoB(a,s,b,n),由A傾倒向B,出現了新的組合

如果該組合之前沒出現過,就標記MARK,并將其保存為N型的tmp
如果已經出現平分了(a == s/2 && b == s/2 及其他可能的情況 ),直接返回
如果還沒有平分,將這個新的節點入隊列 Q.push(tmp)

重復剛才的過程,由b倒向a, 

再重復,由a倒向c

c倒向a

b倒向c

c倒向b

main函數邏輯如下

輸入s\n\m
必須要求s為偶數

對mark進行初始化,為false

當Q不空時,Q.pop();

構造一個N型的tmp,并初始化,并存入Q中,Q.push(tmp);

修改mark[s][0][0] = true

int rec = BFS(s,n,m);

根據rec的值進行不同結果的輸出,

程序代碼很長,但是中間有很多重復的部分。

5、 遞歸調用

今天看太多啦,明天再看

08.14

1、遞歸的例子,漢諾塔3
對題目進行分析,不同的題目要求可能不同。這里分析出F(x) = 3*F(x-1) + 2,并且F(1) = 2
函數實現過程如下:
頭文件

#include <stdio.h>
#include <string.h>

定義遞歸函數

long long F(int num){
if(num == 1) return 2;
else
  return 3*F(num-1) + 2;
}

代碼十分簡單,但是計算機做的工作較多,所以N不能太大

遞歸的另一個例子
素數環:環中任意兩個相鄰數字的和為素數。
解決方法:可以采用回溯法去枚舉每一個值。如果放了之后發現后面的都不能再成立了,就改變上一個的值。
代碼實現
頭文件

#inclde <stdio.h>
#include <string.h>

定義一些元素ans , hash, n, prime數組

判斷一個數是否為素數,需要定義一個函數judge

bool judge(int x){
  for(i = 0; i < 13; i++){
    if(prime[i] == x) return true;
  }
  return false
}

check是進行最后的檢查

重點在DFS函數,這道題中有一點清奇,按照常理應該是先檢查是否正確再放入ans中,但是這個是先放入,下一次再檢查是否正確。

void DFS(int num){ num是剛剛放入的,但是不確定是否正確 
 if(num > 1) // 
  if(judge(ans[num] + ans[num - 1]) == false) return; 說明這個情況是不正確的,直接return,就不會到check輸出了
 if(num == n){ //n  
 check(); //  
 return; //, 
 } 
 for(int i = 2;i <= n;i ++){ 從小到大依次放入
 if(hash[i] == false){ //i 如果之前沒有被放入過,就可以放入
   hash[i] = true; //i 
   ans[num + 1] = i; // 嘗試把i放入num+1的位置
   DFS(num + 1); //  檢測是否成立
   hash[i] = false; // 只有剛才的不成立,或者成立并且已經輸出結束   的時候才會返回這里,再進行下一個。
 } 
 } }

遞歸的應用2:圖的遍歷
題目條件:把一矩形的區域創建為許多方形結構。
有油的區域叫做pocket, 相鄰的區域可以連起來。程序需要檢測出來有多少不相鄰的區域。

輸入m和n,表示每個grid的行和列,1-100之間,*表示沒有石油,而@表示此處有石油。對每個grid,輸出分離的油區共有多少。

解決思路:對每個@都要設置標記位

代碼實現:
頭文件
#include <stdio.h>

一些元素用于保存信息

char maze[101][101]; 存儲輸入的信息
bool mark[101][101]; 標記狀態
int n,m; 長寬
int go[][2] = {1,0,-1,0,0,1,0,-1,1,1,-1,1,-1,-1,-1,1};  定義八個方向

main函數如下:

判斷是否結束
if (m == 0 && n == 0) break;

輸入地圖信息(二維數組,字符串)
for(i=1;i<=n;i++){
    scanf("%s",maze[i]+1);
}

初始化所有mark為false

遍歷的思想為:按照行和列分別進行遍歷。
如果mark已經被標記過,continue
如果是*,也跳過
否則,DFS遍歷,并且ans++

DFS的過程如下:遍歷與(x,y)相鄰的所有的點,標記為false

for(i=0;i>8;i++){
    int nx = x + go[i][0];
    int ny = y + go[i][1];
    
    if(nx < 1 || nx > n || ny < 1 || ny > m) continue;
    if(maze[nx][ny] == '*') continue;
    if(mark[nx][ny] == true) continue;
    mark[nx][ny] = true;
    DFS(nx,ny);  // 最重要的是這一步:還要遍歷新找到的這個節點相鄰的點。
}

2、深度優先搜索DFS
不再具有最優特性了,只求解???或???問題。
把一枝上的遍歷完之后,才會到上一個節點,再遍歷其他的分枝。
應該可以使用棧結構 也可以使用遞歸操作來完成。
此處用遞歸。

例題
一個迷宮問題,每個門??會在特定的一秒內開一下

08.17

1、上面的例題
每個位置被走過之后,就不能再通過了。(題目上說會掉下去)
定義一個三元組(x,y,t),x,y表示坐標,t表示走到這個位置的時間t
過程中需要剪枝
每次移動都會讓坐標的奇偶性發生變化,首先可以判斷一個根據起始位置和終點位置和時間,減去的枝(起點和終點的奇偶性不同,說明需要奇數個時間,但是題目給的需要偶數個時間)

代碼實現過程如下
頭文件

#include <stdio.h>

定義需要用到的變量

char maze[8][8];  給出的矩陣的大小都不大于7
int n,m,t;
bool success;  用來標記是否成功了
int go[][2] = {
1,0,
-1,0,
0,1,
0,-1
};  定義要行走的四個方向

main函數如下

大的循環輸入
while(scanf("%d%d%d",&n,&m,&t)!=EOF){
...
}

while內部的東西如下
if(n==0 && m==0 && t==0) break;

輸入
for(int i = 1;i<=n,i++){
scanf("%s",maze[i]+1);
}
數組輸入時,不需要&符號,并且二維數組每次輸入一行
原來定義的大小是8,所以如果n = 7,下標是從1-8,并沒有占用0的位置,所以有maze[i]+1

初始化success, 終點sx和sy,起點,并判斷是否一定無解
success = false;
for...(maze[i][j] = 'D') sx = i,sy = j;
t時間會讓位置的奇偶性變化t次
if(maze[i][j] == 'S' &&  (i+j)%2 == ( (sx + sy)%2 + t%2 )%2 ){
  maze[i][j] = 'X';  起點標記為強,即走過的地方都標記為墻
  DFS(i,j,0);
}

每個測試用例都輸出結果
puts(success == true? "YES" : "NO");

DFS函數如下,有x,y,time

for(i=0;i<4;i++){ 每個位置可走的情況有四種,分別進行遍歷。并且需要用到的是go[][],所以下標從0開始
....
}

int nx = x + go[i][0];
int ny = y + go[i][1];

需要保證坐標在地圖內
if(nx < 1 || nx > n || ny < 1 || ny > m) continue;
if(maze[nx][ny] == 'X') continue;

對成功的情況進行處理,(如果時間恰好對,就是結果,如果時間不對,這條枝上的結果不可能再對了)
if(maze[nx][ny] == 'D'){
  if(time +1 == t){
    success = true;
    return;
  }
  else
    continue;
}

如果這個點沒有成功,就先標記不可再達,再遍歷這個點的下一個點。
maze[nx][ny] = 'X' ;
DFS(nx,ny,time+1);
----->如果調用的后續的節點都是不答案,那么就會返回這里,那么再把它改為普通位置,再次搜索時可以遍歷。
maze[nx][ny] = '.' ;

if(success) return;

深度優先搜索類似于先序遍歷

看完之后,看編程題。

2、Universal Travel Sites
題目要求:
第一行輸入 源地址、目的地址、整數N
再輸入N行,格式為:源 目的 capacity

自己還是不會做。參考答案,考察最大流問題。用基于BFS的Edmonds-Karp算法求解。
最大流基礎參考 https://blog.csdn.net/u014679804/article/details/47016513

代碼實現如下
頭文件

#include <cstdio>   輸入輸出
#include <cstdlib> C語言中對應stdlib.h,包含了一些常見的函數,如malloc\free等
#include <cstring> 字符串
#include <vector> 類似棧
#include <queue> 隊列
#include <algorithm> 一些算法如排序等
#include <climits>  用來定義各種數據類型的最值常量
using namespace std;

定義結構體
此處定義的是邊的結構體,以及一些操作函數

struct edge {
    char source[4], dest[4];
    int capacity, s, d;
    int dindex(void) {
        int index = 0;
        index = ((dest[0] - 'A') * 26 + dest[1] - 'A') * 26 + dest[2] - 'A';
        return index;
    }
    int sindex(void) {
        int index = 0;
        index = ((source[0] - 'A') * 26 + source[1] - 'A') * 26 + source[2] - 'A';
        return index;
    }
    void read(void) {
        scanf("%s %s %d", source, dest, capacity);
        return;
    }
};

看不懂

08.18

1、Universal Travel Sites
定義的邊的結構體
分別包含 變量,以及index
index是分別按照地址的三個字母順序排列的。
index = ((dest[0] - 'A')*26 + dest[1] - 'A' )*26 + dest[2] - 'A';
還定義了一個read函數,用于輸出

準備工作
靜態變量 const int MAX = 26*26*26;
BFS
Edmonds_Karp

main函數如下

初始變量

用到了setbuf函數
setvbuf(stdin, new char[1 << 20], _IOFBF, 1 << 20);
int setvbuf(FILE *stream, char *buffer, int mode, size_t size);  setbuf函數聲明
mode是 `IOFBF`,指定文件緩沖模式的

tmp內容輸入

定義arr,用來輸入信息
arr = new vector<edge>[MAX];
for(i = 0; i < n; i++){
  scanf("%s %s %d", tmp.source, tmp.dest, &tmp.capacity);
  tmp.s = s.index;
  tmp.d = d.index;
  arr[tmp.s].push_back(tmp);
}

調用 Edmonds_Karp 函數
這里返回的結果就是最大流,輸出

setbuf用來定義stream流應該如何緩沖
setvbuf(stdin, new char[1 << 20], _IOFBF, 1 << 20);
int setvbuf(FILE *stream, char *buffer, int mode, size_t size); setbuf函數聲明
new char[1<<20] 是分配給用戶的緩沖,1<<20是左移20位,是用來規定大小的。
mode是_IOFBF

image.png

如果成功返回0,否則返回非0

Edmonds_Karp函數,EK算法
思想就是:找增廣路。從源點開始做BFS。
代碼實現如下

vector<edge>::iterator vit;
::表示成員函數所屬的類
route[i] = -1,首先把route[i]初始化都置為-1

每次循環都進行BFS,返回從源地址到目的地址是否還能找到增廣路
while (BFS(arr, route, source, dest)) {
        f = INT_MAX;
        for (i = dest; i != source;) {
            j = route[i];
            for (vit = arr[j].begin(); vit != arr[j].end(); vit++) {
                if (vit->d == i) {
                    f = min(f, vit->capacity);
                    break;
                }
            }
            i = j;
        }  這里結束之后,找到的f為最小值,即增廣路的最大值。


        for (i = dest; i != source;) {
            j = route[i];
            for (vit = arr[j].begin(); vit != arr[j].end(); vit++) {
                if (vit->d == i) {
                    vit->capacity -= f;  把所有的容量減去f,相當于把現有的流量增加了f
                    break;
                }
            }
            i = j;
        }

        max += f;  最大值再加上f
        for (i = 0; i < MAX; i++) {
            route[i] = -1;  清除操作
        }
    }

delete[] route;  清除操作
return max;  返回

BFS代碼如下

    bool *visit = (bool *)calloc(MAX, sizeof(bool)); calloc一個 visit
    int i, j;
    queue<int> q;
    q.push(source);
    visit[source] = true;
    while (!q.empty()) {
        i = q.front();
        q.pop();
        if (i == dest) {
            break;
        }
        for (auto vit : arr[i]) {  C++ 里面的一種新的遍歷方式
            j = vit.d;
            if (!visit[j] && vit.capacity > 0) {  需要使得visit[j] == 0
                q.push(j);
                route[j] = i;
                visit[j] = true;  已經訪問過j
            }
        }
    }
    free(visit);
    return i == dest ? true : false;

void *calloc(size_t nitems, size_t size)
參考https://www.runoob.com/cprogramming/c-function-calloc.html

感悟:目前見過的最難的一道題,理解一下最大流的思想即可,自己也寫不出來。

2、To Buy or Not to Buy - Hard Version
題目大意:Eva想用最喜歡的顏色做一串珠子,商店只整片地賣。
給出想做的和商店有的,輸出能否做成,以及額外的/缺少的珠子個數。
不知道考察什么算法

先看簡單版本To Buy or Not to Buy
題目大意差不多,但是每個測試用例只有兩行,參考結果,人家用的暴力搜索

#include <iostream> 頭文件
using namespace std;

int main(){
    string s1,s2;
    cin>>s1>>s2;  由于輸入的只有兩行,直接輸入s1,s2即可。
    int a[123]={0},less=0,more=0;
    a[123]中的123代表的是:0-9 + a-z + A - Z , 的最大的ASCII碼,z的碼為122.
    for(int i=0;i<s1.size();i++)
        a[s1[i]]++;  將s1的字母在數組a中表示出來
    for(int i=0;i<s2.size();i++)
        a[s2[i]]--;  把s2的減去
    
    for(int i=1;i<123;i++)
        if(a[i]<0) less+=a[i];  如果有<0的,說明有不滿足情況的,少的,計數
        else more+=a[i]; 如果>的話,就記下來多的(每次都會記)
   
   if(less<0) cout<<"No "<<-less<<endl; 如果較小,輸出less的相反數。
    else cout<<"Yes "<<more<<endl;否則,輸出more
    return 0;
} 

暴力搜索,思想簡單

08.19

補充數據結構知識

1、To Buy or Not to Buy - Hard Version
與原來的區別是,項鏈種類的數目增多了,并且可以選擇多條。
不能簡單地用暴力搜索了,需要DFS+剪枝
參考別人代碼如下:一個想法:我還是不會做

08.20

數據結構知識
1、棧
頭文件 #include <stack>
stack<int> S
S.push(i)
例題:括號匹配
要求:分別找到不能匹配的左括號和右括號,并輸出。
知識點:棧應用
思想:左括號入棧,檢測到右括號時,棧頂的出棧。
代碼實現:

頭文件
#include <stdio.h>
#include <iostream> 這句可以不要
#include <stack>
#include <string.h>
using namespace std;

主函數
int main(){
  char s[101];
  int flag[101];  用flag做標記也行,也可以用char ans[110]直接來保存輸出的結果,待會兒直接輸出
  stack<int> S;  一般放在main函數外面
  int i = 0;

  輸入
  scanf("%s",s); 因為有多組數據,最好直接寫成while(scanf("%s",s) != EOF){
XXX
}
 
 循環檢測
  for(i=0; s[i]!= 0;i++){
    if(s[i] == '(')
      S.push(i);
如果使用ans的話,ans[i] = ' '; 先默認一個空格

    else if ( s[i] == ')'){
      if(S.empty() == true)
        flag[i] == -1; ---> ?
      else
        S.pop();  
加入ans[i] = ' ' ;
    }

else ans[i] = ' '; 普通的字符

左括號不匹配的做標記
while( !S.empty()){
  flag[S.top()] = 1;
  加ans[S.top()] == '$';
  S.pop();
}

輸出
puts(s);
for(i = 0; s[i] != 0; i++){
  if(flag[i] == 1)
    printf("?");
  else if(flag[i] == -1)
    printf("$");
  else
  printf(" ");
}
return 0;
}

收獲:

  • 判斷字符串數組中是否到了結尾,是用str[i] == 0是否成立來判斷的
  • 字符串ans賦值之后,為了使得能夠正確輸出,加一句ans[i] = 0;

棧的例題2:表達式求值
知識點:堆棧
思想:
需要設立兩個堆棧,一個保存數字,一個保存運算符
數學運算有優先級的,自己定義一個優先級矩陣,5*5,代表加減乘除,并且自己在表達式首位默認加入一個最低優先級。

收獲:

  • 運算優先級用一個矩陣來表示,并且還自己加了運算符0號。
  • 棧和隊列在使用前,都先自己清空一下

08.21

1、哈夫曼樹
中間節點的權值的和即為哈夫曼樹的帶權路徑和
求最小值:用堆,用優先隊列 priority_queue<int> Q來實現,默認堆頂是最大值。
小頂堆:priority_queue<int, vector<int>, greater<int> > Q
需要頭文件: #include <queue>

例題:構造哈夫曼樹,輸出所有節點的值與權值的乘積之和
限制:2=<n<=1000
代碼實現:
頭文件

#include <queue>
#include <stdio.h>
using namespace std;

小堆頂

priority_queue<int, vector<int>, greater<int>> Q;

main函數如下,對于每次輸入

while(Q.empty() == false) Q.pop();

for(i = 1;i<=n;i++){
  int x;
  scanf("%d",&x);
  Q.push(x);
}

int ans = 0;

下面是精華
while(Q.size()>1){
  int a = Q.top();
  Q.pop();
  int b = Q.top();
  Q.pop();
  ans += a+b; //非葉子節點的權值和
  Q.push(a+b);
}

printf("%d", ans);

2、二叉樹
遍歷:前序、中序、后序,可以使用遞歸來實現
節點的結構體

struct Node{
  Node *lchild;
  Node *rchild;
  XXX...
}

中序遍歷

void inOrder(Node *Tree){

遍歷左子樹
if(Tree -> lchild != NULL)
  inOrder(Tree -> lchild);
}

遍歷當前節點

if(Tree -> rchild != NULL){
  inOrder(Tree -> rchild);

return;
}

例題:給定前序和中序遍歷,求其后序遍歷
限制:節點數n<=26
知識點:根據前序中序還原二叉樹,并保存在內存中。

08.22

1、二叉樹構建
代碼實現:

#include <stdio.h>
#include <string.h>

struct Node{
Node *lchild;
Node *rchild;
char c;
}Tree[50];

char str1[30], str2[30];

main函數如下

int main(){
  while(scanf("%s",str1)!= EOF){
    scanf("%s",str2);
    loc = 0; 表示靜態數組中已經分配的節點個數
    int L1 = strlen(str1);
    int L2 = strlen(str2);
    Node *T = build(0,L1-1,0,L2-1);
    postOrder(T);
}
  return 0;
}

build函數如下

Node *build(int s1, int e1, int s2, int e2){
  Node *ret = creat();
  ret -> c = str1[s1];
  int rootIdx;
  
  查找根節點在中序中的位置
  for(int 1 = s2; i<= e2; i++){
    if(str2[i] == str1[s1]){
      rootIdx = i;
      break;
    }
  }

  如果左子樹不空,重建左子樹
  if(rootIdx != s2){
    ret -> lchild = build(s1+1,s1+ (rootIdx - s2), s2, rootIdx - 1 )

如果右子樹不空,重建右子樹
類似左

}

收獲:

  • 結構體中的內容,用 ->輸出,比如 T -> c;
  • loc表示的是:每組數據中,已經處理完的節點,所以在main中每次循環先初始化為0
  • 每次創建節點 ,定義的是create函數。

2、二叉排序樹
插入順序不同,結果可能不同
中序遍歷,是一個遞增序列

例題:建立二叉排序樹
要求:節點數n <= 100,重復節點忽略,不用輸出

插入函數

Node *Insert(Node *T, int x){
  
  情況一:T是空樹
  if(T == NULL){
    T = creat();
    T ->c = x;
    return T;
  }

插入左子樹
else if( x < T ->c ){
  T -> lchild = Insert( T->lchild, x );
}
else if (x > T->c){
  T -> rchild = Insert( T->rchild, x );
}

return T;
}

收獲:

  • postOrder 后序遍歷 / inOrder 中序遍歷 / preOrder 先序遍歷
  • 只要掌握了調用的邏輯,遞歸循環即可完成操作,我們所做的工作是很小的。

3、一個結論
中序遍歷再加上任何一個遍歷,就可以完全確定一個二叉樹。

例題:二叉搜索樹,判斷兩個序列是否為同一個樹的序列
限制:n<= 20,表示有n個需要判斷/ 序列的長度小于10
思路:給出的只是插入順序,其實還沒開始構建樹,我們的目的就是要構建樹。
對輸入序列構建二叉樹,構建兩顆,分別進行前序遍歷和中序遍歷,如果結果相同,說明樹相同。否則不同。

代碼實現
輸入tmp并轉為數字的方法

scanf("%s",tmp);
for(int i = 0; tmp[i] != 0; i++) {
  T = Insert(T,tmp[i] - '0');
}

在先序和中序遍歷的時候,已經把節點中的字符放入字符串str中了
str[*(size) ++ ] = T ->c + '0';
T中存放的純數字,放入字符串中需要加上數字0的ASCII

處理其他需要判別的輸入字符串

由于程序比較復雜,有多個字符串,多個標記變量。
char *str;用來指示當前正在保存的字符串

size的操作
int size1, size2;
int *size;

size1 = 0;
size = &size1;
遍歷保存時:str[*(size) ++ ] = T ->c + '0';,即size1 ++
str1[size1] = 0;

收獲:

  • 要根據題目進行變通。這里把前序遍歷和中序遍歷都保存在一個字符串中,字符串的長度設置為25,實際應用應該不超過20
  • tmp暫時用來存儲,可以存儲自己的基字符串,也可以存儲要比較的字符串。雖然它單個是數字,但是由于一串輸入了,仍然看作是字符串的形式。所以需要進行處理。
  • 比較兩個字符串是否相同,使用puts(strcmp(str1,str2)==0 ? "YES" : "NO" );

08.23

1、二叉樹的刪除操作
非特殊情況:將右子樹的最小的節點代替這個節點。
原理:中序遍歷不變。

08.24

1、上題 —— 買珠子
代碼不容易理解,參考另一個答案
main函數如下

str是輸入的,j單個遍歷每個字符,
如果這個字符在需要的字符串中出現過,

使用到了str.find()函數
str.find(ss) 返回字符串ss在str中的位置,需要的頭文件是iostream。
string::npos是個特殊值,說明查找沒有匹配

頭文件
用到的 cstdio, cstdlib, cstring,
和 algorithm, climits, cctype, set, map, vector

定義const N = 150

還是不會做,確實難了,先放棄。
繼續看《王道機試》

2、成績排序
要求:按照三個條件排序
限制:學生個數N <= 1000, 姓名的長度不超過100

需要自己根據題意來定義排序規則函數

bool cmp(E a, E b){  定義成bool型,如果a < b, 返回真
  if (a.score != b.score) return a.score < b.score;
  
  int tmp = strcmp(a.name, b,name);
  if tmp != 0, return tmp < 0;
  else return a.age < b.age;

}

main函數中,排序用 sort(buf, buf + n, cmp)

如果不定義cmp函數,我們可以定義<運算

bool operator < (const E &b ) const {
  if(score != b.score) return score < b.score;
  
  int tmp = strcmp(name, b.name);
  if(tmp != 0) return tmp < 0;
  else return age < b.age;
  
}

這樣在main函數中寫的時候,就不用再寫cmp了,只寫sort(buf,buf+n)即可。

有兩種,記重載運算符這種形式。

3、日期類問題
例如:兩個日期之間的天數
思想:預先寫好一個程序,可以處理所有日期與一個特定日期之間的差值,再將這兩個值相減即可。
2月需要做特殊處理,考慮平年閏年

#define ISYEAP(x) x % 100 != 0 && x % 4 == 0 || x % 400 == 0 ? 1 : 0 //

定義一個數組,預存每個月的天數

int dayOfMonth[13][2]{
    0,0,
    31,31,
    28,29,
    31,31,
    30,30,
    31,31,
    30,30,
    31,31,
    31,31,
    30,30,
    31,31,
    30,30
    31,31
};

為日期定義一個類,方便時間的推移

struct Data{
    int Day;
    int Month;
    int Year;
    
    void nextDay(){
        Day ++;
        if(Day > dayOfMonth[Month][ISYEAP(Year)]){ 若成立,需要換月
            Day = 1;
            Month++;
            if(Month > 12){ 如果成立,需要換年
                Month = 1;
                Year++;
            }
        }
    }
};

main函數將相差的日期結果都保存在數組buf中。
當要計算題目給的數據時,輸入為:

while(scanf("%4d%2d%2d",&y1,&m1,&d1) != EOF){
  XXX
}

收獲:

  • 這種思想很好,可以解決很多問題
  • 開辟大空間,如果在main函數中,可能會由于棧空間不足無法開辟這么大的空間,所以定義成全局變量。
    聯想:之前一個老師問的問題:全局變量和局部變量各自的優缺點:可以加一個點:如果要開辟的內存太大,局部變量無法滿足要求空間不夠,就需要開辟為全局變量(或者malloc動態分配)

日期類例題2: 給出一個日期 三個數,年月日,算出它星期幾
限制:年為1000-3000之間
思想:與上題相同,知道基數日期是星期幾,求出基數日期和給出日期之間的差的天數,將天數%7,計算出結果。
與上題的差別在輸出格式上。month的英文輸出,星期的英文輸出。

char monthName[13][20] = {
    "",
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December"
}

char weekName[7][20]{
    "Sunday",
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday",
    "Saterday"
    
};

main函數中對于每次輸入,通過strcmp函數,找到對應的月份,保存在m中。
日期差保存在days中。
(day % 7 + 7 ) % 7
第一個%7是為了運算方便,+7 是為了解決負數的情況(但是按理說沒有負數了)%7是為了最后的運算。

4、Hash的應用
hash:將存儲位置與數據本身對應起來。

08.25

1、Hash的應用
例題1: 統計同成績的學生人數
要求:輸出某個分數的學生的人數
限制:N <= 1000
思想:用hash來解決。

因為輸入的分數在0-100之間,共有101種情況,是有限的。在輸入時,就對這些情況分別計數。
代碼實現如下:
頭文件
#include <stdio.h>
main函數如下

int main(){
  int n;
  while(scanf("%d",&n) != EOF && n != 0){
    int Hash[101] = {0};

    輸入
    int i;
    for(i = 1;i<n;i++){
      int x;
      scanf("%d",&x);
      Hash[x]++;
  }

  輸出
  int x;
  scanf("%d",&x);
  printf("%d\n",Hash[x]);
  }
return 0;
}

2、例題2:排序
輸入格式,輸入n\m,共有n個數,找出其中前m大的數。
如果n和m較大,算法的復雜度較高,不能簡單的排序。
考察:哈希實現排序

代碼實現如下

#include <stdio.h>
#define OFFSET 500000
int Hash[1000001];

int main(){
    int n,m;
    while (scanf("%d%d",&n,&m)!= EOF) {
        for(int i= -500000; i<= 500000; i++){
            Hash[i + OFFSET] = 0;
        }
        for(int i = 1; i<= n; i++){
            int x;
            scanf("%d",&x);
            Hash[x + OFFSET] = 1;
        }
        
        for(int i = 500000; i>= -500000; i--){
            if(Hash[i+OFFSET] == 1){
                printf("%d",i);
                m--;
                
                if(m!=0) printf(" ");
                else{
                    printf("\n");
                    break;
                }
            }
        }
    }

    return 0;
}

收獲:

  • 由于Hash的位置相當于一境排序過的,我們只需要判斷出現過沒有即可。
  • 對于輸出結束,是每次都讓m --,當m到0的時候表示輸出結束了。這時候break就會跳出循環。
  • 有負數,所以使用OFFSET偏移。
  • 觀察輸出格式,何時有空格

3、數學問題
a%b的值的正負與a相同,與b無關。
當有可能出現負數時,對于原來求得的r, (r + b) %b
大數求模時,為了防止溢出,可以在內部進行%c

例題:位數拆解
特殊乘法
限制:兩個數小于1000000000
知識點:位數拆解

#include <stdio.h>

int main(){
    int a,b;  要輸入的兩個數保存在a和b中
    while(scanf("%d%d",&a,&b)!= EOF){
        int buf1[20], buf2[20], size1 = 0, size2 = 0;
        
        while(a != 0){
            buf1[size1++] = a%10;  先求模,再/10,把位數保存在buf1中。
            a = a/10;
        }
        
        while( b != 0){
            buf2[size2++] = b%10;  把位數保存在buf2中。低位在左邊,是靠0開始的。
            b/= 10;
        }
        
        int ans = 0;
        int i,j;
        for(i = 0; i<size1 ; i++){
            for(j = 0; j<size2; j++){
                ans+= buf1[i] * buf2[j];  題目給出的運算方法
            }
            
        }
        printf("%d\n",ans);
    }
    return 0;
}

08.26

1、進制轉換
都以10進制為過渡,所以每次都需要轉換兩次
例題:把輸入的十進制轉為m進制,m是輸入的。

#include <stdio.h>
int main(){
    long long a,b;
    int m;
    while(scanf("%d",&m)!= EOF){
        if (m == 0) break;
        scanf("%lld%lld",&a,&b);
        a = a+b;
        int ans[50], size = 0;
        do{
            ans[size++] = a%m;
            a/= m;
        } while(a != 0);  數值轉換為m進制,保存在ans[]中。
        for(int i = size - 1; i>= 0; i--){
            printf("%d",ans[i]);  將其輸出
        }
        printf("\n");
    }
    return 0;
}

收獲:

  • 題目給出的整數是不超過整形定義,所以定義的為long long型,scanf輸入時,為lld

例題2:進制轉換
要求:輸入為 a,n,b,表示輸入的n是a進制,但是我們想把其轉換為b進制。
限制:a,b均 <= 16

#include <stdio.h>
#include <string.h>

int main(){
    int a,b;
    char str[40];  保存輸入的字符串
    
    while(scanf("%d%s%s",&a,str,&b) != EOF){
        int tmp, length = strlen(str), c = 1;
        int i;
        for(i=length-1; i>= 0; i--){
            int x;
            if(str[i] >= '0' && str[i] <= '9'){
                x = str[i] - '0';
            }
            else if (str[i] >= 'a' && str[i] <= 'z'){
                x = str[i] - 'a' + 10;
            }
            else{
                x = str[i] - 'A' + 10;
            }
            tmp += x*c;
            c*= a;
        }
        tmp是得到的數的十進制
        
        char ans[40], size = 0; 
        do{
            int x = tmp % b;
            ans[size++] = (x<10)? x+'0':'x'- 10 + 'A';
            tmp /= b;
        }while(tmp);   將b進制的結果保存在ans[]中
        
        
        for(int i = size - 1; i>= 0; i--){
            printf("%c",ans[i]);
        }  輸出結果
        printf("\n");
    }
    return 0;
}

2、STL標準模版庫
string : 保存和處理字符串
一般來說,XXX.h 頭文件是C文件,XXX是C++ 。但是string比較特殊,
string.h : C標準庫,包含一些常見的字符串處理函數,如strcmp
<string>: 是c++ 的頭文件,其內包含了一個string類,string s1就是建立一個string類的對象

string對象中已經重載了 + 、+=、<=、等運算符。

輸出
使用C++風格:

string c = "cout";
cout << c << endl;

使用C語言風格:

string c = "cout";
printf("%s\b",c.c_str());

記住c.c_str()
并且\b是退格的意思

對每一個字符進行遍歷:

for(int i = 0; i<s.size(); i++){
    char c = s[i];
}

其中 erase函數,s.erase(10,8)會從10開始,

int pos = a.find(b,startPos);
會在a中從startPos開始找b字符串,如果能找到,就返回第一次出現的位置。如果沒找到,返回一個常數string::nops

例題:字符串的查找刪除
要求:在字符串中進行刪除,刪除短字符串。并且還要刪除空格。

08.27

1、上個例題

記錄一下自己的情緒,發泄一下。
在醫院兩天我是沒有任何怨言的。真正讓我崩潰的是:公交車上看到的消息/打電話時的冷嘲熱諷/樓梯間吸煙的老人/被氣到肚子發抖。
人們的悲喜并不相通。
雖然說過很多遍了,但是還是真正去做到:認真地感受生活,不要對任何人有太多期待,自己強大起來才是最可靠的。
加油刷題!小馬最棒!在我心里,我自己就是最棒的!

給字符串賦值如下:

char str[101];
gets(str);
string a = str;

將str中的大寫轉為小寫。

for(int i = 0; i < a.size();i++){
    a[i] = tolower(a[i]);
}

下面這個循環用來進行刪除操作

while(gets(str)){
    string b = str, c = b;  b用來刪除小寫的,但是c中保存的才是我們真正需要輸出的。
    

    for(int i = 0; i<b.size(); i++){
        b[i] = tolower(b[i]);
    }
    
    int t = b.find(a,0);
    while( t != string::npos){
        c.erase(t,a.size());
        b.erase(t,a.size());
        t = b.find(a.t);
    }
    
    t = c.find(' ',0);  空格就要從真正的c中去刪除了。
    while( t!= string::npos){
        c.erase(t,1);
        t = c.find(' ',0);
    }
    
    cout << c << endl;
}

收獲:

  • scanf 在讀入內容之后,不會清除后面的換行符。如果后面用一個gets,見到換行之后就會停止。就會導致輸入錯誤。解決方法是:使用getchar() 消除空格符。所以盡可能不使用gets()

  • 如果題目說了是不區分大小寫輸出的,那么我們可以都先把字符串轉為小寫。對于判斷大小寫,需要使用一些函數。那么需要包含的頭文件是:#include <ctype.h>

    image.png

2、最大公約數GCD
a和0的最大公約數是a
方法:遍歷(數字較大時復雜度較高);擴展的歐幾里得

  • 需要注意:非零數與零的最大公因數是這個非零數。

例題:

#include <stdio.h>

int gcd(int a, int b){
    if(b == 0) return a;
    else return gcd(b, a%b);
}

int main(){
    
    int a, b;
    while(scanf("%d%d",&a,&b) != EOF){
        printf("%d\n",gcd(a,b));
    }
    
    return 0;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372