08.03
1、《王道機試》—— 動態規劃
搬宿舍
步驟:
(1)將所有物品重量遞增排序
(2)用二維數組dp[i][j]記錄在前j件物品中選擇i件的最小疲勞度。
j和之前的是否為一組。
(3)初始dp[0][j] = 0.說明這時還沒有開始選擇。
- 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時,兩堆的最大重量總和。
這是在三種情況中取最大值,表示當前狀態可以由哪一種轉移而來。三種現有的情況分別是:差值為[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件物品所能達到的最大值。
每個狀態有兩個來源:與前一個值相等,或前一個狀態新放入一個。
數組結構體賦值:&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] = {
..... //共六行,表示變換的六種情況
}
函數存儲結構如下
定義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
如果成功返回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;
}