【算法】算法筆記

算法專題

1、樹和圖

1. 二叉樹構造和遍歷

給出二叉樹的後序(postorder)遍歷和中序(inorder)遍歷,創建二叉樹,並獲得樹的層次遍歷(level traversal)html

  • preorder:根 - 左子樹 - 右子樹
  • inorder:左子樹 - 根 - 右子樹 (即爲深度優先)
  • postorder:左子樹 - 右子樹 - 根
  • level traversal:層次遍歷,即廣度優先
#include<bits/stdc++.h> 
using namespace std;

//由樹的中序和後序=>層析遍歷 
typedef struct BiTree{
    int num;
    BiTree *left;
    BiTree *right;
}BT;
int post[10010];
int in[10010];
BT *T;
//創建二叉樹
BT* CreateBiTree(int postl,int postr,int inl,int inr){
    //建立二叉樹
    
    //遞歸邊界,重要! 
    if(postl > postr){
        return NULL;
    }
    //printf("--------------------\n");
    //尋找根節點 
    int rooti;
    for(int i = inl;i<=inr;i++){
        if(in[i] == post[postr]){
            rooti = i;
            break;
        }
    }
    BT *node = (BT*)malloc(sizeof(BiTree));
    node->num = in[rooti];
    
    node->left = CreateBiTree(postl,postl+(rooti-inl)-1,inl,rooti-1);
    node->right = CreateBiTree(postl+(rooti-inl),postr-1,rooti+1,inr); 
    
    return node;
}

//層析遍歷,藉助queue
void bfs(BT* root) {
    queue<BT*> q;
    q.push(root);
    while(!q.empty()){
        BT *temp = q.front();
        q.pop();
        printf("%d ",temp->num);
        if(temp->left) q.push(temp->left);
        if(temp->right) q.push(temp->right);
    }
}

int main(){
    int n;
    cin>>n;
    //輸入後序 
    for(int i = 0;i<n;i++){
        cin>>post[i];
    }
    //輸入中序 
    for(int i = 0;i<n;i++){
        cin>>in[i];
    }

    T = CreateBiTree(0,n-1,0,n-1);
    bfs(T);
    
    return 0;
}

2. 朋友圈 - 並查集

#include<bits/stdc++.h> 
using namespace std;
int parent[10010];
void init(int n){
    for(int i = 0;i<=n;i++)
        parent[i] = -1;
}
//尋找祖先+路徑壓縮 
int find(int x){
    int s;
    for(s = x;parent[s] > 0; s = parent[s]);
    while(s!=x){
        int tmp = parent[x];
        parent[x] = s;
        x = tmp;
    }
    return s;
}
void Union(int a,int b){
    int fa = find(a),fb = find(b);
    int tmp = parent[fa] + parent[fb];
    if(parent[fa] > parent[fb]){
        parent[fa] = fb;
        parent[fb] = tmp;
    }else{
        parent[fa] = tmp;
        parent[fb] = fa;
    }
}

int main(){
    int n,m;
    cin>>n>>m; 
    int na,a[10010];
    init(n);
    for(int i = 0;i<m;i++){
        cin>>na;
        for(int j = 0;j<na;j++){
            cin>>a[j];
        }
        for(int j = 1;j<na;j++){
            if(find(a[j])!=find(a[j-1])){
                Union(a[j],a[j-1]);
            }
        }
    }
    int mins = 100100;
    for(int i =1;i<=n;i++){
        if(parent[i]<0){
            if(parent[i] < mins){
                mins = parent[i];
            }
        }
    }
    cout<<-mins<<endl;
}

3. 公共朋友 - 非朋友圈

poj4109
描述:小明和小紅去參加party。會場中總共有n我的,這些人中有的是朋友關係,有的則相互不認識。朋友關係是相互的,即若是A是B的朋友,那麼B也是A的朋友。小明和小紅想知道其中某兩我的有多少個公共的朋友。
輸入:第一行爲一個正整數c,表明測試數據的個數。接下來是c組測試數據。 對於每組測試數據,第一行是三個數字n(2<=n<=100),m和k,分別表示會場中的人數,已知的朋友關係數目,問題的數目。接下來的m行,每行用兩個數字i和j(1<=i,j<=n)表示了一個朋友關係,表示第i我的和第j我的是朋友關係。接下來的k行,每行用兩個數字i和j(1<=i,j<=n)表示一個問題,請問第i我的和第j我的有多少公共的朋友。
輸出: 對於第i組測試數據,首先輸出一行」Case i:」,接下來得k行表明了k個問題,每行輸出第i我的和第j我的有多少公共的朋友。

利用set去重:node

#include<bits/stdc++.h>
using namespace std;
vector<int> g[104];

int main(){
    int Case;
    cin>>Case;
    for(int cases = 1;cases<=Case;cases++){
        
        for(int i = 0;i<100;i++){
            g[i].clear();
        }
        
        int n,m,k;
        cin>>n>>m>>k;
        int a,b;
        for(int i = 0;i<m;i++){
            cin>>a>>b;
            g[a].push_back(b);
            g[b].push_back(a);
        }
        cout<<"Case "<<cases<<":"<<endl;
        for(int i = 0;i<k;i++){
            cin>>a>>b;
            set<int> s;
            int n1 = g[a].size();
            int n2 = g[b].size();
            for(int i = 0;i<n1;i++){
                s.insert(g[a][i]);
            }
            for(int i = 0;i<n2;i++){
                s.insert(g[b][i]);
            }
            int n3 = s.size();
            cout<<n1+n2-n3<<endl;
        }
    }

    return 0;
}

4. 哈夫曼樹

哈夫曼樹的定義

定義:哈夫曼樹即爲最小二叉樹。所謂最優指的是樹的帶權路徑長度最小,即:二叉樹的樹枝賦權值,從根節點到全部葉子節點的路徑上權值之和最小,這樣的樹爲哈夫曼樹。相應的應用是哈夫曼編碼ios

構造哈夫曼樹 - 優先隊列實現

題目: CSU - 1588
在一個果園裏,多多已經將全部的果子打了下來,並且按果子的不一樣種類分紅了不一樣的堆。多多決定把全部的果子合成一堆。  每一次合併,多多能夠把兩堆果子合併到一塊兒,消耗的體力等於兩堆果子的重量之和。能夠看出,全部的果子通過n-1次合併以後,就只剩下一堆了。多多在合併果子時總共消耗的體力等於每次合併所耗體力之和。  由於還要花大力氣把這些果子搬回家,因此多多在合併果子時要儘量地節省體力。假定每一個果子重量都爲1,而且已知果子的種類數和每種果子的數目,你的任務是設計出合併的次序方案,使多多耗費的體力最少,並輸出這個最小的體力耗費值。  例若有3種果子,數目依次爲1,2,9。能夠先將一、2堆合併,新堆數目爲3,耗費體力爲3。接着,將新堆與原先的第三堆合併,又獲得新的堆,數目爲12,耗費體力爲12。因此多多總共耗費體力=3+12=15。能夠證實15爲最小的體力耗費值。
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
//優先隊列實現哈夫曼樹
int n;
priority_queue<ll,vector<ll>,greater<ll> >  que;
int main(){
    cin>>n;
    int x;
    for(int i = 0;i<n;i++){
        cin>>x;
        que.push(x);
    }
    int ans = 0; 
    while(que.size()>1){
        ll a1 = que.top();
        que.pop();
        ll a2 = que.top();
        que.pop();
        ans+=a1+a2;
        que.push(a1+a2);
    }
    cout<<ans<<endl;
    return 0;
}

5. 其餘二叉樹性質相關計算

  • poj 2788 (√)c++

  • poj 2756 (√)算法

6. 圖的連通份量

尋找一個圖中知足條件的全部連通份量spring

九度1446:Head of a Gang
描述:One way that the police finds the head of a gang is to check people's phone calls. If there is a phone call between A and B, we say that A and B is related. The weight of a relation is defined to be the total time length of all the phone calls made between the two persons. A "Gang" is a cluster of more than 2 persons who are related to each other with total relation weight being greater than a given threthold K. In each gang, the one with maximum total weight is the head. Now given a list of phone calls, you are supposed to find the gangs and the heads.
輸入:第一行兩個整數,電話數n和閾值k。接下來n行,(A B c),每行一個電話,表示A打給B,且通話時間爲c。注意,名字A和B是由三個大寫字母組成的。
輸出:第一行表示團伙數n,接下來n行每行爲一個團伙,輸出爲該團伙的頭目名字和團伙成員數。

樣例:編程

輸入:數組

8 59
AAA BBB 10
BBB AAA 20
AAA CCC 40
DDD EEE 5
EEE DDD 70
FFF GGG 30
GGG HHH 20
HHH FFF 10

輸出:安全

2
AAA 3
GGG 3

題目分析

  • 找圖中全部的連通子圖。注意題目描述不清楚,實際上,只要連通就算一個團伙,而不是必定要子圖爲徹底圖。
  • 用並查集,能夠找到全部連通塊。
  • 判斷連通塊的通話時間總數是否知足閾值,須要找到該連通塊全部的節點編號,故同時使用鄰接矩陣存儲,用dfs找到全部節點並保存。
  • 找頭目。找鏈接圖中權值最大的點,故同時用數組a[MAX]記錄每一個點的權值之和,在輸入時就記錄。
  • 用優先隊列(pair)存儲全部找到的頭目結點編號以及該團伙人數,最後按序輸出便可。
  • 注意輸入的名字是三個大寫字母,故將其hash成整數,最後輸出再轉回字符數組。

解決

#include<bits/stdc++.h>
using namespace std;

int parent[18005];
int node[18005];
int n,k;
vector<int > g[18000];
vector<int> que;
int vis[18000];
typedef pair<int,int > P;
priority_queue<P,vector<P>,greater<P> > res;
void init(){
    for(int i = 0;i<18000;i++){
        parent[i]=-1;
        node[i] = 0;
    } 
}

int hashName(const char name[]){
    return (name[0]-'A')*26*26 + (name[1] - 'A') *26 +(name[2] -'A');
}
void intToName(int x,char name[4]){
    char a = x%26+'A';
    char b = x%(26*26)/26 +'A';
    char c = x/26/26 + 'A';
    name[0] = a;name[1] = b;name[2] = c;name[3]='\0';
}

int find(int x){
    int s;
    for(s = x;parent[s]>0;s=parent[s]);
    while(s!=x){
        int tmp = parent[x];
        parent[x] = s;
        x = tmp;
    }
    return s;
}
void Union(int a,int b){
    int fa = find(a);
    int fb = find(b);
    int tmp = parent[fa] + parent[fb];
    if(parent[fa] > parent[fb]){
        parent[fb] = tmp;
        parent[fa] = fb;
    }else{
        parent[fb] = fa;
        parent[fa] = tmp;
    }
}

void dfs(int x){
    que.push_back(x);
    vis[x] = 1;
    for(int i = 0;i<g[x].size();i++){
        if(!vis[g[x][i]]){//未訪問 
            dfs(g[x][i]);
        }
    }
}

int main(){
    cin>>n>>k;
    char name1[4],name2[4];
    int cost;
    init();
    for(int i = 0;i<n;i++){
        //scanf("%s%s%d",name1,name2,cost);
        cin>>name1>>name2>>cost;
        int na1 = hashName(name1);
        int na2 = hashName(name2);
        if(find(na1)!=find(na2)){
            Union(na1,na2);
        }
        node[na1]+=cost;
        node[na2]+=cost;
        g[na1].push_back(na2);
        g[na2].push_back(na1);
    }
    for(int i = 0;i<=17575;i++){
        if(parent[i] <=-3){//知足一個羣 
            //判斷總通話是否大於閾值 
            ////dfs尋找這個羣中全部的節點標號
            que.clear();
            memset(vis,0,sizeof(vis));
            dfs(i);
            ////全部節點保存在que中了
            ////計算總權值
            int totalV = 0;
            int maxx = -1;
            int maxi = 0;
            for(int i = 0;i<que.size();i++){
                totalV+= node[que[i]];
                if(node[que[i]]>maxx){
                    maxx = node[que[i]]; 
                    maxi = que[i];
                }
            }
            totalV/=2;
            if(totalV > k ){ //知足
                //最大點是maxi 
                char namemax[4];
                res.push(P(maxi,que.size()));
            }
        }
    }
    
    cout<<res.size()<<endl;
    char nameres[4];
    while(!res.empty()){
        intToName(res.top().first,nameres);
        cout<<nameres<<" "<<res.top().second<<endl;
        res.pop();
    }
    
    return 0;
}

7. 最小生成樹

災後重建

Pear市一共有N(<=50000)個居民點,居民點之間有M(<=200000)條雙向道路相連。這些居民點兩兩之間均可以經過雙向道路到達。這種狀況一直持續到最近,一次嚴重的地震毀壞了所有M條道路。
震後,Pear打算修復其中一些道路,修理第i條道路須要Pi的時間。不過,Pear並不打算讓所有的點連通,而是選擇一些標號特殊的點讓他們連通。
Pear有Q(<=50000)次詢問,每次詢問,他會選擇全部編號在[l,r]之間,而且 編號 mod K  = C 的點,修理一些路使得它們連通。因爲全部道路的修理能夠同時開工,因此完成修理的時間取決於花費時間最長的一條路,即涉及到的道路中Pi的最大值。

你能幫助Pear計算出每次詢問時須要花費的最少時間麼?這裏詢問是獨立的,也就是上一個詢問裏的修理計劃並無付諸行動。

【輸入格式】
第一行三個正整數N、M、Q,含義如題面所述。
接下來M行,每行三個正整數Xi、Yi、Pi,表示一條鏈接Xi和Yi的雙向道路,修復須要Pi的時間。可能有自環,可能有重邊。1<=Pi<=1000000。

接下來Q行,每行四個正整數Li、Ri、Ki、Ci,表示此次詢問的點是[Li,Ri]區間中全部編號Mod Ki=Ci的點。保證參與詢問的點至少有兩個。

【輸出格式】
輸出Q行,每行一個正整數表示對應詢問的答案。

【樣例輸入】
7 10 4
1 3 10
2 6 9
4 1 5
3 7 4
3 6 9
1 5 8
2 7 4
3 2 10
1 7 6
7 6 9
1 7 1 0
1 7 3 1
2 5 1 0
3 7 2 1

【樣例輸出】
9
6
8
8

【數據範圍】
對於20%的數據,N,M,Q<=30
對於40%的數據,N,M,Q<=2000
對於100%的數據,N<=50000,M<=2*10^5,Q<=50000. Pi<=10^6. Li,Ri,Ki均在[1,N]範圍內,Ci在[0,對應詢問的Ki)範圍內。




資源約定:
峯值內存消耗 < 256M
CPU消耗  < 5000ms

解決

#include<bits/stdc++.h>
using namespace std;

struct edge{
    int from,to;
    int cost;
    edge(){}
    edge(int f,int t,int c):from(f),to(t),cost(c){}
};
int n,m,q;
edge edges[200005];
int parent[50010];
int node[50006]; //要聯通的點臨時保存 
int node_num;

//重寫cmp, qsort使用 
int cmp(const void *a, const void *b){
    edge e1 = *(edge*)a;
    edge e2 = *(edge*)b;
    if(e1.cost > e2.cost) return 1;
    else if(e1.cost < e2.cost) return -1;
    else{
        return 0;
    }
}
//並查集init
void init(int n){
    for(int i = 0;i<=n;i++){
        parent[i] = -1;
    }
}
//尋找祖先,路徑壓縮 
int find(int x){
    int s;
    for(s = x;parent[s]>=0;s=parent[s]);
    while(s!=x){
        int tmp = parent[x];
        parent[x] = s;
        x = tmp;
    }
    return s;
}
//合併 
void Union(int a,int b){
    int fa = find(a);
    int fb = find(b);
    int tmp = parent[fa] + parent[fb];
    if(parent[fa] > parent[fb]){
        parent[fa] = fb;
        parent[fb] = tmp;
    }else{
        parent[fb] = fa;
        parent[fa] = tmp;
    }
}
//判斷是否結束 
int judge(int a[50010],int cnt){
    int parents = find(a[0]);
    for(int i = 1;i<cnt;i++){
        if(find(a[i]) != parents){
            return 0;
        }
    }
    return 1;
}
//克魯斯卡爾 
int Kruskul(){
    init(n);
    qsort(edges,m,sizeof(edges[0]),cmp);
    //n個點,共n-1條邊就能夠 
    int mincost = -1;
    for(int i = 0;i<n-1;i++){
        edge et = edges[i];
        int from = et.from;
        int to = et.to;
        if(find(from) != find(to)){
            Union(from,to); //合併
            if(et.cost > mincost){
                mincost = et.cost;
            }
            //判斷是否結束
            if(judge(node,node_num)){
                break;
            }
        }
    }
    return mincost;
}

int main(){
    cin>>n>>m>>q;
    for(int i = 0;i<m;i++){
        int x,y,p;
        cin>>x>>y>>p;
        edge e = edge(x,y,p);
        edges[i] = e;
    }
    for(int i = 0;i<q;i++) {
        int l,r,k,c;
        cin>>l>>r>>k>>c;
        node_num = 0;
        for(int j = l;j<=r;j++) {
            if(j % k ==  c){
                node[node_num++] = j;
            }
        }
        int ans = Kruskul();
        cout<<ans<<endl;
    }
    return 0;
}

8. 單源最短路徑 - dijkstra

A traveler's map gives the distances between cities along the highways, together with the cost of each highway. Now you are supposed to write a program to help a traveler to decide the shortest path between his/her starting city and the destination. If such a shortest path is not unique, you are supposed to output the one with the minimum cost, which is guaranteed to be unique.

Input Specification:

Each input file contains one test case. Each case starts with a line containing 4 positive integers N, M, S, and D, where N (≤500) is the number of cities (and hence the cities are numbered from 0 to N−1); M is the number of highways; S and D are the starting and the destination cities, respectively. Then M lines follow, each provides the information of a highway, in the format:

City1 City2 Distance Cost
where the numbers are all integers no more than 500, and are separated by a space.

Output Specification:

For each test case, print in one line the cities along the shortest path from the starting point to the destination, followed by the total distance and the total cost of the path. The numbers must be separated by a space and there must be no extra space at the end of output.

Sample Input:

4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20

Sample Output:

0 2 3 3 40

分析

  • 單源最短路徑問題。
  • 注意這裏兩個度量,即距離最短,相同最短距離的路徑取代價最小。
  • 記錄最短路徑。

解決

#include<bits/stdc++.h>
#define MAX 10010
#define INF 1001
using namespace std;

struct edge{
    
    int from;
    int to;
    int dis;
    int cost;
    edge(){}
    edge(int t,int d,int c):to(t),dis(d),cost(c){}
    
};
vector<edge> g[MAX];
typedef pair<int,int> P;
int D[MAX];
int C[MAX];
int n,m;
int pre[MAX];
void Dijkstra(int begin,int end){
    fill(D,D+MAX,INF);
    for(int i = 0;i<n;i++){
        pre[i] = -1;
        C[i] = 0;
    }
    D[begin] = 0;
    priority_queue<P,vector<P>,greater<P> > que;
    que.push(P(D[begin],begin));
    while(!que.empty()){
        P p = que.top();
        que.pop();
        int v = p.second;
        if(D[v] < p.first) continue;
        for(int i = 0;i<g[v].size();i++){
            edge e = g[v][i];
            int to = e.to;
            int dis = e.dis;
            if(D[v] + dis < D[e.to]){
                pre[to] = v;
                D[e.to] = D[v] + dis;
                que.push(P(D[to],to));
                C[to] = (C[v] + e.cost);
            }else if(D[v]+dis == D[e.to]){
                if(C[v]+e.cost < C[to]){
                    pre[to] = v;
                    D[e.to] = D[v] + dis;
                    que.push(P(D[to],to));
                    C[to] = (C[v] + e.cost);                
                }
            }
        }
    }
}

int main(){
    int a,b,c,d;    
    int begin,end;
    
    cin>>n>>m;
    cin>>begin>>end;
    
    for(int i = 0;i<m;i++){
        cin>>a>>b>>c>>d;
        g[a].push_back(edge(b,c,d));
        g[b].push_back(edge(a,c,d));
    }

    Dijkstra(begin,end);
    stack<int> st;
    st.push(end);
    int now = end;
    while(pre[now]>= 0){
        now = pre[now];
        st.push(now);
    }
    while(!st.empty()){
        cout<<st.top()<<" "; 
        st.pop();
    }
    cout<<D[end]<<" "<<C[end]<<endl;
    
    return 0;
}

2、枚舉搜索

1. 按鈕開關問題

若局部被肯定則總體狀態被肯定

枚舉第一個按鈕的狀態(這裏第一個按鈕做爲局部,一旦肯定就可推出所有按鈕狀態)

問題1:特殊密碼鎖

## 特殊密碼鎖
*描述*
有一種特殊的二進制密碼鎖,由n個相連的按鈕組成(n<30),按鈕有凹/凸兩種狀態,用手按按鈕會改變其狀態。
然而讓人頭疼的是,當你按一個按鈕時,跟它相鄰的兩個按鈕狀態也會反轉。固然,若是你按的是最左或者最右邊的按鈕,該按鈕只會影響到跟它相鄰的一個按鈕。
當前密碼鎖狀態已知,須要解決的問題是,你至少須要按多少次按鈕,才能將密碼鎖轉變爲所指望的目標狀態。

*輸入*
兩行,給出兩個由0、1組成的等長字符串,表示當前/目標密碼鎖狀態,其中0表明凹,1表明凸。

*輸出*
至少須要進行的按按鈕操做次數,若是沒法實現轉變,則輸出impossible。

*樣例輸入*
011
000
*樣例輸出*
1

分析

  • 枚舉法:每一個按鈕有2種狀態,可是最多可能有30個燈,所以狀態有2^30之多,窮舉必定會超時。

  • 點1:一個燈若是按了第二下,就會抵消上一次按下所產生的影響。所以,一個燈只有按或者不按兩種狀況,不存在一個燈要開關屢次的狀況。

    例如八個燈 00000000

    按1後 11000000
    按3後 10110000
    按1後 01110000
    這和八個燈 00000000
    只按一次3後 01110000
    是徹底相同的狀況

  • 點2 :咱們只須要考慮是否按下第一個燈。由於若是第一個燈的狀態被肯定了,那麼是否按下第二個燈也就決定了(若是第一個燈與指望不一樣,則按下,若是指望相同,則不按下)同理,第三個燈是否按下也惟一肯定。因此,本題只要分兩種狀況:燈1被按下和沒有被按下。以後使用for循環判斷別的燈是否須要按下便可,當循環結束,若如今的燈況與答案相同(只須要斷定最後一個燈是否相同),則輸出兩種方案中按鍵次數最少的,若不一樣,則impossible!

解決

#include<bits/stdc++.h> 
using namespace std;

char s[100],re[100],s_temp[100];
int minp = 1001;
int n; 
void push1(int i){
    for(int j = max(0,i-1);j<min(n,i+2);j++){
        if(s[j] == '0') 
            s[j] = '1';
        else
            s[j] = '0';
    }
}
void push2(int i){
    for(int j = max(0,i-1);j<min(n,i+2);j++){
        if(s_temp[j] == '0') 
            s_temp[j] = '1';
        else
            s_temp[j] = '0';
    }
}

int main(){
    scanf("%s",s);
    scanf("%s",re);
    int len = strlen(s);
    n = len;
    strcpy(s_temp,s);
    //printf("%s %s\n",re,reT);
    //第一個按鈕按下 
    int cnt = 0;
    push1(0);
    cnt++;
    for(int i = 1;i<len;i++){ //依次判斷其他燈是否須要被按下 
        if(s[i-1] != re[i-1]){ //上一個相同,而上一個也按下了而致使不一樣,則這個也得按下
            push1(i);
            cnt++;
        }
    }
    //判斷是否成功了
    int flag = 1;
    if(s[len-1] == re[len-1]){
        minp = min(cnt,minp);   
    }

    //第一個按鈕不按下 
    cnt = 0;
    for(int i = 1;i<len;i++){ //依次判斷其他燈是否須要被按下 
        if(s_temp[i-1] != re[i-1]){ //上一個不一樣,則這個也得按下
            push2(i);
            cnt++;
        }
    }
    //判斷是否成功了
    if(s_temp[len-1] == re[len-1]){
        minp = min(minp,cnt);
    }

    if(minp >=1001){
        cout<<"impossible"<<endl;
    }else{
        cout<<minp<<endl;
    }

    return 0;
}
/*
 *  ┏┓   ┏┓ 
 *┏┛┻━━━┛┻┓ 
 *┃       ┃   
 *┃   ━   ┃ 
 *┃ ┳┛ ┗┳ ┃ 
 *┃       ┃ 
 *┃   ┻   ┃ 
 *┃       ┃ 
 *┗━┓   ┏━┛ 
 *  ┃   ┃神獸
 *  ┃   ┃鎮bug
 *  ┃   ┗━━━┓ 
 *  ┃       ┣┓ 
 *  ┃       ┏┛ 
 *  ┗┓┓┏━┳┓┏┛ 
 *   ┃┫┫ ┃┫┫ 
 *   ┗┻┛ ┗┻┛  
 *    
 */

問題2:熄燈問題

即二維的特殊密碼鎖,這裏一個局部是第一行的操做狀態,經過枚舉第一行的操做狀態獲得全部燈的操做。

*描述
有一個由按鈕組成的矩陣,其中每行有6個按鈕,共5行。每一個按鈕的位置上有一盞燈。當按下一個按鈕後,該按鈕以及周圍位置(上邊、下邊、左邊、右邊)的燈都會改變一次。即,若是燈原來是點亮的,就會被熄滅;若是燈原來是熄滅的,則會被點亮。在矩陣角上的按鈕改變3盞燈的狀態;在矩陣邊上的按鈕改變4盞燈的狀態;其餘的按鈕改變5盞燈的狀態。
須要按下哪些按鈕,剛好使得全部的燈都熄滅。

*Input
5行組成,每一行包括6個數字(0或1)。相鄰兩個數字之間用單個空格隔開。0表示燈的初始狀態是熄滅的,1表示燈的初始狀態是點亮的。

*Output
5行組成,每一行包括6個數字(0或1)。相鄰兩個數字之間用單個空格隔開。其中的1表示須要把對應的按鈕按下,0則表示不須要按對應的按鈕。

樣例輸入

0 1 1 0 1 0
1 0 0 1 1 1
0 0 1 0 0 1
1 0 0 1 0 1
0 1 1 1 0 0

樣例輸出

1 0 1 0 0 1
1 1 0 1 0 1
0 0 1 0 1 1
1 0 0 1 0 0
0 1 0 0 0 0

分析

  • 二維的特殊鎖問題
  • 枚舉第一行(或第一列)全部狀態,以此肯定之後各行狀態,最後判斷是否符合題意

解決

//此段代碼沒有經過,僅僅經過了樣例,還沒有找出bug
#include<bits/stdc++.h>
using namespace std;


int a[5][6];
int a_temp[5][6];
int re[5][6];
int result[5][6];
void push(int x,int y){
    for(int i = max(0,y-1);i<=min(5,y+1);i++){
        a[x][i] = !a[x][i];
    }
    for(int i = max(0,x-1);i<=min(4,x+1);i++){
        a[i][y] = !a[i][y];
    }
    a[x][y] = !a[x][y];//注意上面(x,y)兩次取反則沒變化 
}

int main(){
    for(int i = 0;i<5;i++){
        for(int j = 0;j<6;j++){
            cin>>a[i][j];
            re[i][j] = 0;
        }
    }
    //memcpy(a_temp,a,sizeof(a));
    for(int l1= 0;l1<5;l1++){
        for(int l2 = 0;l2<6;l2++){
            a_temp[l1][l2] = a[l1][l2];
        }
    }
    push(2,2);
    
    int sucess = 0;
    for(int i = 0;i<63;i++){
        //按下第一行
        //每次都初始化將a變回開始值 
        //memcpy(a,a_temp,sizeof(a_temp));
        for(int l1= 0;l1<5;l1++){
            for(int l2 = 0;l2<6;l2++){
                a[l1][l2] = a_temp[l1][l2];
            }
        }
        //初始化結果result 
        for(int j = 0;j<5;j++){
            for(int k = 0;k<6;k++){
                result[j][k] = 0;
            }
        }
        //第一行按燈 
        if(i&32){
            push(0,0);
            result[0][0] = 1;
        }           
        if(i&16){
            push(0,1);
            result[0][1] = 1;   
        }       
        if(i&8){
            push(0,2);      
            result[0][2] = 1;
        }
        if(i&4){
            push(0,3);  
            result[0][3] = 1;
        }
        if(i&2){
            push(0,4);      
            result[0][4] = 1;
        }
        if(i&1){
            push(0,5);  
            result[0][5] = 1;
        }           

        //每行 
        for(int j = 1;j<5;j++){ //每行            
            for(int k = 0; k < 6; k++){
                if(a[j-1][k]!=0){
                    push(j,k);
                    result[j][k] = 1;
                }
            }
        }
        
        //判斷是否成功
        int flags = 1;
        for(int l2 = 0;l2<6;l2++)   {
            if(a[4][l2]!=0) {
                flags = 0;
                break;
            }
        }
        if(flags){
            sucess = 1;
            break;
        }   
    }
    if(sucess){
        for(int i = 0;i<5;i++){
            for(int j = 0;j<5;j++){
                cout<<result[i][j]<<" ";
            }
            cout<<result[i][5]<<endl;
        }
    }
    return 0;
}
//此段代碼AC,@北大郭煒老師
#include<memory>
#include<string>
#include<cstring>
#include<iostream>
using namespace std;
int GetBit(char c,int i)//取c的第i位
{  return ( c >> i ) & 1;  }
void SetBit(char & c,int i, int v)//設置c的第i位設爲v 
{
    if( v )  c |= ( 1 << i);
    else   c &= ~( 1 << i);
}
void Flip(char & c, int i)//將c的第i位取反 
{  c ^= ( 1 << i);  }
void OutputResult(int t,char result[]) //輸出結果
{
    //cout << "PUZZLE #" << t << endl;
    for( int i = 0;i < 5; ++i )
    {
        for( int j = 0; j < 6; ++j )
        {
            cout << GetBit(result[i],j);
            if( j < 5 ) cout << " ";
        }
        cout << endl;
    }
}
int main()
{
    char oriLights[5]; //最初燈矩陣,一個比特表示一盞燈
    char lights[5]; //不停變化的燈矩陣
    char result[5]; //結果開關矩陣
    char switchs; //某一行的開關狀態
    int T;
    //cin >> T;//POJ原題須要輸入T表示有T組測試數據
    T=1;
    for( int t = 1; t <= T; ++ t)
    {
        memset(oriLights,0,sizeof(oriLights));
        for( int i = 0;i < 5; i ++ )//讀入最初燈狀態
        { 
            for( int j = 0; j < 6; j ++ )
            {
                int s;
                cin >> s;
                SetBit(oriLights[i],j,s);
            }
        }
        
        for( int n = 0; n < 64; ++n )//遍歷首行開關的64種操做
        { 
            memcpy(lights,oriLights,sizeof(oriLights));
            switchs = n; //先假定第0行的開關須要的操做方案
            for( int i = 0;i < 5; ++i )
            {
                result[i] = switchs; //保存第i行開關的操做方案
                
                for( int j = 0; j < 6; ++j )//根據方案修改第i行的燈
                {
                    if( GetBit(switchs,j))
                    {   //switchs的第j個位等於1表示須要按下第i行第j個按鈕,等於0表示不須要按下該按鈕
                        if( j > 0) Flip(lights[i],j-1);//改左燈
                        Flip(lights[i],j);//改開關位置的燈
                        if( j < 5 ) Flip(lights[i],j+1);//改右燈
                    }
                }
                if( i < 4 ) lights[i+1] ^= switchs;//改下一行的燈
                
                switchs = lights[i]; //第i+1行開關的操做方案由第i行燈的狀態決定
            }
            if( lights[4] == 0 )
            {
                OutputResult(t,result);
                break;
            }
        } // for( int n = 0; n < 64; n ++ )
    }
    return 0;
}

問題3:畫家問題

*描述

有一個正方形的牆,由<N*N>個正方形的磚組成,其中一些磚是白色的,另一些磚是黃色的。Bob是個畫家,想把所有的磚都塗成黃色。但他的畫筆很差使。當他用畫筆塗畫第(i, j)個位置的磚時, 位置(i-1, j)、 (i+1, j)、 (i, j-1)、 (i, j+1)上的磚都會改變顏色。請你幫助Bob計算出最少須要塗畫多少塊磚,才能使全部磚的顏色都變成黃色。

*輸入
第一行是一個整數n (1≤n ≤15),表示牆的大小。接下來的n行表示牆的初始狀態。每一行包含n個字符。第i行的第j個字符表示位於位置(i,j)上的磚的顏色。「w」表示白磚,「y」表示黃磚。

*輸出
一行,若是Bob可以將全部的磚都塗成黃色,則輸出最少須要塗畫的磚數,不然輸出「inf」。

*樣例輸入
5
wwwww
wwwww
wwwww
wwwww
wwwww

*樣例輸出
15

分析

  • 相似於二維按鈕開關,即上題熄燈問題

解決

//AC
//注意位運算枚舉的技巧

#include<bits/stdc++.h> 
using namespace std;

char a[20][20];
char a_tmp[20][20];
char result[20][20];
int n;
void push(int x,int y){
    for(int i = max(x-1,0);i<=min(n-1,x+1);i++){
        if(a[i][y] == 'w')
            a[i][y] = 'y';
        else
            a[i][y] = 'w';
    }
    for(int i = max(0,y-1);i<=min(n-1,y+1);i++){
        if(i == y)
            continue;
        if(a[x][i] == 'w')
            a[x][i] = 'y';
        else
            a[x][i] = 'w';
    }
}

int main(){
    cin>>n;
    for(int i = 0;i<n;i++){
        for(int j = 0;j<n;j++){
            result[i][j] = 'y';
        }
    }
    for(int i = 0;i<n;i++){
        scanf("%s",a[i]);
    }
    for(int i = 0;i<n;i++){
        for(int j = 0;j<n;j++){
            a_tmp[i][j] = a[i][j];
        }
    }
    int cnt = 0;
    int minp = 1000;
    for(int t = 0;t<pow(2,n);t++){
        cnt = 0;
        for(int i = 0;i<n;i++){
            for(int j = 0;j<n;j++){
                a[i][j] = a_tmp[i][j];
            }
        }
        //第一行處理 
        for(int i = 0;i<n;i++){
            if(t&(1<<i)){
                push(0,i);
                cnt++;
            }
        }
        //之後各行處理
        for(int i = 1;i<n;i++) {
            for(int j = 0;j<n;j++){
                if(a[i-1][j]!=result[i-1][j]){
                    push(i,j);
                    cnt++;
                }
            }
        }
        //判斷是否成功
        int flag = 1;
        for(int i = 0;i<n;i++){
            if(a[n-1][i] != 'y'){
                flag = 0;
                break;
            }
        }
        if(flag){
            if(cnt < minp)
                minp = cnt;         
        }
    }
    
    if(minp < 1000){
        cout<<minp<<endl;
    }else{
        cout<<"inf"<<endl;
    }

    return 0;
}

2. 多層枚舉問題

作一下這個:2694:逆波蘭表達式

問題1: 生理週期

這個題有個坑。

*描述

人生來就有三個生理週期,分別爲體力、感情和智力週期,它們的週期長度爲23天、28天和33天。每個週期中有一天是高峯。在高峯這天,人會在相應的方面表現出色。例如,智力週期的高峯,人會思惟敏捷,精力容易高度集中。由於三個週期的周長不一樣,因此一般三個週期的高峯不會落在同一天。對於每一個人,咱們想知道什麼時候三個高峯落在同一天。對於每一個週期,咱們會給出從當前年份的第一天開始,到出現高峯的天數(不必定是第一次高峯出現的時間)。你的任務是給定一個從當年第一天開始數的天數,輸出從給定時間開始(不包括給定時間)下一次三個高峯落在同一天的時間(距給定時間的天數)。例如:給定時間爲10,下次出現三個高峯同天的時間是12,則輸出2(注意這裏不是3)。

*輸入
一行,包含四個整數:p, e, i和d,相鄰兩個整數之間用單個空格隔開。 p, e, i分別表示體力、情感和智力高峯出現的時間(時間從當年的第一天開始計算)。d 是給定的時間,可能小於p, e, 或 i。 全部給定時間是非負的而且小於等於365, 所求的時間小於等於21252。

*輸出
一個整數,即從給定時間起,下一次三個高峯同天的時間(距離給定時間的天數)。

*樣例輸入
4 5 6 7

*樣例輸出
16994

分析

  • 坑1:給出的到出現高峯的天數不必定是第一次高峯出現的時間,好比p是第一次,而e是第二次,i是第三次都有可能。

  • 坑2:所求的時間小於等於21252,那麼暴力搜索的時間應該是小於等於21252+d!

解決

版本1:枚舉三個高峯期+剪枝

#include<bits/stdc++.h>
using namespace std;

int main(){
    int p,e,i,d;
    while(cin>>p>>e>>i>>d && p+e+i+d!=-4){
        int ans = 0;
        int px = p/23;
        int ex = e/28;
        int ix = i/33;
        for(int a1 = p-px*23;a1<=d+21252;a1+=23){
            for(int a2 = e-ex*28;a2<=d+21252;a2+=28){
                if(a1 != a2){
                    continue;
                }
                for(int a3 = i-ix*33;a3<=d+21252;a3+=33){
                    if(a2 != a3){
                        continue;
                    }
                    if(a1 >= d){
                        ans = a1 - d;
                        break;
                    }
                }
            }
        }       
        cout<<ans<<endl;        
    }

    return 0;
}

版本2:直接枚舉全部時間,判斷是否爲高峯期

#include<bits/stdc++.h>
using namespace std;
int main(){
    int p,e,i,d;
    while(cin>>p>>e>>i>>d && p+e+i+d!=-4){
        int a;
        for(a = d+1;a<=d+21252;a++){
            if((a-p) %23 ==0 && (a-e)%28==0 && (a-i)%33==0){
                break;  
            }
        }
        cout<<a-d<<endl;
    }
    return 0;   
}

問題2: 撥鍾問題

總時間限制: 1000ms 內存限制: 65536kB
    
*描述
有9個時鐘,排成一個3*3的矩陣。

|-------|    |-------|    |-------|
|       |    |       |    |   |   |
|---O   |    |---O   |    |   O   |
|       |    |       |    |       |
|-------|    |-------|    |-------|
    A            B            C    

|-------|    |-------|    |-------|
|       |    |       |    |       |
|   O   |    |   O   |    |   O   |
|   |   |    |   |   |    |   |   |
|-------|    |-------|    |-------|
    D            E            F    

|-------|    |-------|    |-------|
|       |    |       |    |       |
|   O   |    |   O---|    |   O   |
|   |   |    |       |    |   |   |
|-------|    |-------|    |-------|
    G            H            I    
(圖 1)
如今須要用最少的移動,將9個時鐘的指針都撥到12點的位置。共容許有9種不一樣的移動。以下表所示,每一個移動會將若干個時鐘的指針沿順時針方向撥動90度。
移動    影響的時鐘
 1         ABDE
 2         ABC
 3         BCEF
 4         ADG
 5         BDEFH
 6         CFI
 7         DEGH
 8         GHI
 9         EFHI    

*輸入
9個整數,表示各時鐘指針的起始位置,相鄰兩個整數之間用單個空格隔開。其中,0=12點、1=3點、2=6點、3=9點。

*輸出
輸出一個最短的移動序列,使得9個時鐘的指針都指向12點。按照移動的序號從小到大輸出結果。相鄰兩個整數之間用單個空格隔開。

*樣例輸入
3 3 0 
2 2 2 
2 1 2 
    
*樣例輸出
4 5 8 9

分析

  • 每一個鐘最多移動3次,故每一個鐘有4種移動方式-撥動0次、1次、2次、3次
  • 移動次序與結果無關,故總的枚舉次數是4的9次方
  • 給出了9種移動方式。例如讓1-9撥動i1到i9次,則就能夠根據影響的鐘,使得每一個鐘都處於4,則完成
  • 暴力,搜索移動次數最小的一種
  • 簡單,但須要理解

解決

#include<bits/stdc++.h> 
using namespace std;

int a[10] = {0};
int res[10] = {0};
int main(){
    for(int i = 1;i<=9;i++){
        cin>>a[i];
    }
    
    //九層循環暴力
    int mincount = 10000;
    for(int i1 = 0;i1<4;i1++) {
        for(int i2 = 0;i2<4;i2++){
            for(int i3=0;i3<4;i3++){
                for(int i4=0;i4<4;i4++){
                    for(int i5=0;i5<4;i5++){
                        for(int i6=0;i6<4;i6++){
                            for(int i7=0;i7<4;i7++){
                                for(int i8=0;i8<4;i8++){
                                    for(int i9=0;i9<4;i9++){
                    if(((i1+i2+i4+a[1])%4== 0)&&
                      ((i1+i2+i3+i5+a[2])%4==0) &&
                      ((i2+i3+i6+a[3])%4==0) &&
                      ((i1+i4+i5+i7+a[4])%4==0) &&
                      ((i1+i3+i7+i9+i5+a[5])%4==0) &&
                      ((i3+i5+i6+i9+a[6])%4==0) &&
                      ((i4+i7+i8+a[7])%4==0) &&
                      ((i5+i7+i8+i9+a[8])%4==0) &&
                      ((i8+i9+i6+a[9])%4==0)){
                        int sums = i1+i2+i3+i4+i5+i6+i7+i8+i9;
                        if(sums<mincount){
                            mincount = sums;
                            res[1] = i1;
                            res[2] = i2;
                            res[3] = i3;
                            res[4] = i4;
                            res[5] = i5;
                            res[6] = i6;
                            res[7] = i7;
                            res[8] = i8;
                            res[9] = i9;
                          }   
                      } 
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    //打印結果
    for(int i = 1;i<=9;i++){
        while(res[i]--){
            printf("%d ",i);
            //res[i]--;
        }
    }
    cout<<endl; 
    return 0;
}

問題3:反正切函數的應用

@這題不錯。

總時間限制: 1000ms 內存限制: 65536kB

*描述
反正切函數可展開成無窮級數,有以下公式
(其中0 <= x <= 1) 公式(1)
使用反正切函數計算PI是一種經常使用的方法。例如,最簡單的計算PI的方法:
PI=4arctan(1)=4(1-1/3+1/5-1/7+1/9-1/11+...) 公式(2)
然而,這種方法的效率很低,但咱們能夠根據角度和的正切函數公式:
tan(a+b)=[tan(a)+tan(b)]/[1-tan(a)*tan(b)] 公式(3)
經過簡單的變換獲得:
arctan(p)+arctan(q)=arctan[(p+q)/(1-pq)] 公式(4)
利用這個公式,令p=1/2,q=1/3,則(p+q)/(1-pq)=1,有
arctan(1/2)+arctan(1/3)=arctan[(1/2+1/3)/(1-1/2*1/3)]=arctan(1)
使用1/2和1/3的反正切來計算arctan(1),速度就快多了。
咱們將公式(4)寫成以下形式
arctan(1/a)=arctan(1/b)+arctan(1/c)
其中a,b和c均爲正整數。
咱們的問題是:對於每個給定的a(1 <= a <= 60000),求b+c的值。咱們保證對於任意的a都存在整數解。若是有多個解,要求你給出b+c最小的解。

*輸入
輸入文件中只有一個正整數a,其中 1 <= a <= 60000。

*輸出
輸出文件中只有一個整數,爲 b+c 的值。

*樣例輸入
1

*樣例輸出
5

分析

  • 經過化簡,轉換成b+c = 2a+t+(a^2+1)/t,注意a^2+1必須整除t。

  • 對於上個式子,枚舉m,求最小值。

  • 1a=b+cbc−11a=b+cbc−1
    c=ab+1b−a(b>a)c=ab+1b−a(b>a)
    b+c=b+ab+1b−a=b+ab+1+a∗a−a∗ab−a=b+a+a∗a+1b−ab+c=b+ab+1b−a=b+ab+1+a∗a−a∗ab−a=b+a+a∗a+1b−a
    令t=b−at=b−a
    b+c=f(t)=t+2a+a∗a+1tb+c=f(t)=t+2a+a∗a+1t。

    以t=a爲界限,左右枚舉t。

  • 注意整形溢出!

解決

#include<iostream>
#include<algorithm>

using namespace std;

int main()
{
    unsigned int a, t, ans1, ans2;
    cin >> a;
    for (t = a; t > 0; t--)
    {
        if ((a*a + 1) % t == 0)
        {
            ans1 = t + 2 * a + (a*a + 1) / t;
            break;
        }
    }

    for (t = a + 1;; t++)
    {
        if ((a*a + 1) % t == 0)
        {
            ans2 = t + 2 * a + (a*a + 1) / t;
            break;
        }
    }

    cout << min(ans1, ans2) << endl;

    return 0;
}
//這個代碼以t=a爲界限,向左枚舉,也經過;

//bc-1 = ab+ac
//由於b和c必定大於a
//故令b=a+m,c=a+n;
//則mn=a^2+1;
//b+c = 2a+m+(a^2+1)/m 枚舉m算最小值
#include<stdio.h> 
typedef long long ll;
int main()
{
    ll a,m,ans;
    while(scanf("%lld",&a)!=EOF)
    {
        for(m=a;m>=1;m--)
            if((a*a+1)%m==0)break;
        ans=(a*a+1)/m+m+2*a;//b+c的值 
        printf("%lld\n",ans);
    }
    return 0;
}

3、遞歸搜索

如何輸入一行字符串?可能包括空格。

方法1

#include<string>
string s;
getline(cin,s);
//注意,cin.getline(s,5);必須指定讀入的字符個數,默認結束邊界爲'\n',添加第三個參數能夠指定

方法2

#include<string>
char s[1001];
gets(s);

string 和 char* 的相互轉換

string ->char * 須要調用函數:

const char *c = s.data();//或者 char *c = (char *)s.data();
const char *c = s.c_str();//或者 char *c = (char *)s.c_str();

char * -> string 直接賦值:

string s = c;

1. 簡單遞歸

問題1:逆波蘭表達式

總時間限制: 1000ms 內存限制: 65536kB

*描述
逆波蘭表達式是一種把運算符前置的算術表達式,例如普通的表達式2 + 3的逆波蘭表示法爲+ 2 3。逆波蘭表達式的優勢是運算符之間沒必要有優先級關係,也沒必要用括號改變運算次序,例如(2 + 3) * 4的逆波蘭表示法爲* + 2 3 4。本題求解逆波蘭表達式的值,其中運算符包括+ - * /四個。

*輸入
輸入爲一行,其中運算符和運算數之間都用空格分隔,運算數是浮點數。

*輸出
輸出爲一行,表達式的值。
可直接用printf("%f\n", v)輸出表達式的值v。

*樣例輸入
* + 11.0 12.0 + 24.0 35.0

*樣例輸出
1357.000000

*提示
可以使用atof(str)把字符串轉換爲一個double類型的浮點數。atof定義在math.h中。
此題可以使用函數遞歸調用的方法求解。

解決

#include<bits/stdc++.h>
using namespace std;
string s;
double dfs(){
    string s;
    cin>>s;
    if(s.size()==1){
        if(s[0] == '+')
            return dfs()+dfs();
        else if(s[0] == '*')
            return dfs()*dfs();
        else if(s[0] == '-')
            return dfs()-dfs();
        else if(s[0] == '/')
            return dfs()/dfs();                 
    }else{
        return atof(s.data());
    }
}
int main(){
    printf("%f\n",dfs());
}

2. 遞增排列組合類

問題1:分解因數

排列組合類1:從起點開始遞增的排列組合。可重複或者不可重複,且與順序無關

總時間限制: 1000ms 內存限制: 65536kB

*描述
給出一個正整數a,要求分解成若干個正整數的乘積,即a = a1 * a2 * a3 * ... * an,而且1 < a1 <= a2 <= a3 <= ... <= an,問這樣的分解的種數有多少。注意到a = a也是一種分解。

*輸入
第1行是測試數據的組數n,後面跟着n行輸入。每組測試數據佔1行,包括一個正整數a (1 < a < 32768)

*輸出
n行,每行輸出對應一個輸入。輸出應是一個正整數,指明知足要求的分解的種數

*樣例輸入
2
2
20

*樣例輸出
1
4

分析

  • 這類題目,從起點開始,要求遞增排列組合起來
  • 這類題目的核心:
    • 1 枚舉全部起點(main中的for)
    • 2 枚舉全部後繼(dfs中的for)
    • 3 dfs(出口參數 , 條件參數1 , 條件參數2…) ; \(注意條件參數個數因題目限制的不一樣而有差別,具體能夠對比下題注意條件參數個數因題目限制的不一樣而有差別,具體能夠對比下題\)
    • 出口
    • 剪枝
    • 遞增枚舉
  • 若再要求因子不能重複,則思路大體相同,只不過枚舉後繼時,記得從前一個因子的下一個開始枚舉

解決

#include<bits/stdc++.h>
using namespace std;
int n,a,ans;

void dfs(int k,int pre)//限定條件:遞增 由參數pre實現(k用來判遞歸出口)
{
    if(k==a)
    {
        ans++;
        return ;
    }
    if(k>a) return ;
    for( int i=pre;i<=a;i++)//若是是不重複遞增則i=pre+1
    {
        if(k*i<=a) //剪枝
            dfs(k*i,i);
        else break;
    }
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        ans=0;
        cin>>a;
        for(int i=2;i<=a;i++) dfs(i,i);//枚舉全部起點,第一個因子是i,前面的乘積也是i
        cout<<ans<<endl;
    }
   return 0;
}

問題2:放蘋果

排列組合類2:起點開始遞增的排列組合且限制增長

總時間限制: 1000ms 內存限制: 65536kB

描述
把M個一樣的蘋果放在N個一樣的盤子裏,容許有的盤子空着不放,問共有多少種不一樣的分法?(用K表示)5,1,1和1,5,1 是同一種分法。

輸入
第一行是測試數據的數目t(0 <= t <= 20)。如下每行均包含二個整數M和N,以空格分開。1<=M,N<=10。

輸出
對輸入的每組數據M和N,用一行輸出相應的K。

樣例輸入
1
7 3
    
樣例輸出
8

分析

  • 相比上題目,一是改成加法,二是條件多了一個」項數不能超過m「。

解決

#include<bits/stdc++.h>
using namespace std;

int n,m;
int ans;
void dfs(int presum,int prem,int pre){
    if(prem > m){
        return;
    }
    if(presum == n){
        ans++;
        return;
    }
    if(presum > n){
        return;
    }
    
    for(int i = pre;i<=n;i++){
        if(i+presum <= n){
            dfs(i+presum,prem+1,i);
        }else{
            break;  
        }
    }
}
int main(){
    
    int t;
    cin>>t;
    while(t--){
        cin>>n>>m;
        ans = 0;
        for(int i = 1;i<=n;i++){
            dfs(i,1,i);
        }
        cout<<ans<<endl;
    }
    
    return 0;
}

問題3:碎紙機

排列組合類3:增長限制+綜合上兩題

*描述
你如今負責設計一種新式的碎紙機。通常的碎紙機會把紙切成小片,變得難以閱讀。而你設計的新式的碎紙機有如下的
特色:
1.每次切割以前,先要給定碎紙機一個目標數,並且在每張被送入碎紙機的紙片上也須要包含一個數。
2.碎紙機切出的每一個紙片上都包括一個數。
3.要求切出的每一個紙片上的數的和要不大於目標數並且與目標數最接近。
舉一個例子,以下圖,假設目標數是50,輸入紙片上的數是12346。碎紙機會把紙片切成4塊,分別包含1,2,34和6。這樣這些數的和是43 (= 1 + 2 + 34 + 6),這是全部的分割方式中,不超過50,而又最接近50的分割方式。又好比,分割成1,23,4和6是不正確的,由於這樣的總和是34 (= 1 + 23 + 4 + 6),比剛纔獲得的結果43小。分割成12,34和6也是不正確的,由於這時的總和是52 (= 12 + 34 + 6),超過了50。
還有三個特別的規則:
1.若是目標數和輸入紙片上的數相同,那麼紙片不進行切割。
2.若是不論怎樣切割,分割獲得的紙片上數的和都大於目標數,那麼打印機顯示錯誤信息。
3.若是有多種不一樣的切割方式能夠獲得相同的最優結果。那麼打印機顯示拒絕服務信息。好比,若是目標數是15,輸入紙片上的數是111,那麼有兩種不一樣的方式能夠獲得最優解,分別是切割成1和11或者切割成11和1,在這種狀況下,打印機會顯示拒絕服務信息。
爲了設計這樣的一個碎紙機,你須要先寫一個簡單的程序模擬這個打印機的工做。給定兩個數,第一個是目標數,第二個是輸入紙片上的數,你須要給出碎紙機對紙片的分割方式。

*輸入
輸入包括多組數據,每一組包括一行。每行上包括兩個正整數,分別表示目標數和輸入紙片上的數。已知輸入保證:兩個數都不會以0開頭,並且兩個數至多都只包含6個數字。
輸入的最後一行包括兩個0,這行表示輸入的結束。

*輸出
對每一組輸入數據,輸出相應的輸出。有三種不一樣的輸出結果:
sum part1 part2 ... 
rejected 
error 

第一種結果表示:
1.每個partj是切割獲得的紙片上的一個數。partj的順序和輸入紙片上原始數中數字出現的次序一致。
2.sum是切割獲得的紙片上的數的和,也就是說:sum = part1 + part2 +...
第一種結果中相鄰的兩個數之間用一個空格隔開。

若是不論怎樣切割,分割獲得的紙片上數的和都大於目標數,那麼打印「error」。
若是有多種不一樣的切割方式能夠獲得相同的最優結果,那麼打印「rejected」。 

*樣例輸入
50 12346
376 144139
927438 927438
18 3312
9 3142
25 1299
111 33333
103 862150
6 1104
0 0

*樣例輸出
43 1 2 34 6
283 144 139
927438 927438
18 3 3 12
error
21 1 2 9 9
rejected
103 86 2 15 0
rejected

分析

  • 限制增多
  • 記錄符合條件的」排列方式「

解決

#include<bits/stdc++.h>
using namespace std;

int sum_num[1000001];
int sums;
stack<int> st1,st2,re;
string bs;
int a,b;
int consult[1001000];
int strToInt(){
    int anss = 0;
    int len = bs.size();
    for(int i = 0;i<len;i++){
        anss*=10;
        anss+=(bs[i]-'0');
    }
    return anss;
}

void dfs(int pre,int next,int k){ 
    //前面已經分割出的和是pre,剩餘可部分還有k位,next是剩餘可分割的數字
    if(k<=0){
        if(pre <= a){   
            if(pre >= sums){
                sums = pre;
                sum_num[sums]++;
                st2 = st1;
                //清空re
                while(!re.empty()) {
                    re.pop();
                }
                while(!st1.empty()){
                    re.push(st1.top());
                    st1.pop();
                }
                st1 = st2;
            }
        }
        return;
    }

    if(pre > a){
        return;
    }
    
    for(int i = 10;i<= pow(10,k);i*=10){
        if(pre+next%i <= a)
        {
            st1.push(next%i);
            dfs(pre+next%i,next/i,k-log(i)/log(10));
            st1.pop();
        }
            
    }
    
}

int main(){
    while(cin>>a>>bs){
        b = strToInt();
        if(!a && !b){
            break;
        }
        sums = -1;
        memset(sum_num,0,sizeof(sum_num));
        for(int i = 10;i<= pow(10,bs.size());i*=10){
            st1.push(b%i);
            dfs(b%i,b/i,bs.size()-log(i)/log(10));
            st1.pop();
        }
        
        if(sums==-1){ //沒喲
            cout<<"error"<<endl;
        }else if(sum_num[sums] > 1){ //大於1個最優解
            cout<<"rejected"<<endl;
        }else{ //棧輸出結果
            cout<<sums<<" ";
            stack<int> re2;
            while(!re.empty()){
                re2.push(re.top());
                re.pop();
            }
            while(!re2.empty()){
                cout<<re2.top()<<" ";
                re2.pop();
            }   
            cout<<endl;
        }

    }
}

3. 全排列問題

問題1:八皇后問題

總時間限制: 1000ms 內存限制: 65536kB

*描述
會下國際象棋的人都很清楚:皇后能夠在橫、豎、斜線上不限步數地吃掉其餘棋子。如何將8個皇后放在棋盤上(有8 * 8個方格),使它們誰也不能被吃掉!這就是著名的八皇后問題。 
對於某個知足要求的8皇后的擺放方法,定義一個皇后串a與之對應,即a=b1b2...b8,其中bi爲相應擺法中第i行皇后所處的列數。已經知道8皇后問題一共有92組解(即92個不一樣的皇后串)。
給出一個數b,要求輸出第b個串。串的比較是這樣的:皇后串x置於皇后串y以前,當且僅當將x視爲整數時比y小。

*輸入
第1行是測試數據的組數n,後面跟着n行輸入。每組測試數據佔1行,包括一個正整數b(1 <= b <= 92)

*輸出
輸出有n行,每行輸出對應一個輸入。輸出應是一個正整數,是對應於b的皇后串。

*樣例輸入
2
1
92

*樣例輸出
15863724
84136275

分析

  • 有限制的全排列問題,而且與順序有關
  • 遞歸解決,從第一行開始放置,dfs(2)放第2行,直到第8行放完,故遞歸結束條件是dfs第9行
  • 其實一共四個約束:
    • 不能同一行:dfs天然解決
    • 不能同一列:設置標誌數組記錄每一列是否放置了
    • 左上-右下斜對角線不能同時放置:設置標誌數組dex,dex[y-x+8]=1,y-x+8表示<x,y>所在的左上右下斜對角線的惟一標識
    • 右上-左下斜對角線不能同時放置:設置標誌數組dex2,dex[x+y]=1,x+y表示<x,y>所在的右上-左下斜對角線的惟一標識
  • 本題打表

解決

#include<bits/stdc++.h> 
using namespace std;

int a[20][20];
int row[10];
int dex[20];
int dex2[20];
int n;
int numOfAns;

int result[100][10];

void dfs(int prex){
    if(prex > 8){
        //每找到一個結果,記錄下來
        int cnt = 0;
        for(int i = 1;i<=8;i++){
            for(int j = 1;j<=8;j++){
                if(a[i][j] == 1){
                    result[numOfAns][cnt++] = j;
                    break;
                }
            }
        }
        numOfAns++;
        return;
    }

    //放第prex行
    for(int i = 1;i<=8;i++){
        if(row[i] == 0 && dex[i-prex+ 8] == 0 && dex2[i+prex] == 0){
            a[prex][i] = 1;
            dex[i-prex+8] = 1;
            row[i] = 1; 
            dex2[i+prex] = 1;
                
            dfs(prex+1);
            
            a[prex][i] = 0;
            dex[i-prex+8] = 0;
            row[i] = 0; 
            dex2[i+prex] = 0;
        }
    }
    
    
}

int main(){

    memset(row,0,sizeof(row));
    memset(a,0,sizeof(a));
    memset(dex,0,sizeof(dex));
    memset(dex2,0,sizeof(dex2));
    numOfAns = 0;
    for(int i = 1;i<=8;i++){ //第一行擺設的位置<1,i>
        a[1][i] = 1;
        dex[i-1+8] = 1;
        row[i] = 1; 
        dex2[i+1] = 1;
        
        dfs(2);
        
        a[1][i] = 0;
        dex[i-1+8] = 0;
        row[i] = 0;
        dex2[i+1] = 0;
    }
    int T;
    cin>>T;
    while(T--){
        cin>>n;
        for(int i = 0;i<8;i++){
            cout<<result[n-1][i];
        }
        cout<<endl;     
    }   
    return 0;
}

問題2:棋盤問題

雖然與N皇后相似,但仍是有不少坑的,暴力每一層會超時。

再作作。

*描述
在一個給定形狀的棋盤(形狀多是不規則的)上面擺放棋子,棋子沒有區別。要求擺放時任意的兩個棋子不能放在棋盤中的同一行或者同一列,請編程求解對於給定形狀和大小的棋盤,擺放k個棋子的全部可行的擺放方案C。

*輸入
輸入含有多組測試數據。
每組數據的第一行是兩個正整數,n k,用一個空格隔開,表示了將在一個n*n的矩陣內描述棋盤,以及擺放棋子的數目。 n <= 8 , k <= n
當爲-1 -1時表示輸入結束。
隨後的n行描述了棋盤的形狀:每行有n個字符,其中 # 表示棋盤區域, . 表示空白區域(數據保證不出現多餘的空白行或者空白列)。

*輸出
對於每一組數據,給出一行輸出,輸出擺放的方案數目C (數據保證C<2^31)。

*樣例輸入
2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1

*樣例輸出
2
1

分析

  • 相比於N皇后,少了斜對角線的條件
  • 此題dfs的終止不只僅是超過邊界,還要考已經放的棋子數
  • 不是每一層都要放,所以下一次遞歸dfs並不必定是從下一層開始,也有多是從下下層開始,從下下層開始也就意味着下一層忽略,只要遞歸時不計下一層放的棋子數(+1)便可。固然也有可能從下下下層開始,但綜合考慮,從本層開始,下一次遞歸只有兩種可能,要麼下一層,要麼下下層,所謂下下下層即:本層開始,下層忽略,下下層也忽略,天然就從下下下層遞歸,依次類推。
  • 總而言之,從i層開始放旗子,下一次遞歸要麼是:找到了i+1層某個位置,放棋子;要麼是:從i+2層開始遞歸。

解決

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char a[10][10];     //記錄棋盤位置
bool book[10];        //記錄一列是否已經放過棋子
int n,k;
int total;    //total 是放棋子的方案數 ,m是已放入棋盤的棋子數目

void DFS(int index,int dijigeshu)
{
    if(dijigeshu==k)
    {
        total++;
        return ;
    }
    if(index>k)    //邊界
        return ;
    for(int j=0; j<n; j++)
    //判斷條件,與八皇后相比這裏多了判斷是否爲棋盤區的條件
        if(book[j]==0 && a[index][j]=='#')
        {
            book[j]=1;
            DFS(index+1,dijigeshu+1);//該行(第index行)找到了能放的位置並放入,到下一行
            book[j]=0;
        }
    DFS(index+1,dijigeshu);  //與八皇后的區別,該行沒有符合條件的位置,直接到下一
}

int main()
{
    int i,j;
    while(scanf("%d%d",&n,&k)&&n!=-1&&k!=-1) //限制條件
    {
        total=0;
        for(i=0; i<n; i++)
            scanf("%s",&a[i]);
        memset(book,0,sizeof(book));
        DFS(0,0);
        printf("%d\n",total);
    }
    return 0;
}

4. 草叢問題

連通塊個數、大小相關

問題1: 紅與黑

廣度優先搜索 - 最大連通點數

*描述
有一間長方形的房子,地上鋪了紅色、黑色兩種顏色的正方形瓷磚。你站在其中一塊黑色的瓷磚上,只能向相鄰的黑色瓷磚移動。請寫一個程序,計算你總共可以到達多少塊黑色的瓷磚。

*輸入
包括多個數據集合。每一個數據集合的第一行是兩個整數W和H,分別表示x方向和y方向瓷磚的數量。W和H都不超過20。在接下來的H行中,每行包括W個字符。每一個字符表示一塊瓷磚的顏色,規則以下
1)‘.’:黑色的瓷磚;
2)‘#’:白色的瓷磚;
3)‘@’:黑色的瓷磚,而且你站在這塊瓷磚上。該字符在每一個數據集合中惟一出現一次。
當在一行中讀入的是兩個零時,表示輸入結束。

*輸出
對每一個數據集合,分別輸出一行,顯示你從初始位置出發能到達的瓷磚數(記數時包括初始位置的瓷磚)。

*樣例輸入
6 9 
....#. 
.....# 
...... 
...... 
...... 
...... 
...... 
#@...# 
.#..#. 
0 0

*樣例輸出
45

分析

  • BFS
  • 給定地圖,圖中最大連通點數
  • queue實現,記得邊界檢查

解決

#include<bits/stdc++.h>
using namespace std;

char maze[100][100];
int n,m;
int sti,stj;
int vis[100][100];
int main(){
    int ans = 0;
    while(scanf("%d%d",&m,&n) &&n&&m){
        for(int i = 0;i<n;i++){
            for(int j = 0;j<m;j++){
                cin>>maze[i][j];
                if(maze[i][j] == '@'){
                    sti=i;
                    stj=j;
                }
            }
        }
        memset(vis,0,sizeof(vis));
        ans = 0;
        queue<pair<int,int> > que;
        que.push(pair<int,int>(sti,stj));
        vis[sti][stj] = 1;
        while(!que.empty()){
            pair<int,int> p = que.front();
            ans++;
            int x = p.first;
            int y = p.second;
            que.pop();
            for(int i = -1;i<=1;i++){
                if(y+i>=0&&y+i<m && maze[x][y+i]=='.' && vis[x][y+i] == 0){
                    vis[x][y+i] = 1;
                    que.push(pair<int,int>(x,y+i));
                }
            }
            for(int i = -1;i<=1;i++){
                if(x+i>=0 && x+i < n &&maze[x+i][y]=='.' && vis[x+i][y] == 0){
                    vis[x+i][y] = 1;
                    que.push(pair<int,int>(x+i,y));
                }
            }       
        }
        cout<<ans<<endl;
        
    }
    return 0;
}

問題2:城堡問題

*描述

     1   2   3   4   5   6   7  
   #############################
 1 #   |   #   |   #   |   |   #
   #####---#####---#---#####---#
 2 #   #   |   #   #   #   #   #
   #---#####---#####---#####---#
 3 #   |   |   #   #   #   #   #
   #---#########---#####---#---#
 4 #   #   |   |   |   |   #   #
   #############################
           (圖 1)

   #  = Wall   
   |  = No wall
   -  = No wall

圖1是一個城堡的地形圖。請你編寫一個程序,計算城堡一共有多少房間,最大的房間有多大。城堡被分割成m*n(m≤50,n≤50)個方塊,每一個方塊能夠有0~4面牆。
輸入
程序從標準輸入設備讀入數據。第一行是兩個整數,分別是南北向、東西向的方塊數。在接下來的輸入行裏,每一個方塊用一個數字(0≤p≤50)描述。用一個數字表示方塊周圍的牆,1表示西牆,2表示北牆,4表示東牆,8表示南牆。每一個方塊用表明其周圍牆的數字之和表示。城堡的內牆被計算兩次,方塊(1,1)的南牆同時也是方塊(2,1)的北牆。輸入的數據保證城堡至少有兩個房間。

*輸出
城堡的房間數、城堡中最大房間所包括的方塊數。結果顯示在標準輸出設備上。

*樣例輸入
4 
7 
11 6 11 6 3 10 6 
7 9 6 13 5 15 5 
1 10 12 7 13 7 5 
13 11 10 8 10 12 13 
    
*樣例輸出
5
9

分析

  • 連通塊個數,並記錄最大連通塊
  • 每一個數字表明牆的加和,能夠用&(1<<i),i=1,2,3,4,來判斷能不能該塊能不能向四個方向走通
  • 第一種方法:BFS
  • 第二種方法:DFS
  • 第三種方法:並查集

解決

BFS

#include<bits/stdc++.h>
using namespace std;
int n,m;
int maze[1001][1001];
int vis[1001][1001];
typedef pair<int,int> P;
int main(){
    cin>>n>>m;
    for(int i = 0;i<n;i++){
        for(int j = 0;j<m;j++){
            cin>>maze[i][j];
        }
    }
    //先用bfs作
    int visnum = 0;
    queue<P> que;
    int maxnum = -1;
    int totalblock = 0;
    int flag = 0;
    memset(vis,0,sizeof(vis));
    for(int i = 0;i<n;i++) {
        for(int j=0;j<m;j++){
            if(!vis[i][j]){
                totalblock++; //塊數+1
                vis[i][j] = 1;
                visnum++;
                que.push(P(i,j));
                int nownum = 0;
                while(!que.empty()){
                    nownum++;
                    P p = que.front();
                    que.pop();
                    int x = p.first;
                    int y = p.second;
                    if(y-1>=0 && !vis[x][y-1] && ((maze[x][y] & 1)==0)){
                        vis[x][y-1] =1;
                        visnum++;
                        que.push(P(x,y-1));
                    }
                    if(y+1<m && !vis[x][y+1] && ((maze[x][y] & 4)==0)){
                        vis[x][y+1] =1;
                        visnum++;
                        que.push(P(x,y+1));
                    }                   
                    if(x-1>=0 && !vis[x-1][y] && ((maze[x][y] & 2)==0)){
                        vis[x-1][y] =1;
                        visnum++;
                        que.push(P(x-1,y));
                    }                   
                    if(x+1<n && !vis[x+1][y] && ((maze[x][y] & 8)==0)){
                        vis[x+1][y] =1;
                        visnum++;
                        que.push(P(x+1,y));
                    }               
                }
                maxnum = max(maxnum,nownum);
            }
        
            if(visnum == n*m){
                flag = 1;
                break;
            }
        }
        if(flag == 1){
            break;
        }
    }
    cout<<totalblock<<endl<<maxnum<<endl;
    return 0;
}

DFS

#include<cstdio>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#define MAX 53
#define  num 32767
#define INF 0x7f7f7f
#define eps 1e-5
using namespace std;
int dx,nb;
int room=0,fk,maxfk;
int D[MAX][MAX];
bool visit[MAX][MAX]={0};
//這裏四個方向可否走通須要額外判斷
void dfs(int i, int j)
{
    //檢查
    if(visit[i][j]) return;
    if(i<=0||j<=0||i>nb||j>dx) return ;
    //訪問
    visit[i][j]=1;//必有
    fk++;
    //向四個方向延申
    if((D[i][j]&1)==0) dfs(i,j-1);//剛開始沒給&運算加括號,==優先級高於按位與
    if((D[i][j]&2)==0) dfs(i-1,j);
    if((D[i][j]&4)==0) dfs(i,j+1);
    if((D[i][j]&8)==0) dfs(i+1,j);
}

int main()
{
   //freopen("input.txt","r",stdin);
    cin>>nb>>dx;
    for(int i=1;i<=nb;i++)
        for(int j=1;j<=dx;j++)
            cin>>D[i][j];
    memset(visit,sizeof(visit),0);
    for(int i=1;i<=nb;i++)
    {
        for(int j=1;j<=dx;j++)
        {
            if(!visit[i][j])
            {
                room++;
                fk=0;
                dfs(i,j);
                maxfk=max(maxfk,fk);
            }
        }
    }
    cout<<room<<endl<<maxfk<<endl;
   return 0;
}

5. 迷宮問題

路徑、是否聯通、路徑最小長度等

問題1: 迷宮

問是否能走出迷宮?

*描述
一天Extense在森林裏探險的時候不當心走入了一個迷宮,迷宮能夠當作是由n * n的格點組成,每一個格點只有2種狀態,.和#,前者表示能夠通行後者表示不能通行。同時當Extense處在某個格點時,他只能移動到東南西北(或者說上下左右)四個方向之一的相鄰格點上,Extense想要從點A走到點B,問在不走出迷宮的狀況下能不能辦到。若是起點或者終點有一個不能通行(爲#),則當作沒法辦到。

*輸入
第1行是測試數據的組數k,後面跟着k組輸入。每組測試數據的第1行是一個正整數n (1 <= n <= 100),表示迷宮的規模是n * n的。接下來是一個n * n的矩陣,矩陣中的元素爲.或者#。再接下來一行是4個整數ha, la, hb, lb,描述A處在第ha行, 第la列,B處在第hb行, 第lb列。注意到ha, la, hb, lb所有是從0開始計數的。

*輸出
k行,每行輸出對應一個輸入。能辦到則輸出「YES」,不然輸出「NO」。

*樣例輸入
2
3
.##
..#
#..
0 0 2 2
5
.....
###.#
..#..
###..
...#.
0 0 4 0
    
*樣例輸出
YES
NO

分析

  • 簡單連通性判斷

解決

#include<bits/stdc++.h>
using namespace std;

char a[1001][1001];
int vis[1001][1001];
typedef pair<int,int> P;
int n,sx,sy,ex,ey;
int main(){
    int T;
    cin>>T;
    while(T--){
        cin>>n;
        for(int i = 0;i<n;i++){
            cin>>a[i];
        }
        cin>>sx>>sy>>ex>>ey;
        if(a[sx][sy] == '#' || a[ex][ey] == '#'){
            cout<<"NO"<<endl;
            continue;
        }
        memset(vis,0,sizeof(vis));
        queue<P> que;
        que.push(P(sx,sy));
        vis[sx][sy] = 1;
        while(!que.empty()){
            int x = que.front().first;
            int y = que.front().second;
            que.pop();
            for(int i = -1;i<=1;i++){
                if(x+i>=0 &&x+i<n &&!vis[x+i][y] &&a[x+i][y]=='.'){
                    vis[x+i][y] = 1;
                    que.push(P(x+i,y));
                }
                if(y+i>=0 &&y+i<n &&!vis[x][y+i] &&a[x][y+i]=='.'){
                    vis[x][y+i] = 1;
                    que.push(P(x,y+i));
                }               
            }
        }
        
        if(vis[ex][ey] == 1){
            cout<<"YES"<<endl;
        }else{
            cout<<"NO"<<endl;
        }
        
    } 
    return 0;
}

問題2: 走迷宮

走出迷宮的最短路徑長度問題。

描述
一個迷宮由R行C列格子組成,有的格子裏有障礙物,不能走;有的格子是空地,能夠走。
給定一個迷宮,求從左上角走到右下角最少須要走多少步(數據保證必定能走到)。只能在水平方向或垂直方向走,不能斜着走。

輸入
第一行是兩個整數,R和C,表明迷宮的長和寬。( 1<= R,C <= 40)
接下來是R行,每行C個字符,表明整個迷宮。
空地格子用'.'表示,有障礙物的格子用'#'表示。
迷宮左上角和右下角都是'.'。

輸出
輸出從左上角走到右下角至少要通過多少步(即至少要通過多少個空地格子)。計算步數要包括起點和終點。

樣例輸入
5 5
..###
#....
#.#.#
#.#.#
#.#..

樣例輸出
9

分析

  • 增長「求最短步數」。
  • \(step[x][y]\)數組記錄到<x,y>點的步數。

解決

#include<bits/stdc++.h>
using namespace std;

typedef pair<int,int> P;
int m,n;
char a[1001][1001];
int vis[1001][1001];
int step[1001][1001];
int main(){
    cin>>m>>n;
    for(int i = 0;i<m;i++){
        cin>>a[i];
    }
    memset(vis,0,sizeof(vis));
    memset(step,0,sizeof(step));
    
    queue<P> que;
    que.push(P(0,0));
    vis[0][0] = 1;
    step[0][0] = 1;
    while(!que.empty()){
        P p = que.front();
        que.pop();
        int x = p.first;
        int y = p.second;
        if(x == m-1 && y == n-1){
            break;
        }
        if(x+1<m && !vis[x+1][y] && a[x+1][y] == '.'){
            vis[x+1][y] = 1;
            step[x+1][y] = step[x][y]+1;
            que.push(P(x+1,y));
        }
        if(y+1<n && !vis[x][y+1] && a[x][y+1] == '.'){
            vis[x][y+1] = 1;
            step[x][y+1] = step[x][y]+1;
            que.push(P(x,y+1));
        }       
        if(x-1>=0 && !vis[x-1][y] && a[x-1][y] == '.'){
            vis[x-1][y] = 1;
            step[x-1][y] = step[x][y]+1;
            que.push(P(x-1,y));
        }       

        if(y-1>=0 && !vis[x][y-1] && a[x][y-1] == '.'){
            vis[x][y-1] = 1;
            step[x][y-1] = step[x][y]+1;
            que.push(P(x,y-1));
        }               
    }
    cout<<step[m-1][n-1]<<endl;
    return 0;
}

問題3: 仍是迷宮問題

總時間限制: 1000ms 內存限制: 65536kB
    
描述
定義一個二維數組: 

int maze[5][5] = {

0, 1, 0, 0, 0,

0, 1, 0, 1, 0,

0, 0, 0, 0, 0,

0, 1, 1, 1, 0,

0, 0, 0, 1, 0,

};

它表示一個迷宮,其中的1表示牆壁,0表示能夠走的路,只能橫着走或豎着走,不能斜着走,要求編程序找出從左上角到右下角的最短路線。



輸入
一個5 × 5的二維數組,表示一個迷宮。數據保證有惟一解。

輸出
左上角到右下角的最短路徑,格式如樣例所示。

樣例輸入
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
    
樣例輸出
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)

**分析*8

  • 記錄路徑
  • 使用\(pair<int,int>\)類型的二維數組記錄前驅:\(pair<int,int> pre[100][100]\)

解決

#include<bits/stdc++.h>
using namespace std;

int a[10][10];
int vis[10][10];
typedef pair<int,int > P;
//int prex[100][100];
//int prey[100][100];
P pre[100][100];
int main(){
    
    for(int i = 0;i<5;i++){
        for(int j = 0;j<5;j++){
            cin>>a[i][j];
//          prex[i][j] = -1;
//          prey[i][j] = -1;
            pre[i][j].first = -1;
            pre[i][j].second = -1;  
            vis[i][j] = 0;  
        }
    }
    memset(vis,0,sizeof(vis));  
    queue<P> que;
    que.push(P(0,0));

    pre[0][0] = P(-1,-1);

    vis[0][0] = 1;
    while(!que.empty()){
        P p = que.front();
        int x = p.first;
        int y = p.second;
        que.pop();
        if(x == 4 && y== 4){
            break;
        }
        if(x+1<5 && !vis[x+1][y] && a[x+1][y] == 0){
            vis[x+1][y] = 1;
            pre[x+1][y] = P(x,y);
            que.push(P(x+1,y));
            if(x == 4 && y== 4){
                    break;
                }           
        }
        if(y+1<5 && !vis[x][y+1] && a[x][y+1] == 0){
            vis[x][y+1] = 1;
            pre[x][y+1] = P(x,y);
            que.push(P(x,y+1));
            if(x == 4 && y== 4){
                    break;
                }           
        }       
        if(x-1>=0 && !vis[x-1][y] && a[x-1][y] == 0){
            vis[x-1][y] = 1;
            pre[x-1][y] = P(x,y);   
            que.push(P(x-1,y));
            if(x == 4 && y== 4){
                    break;
                }           
        }       

        if(y-1>=0 && !vis[x][y-1] && a[x][y-1] == 0){
            vis[x][y-1] = 1;
            pre[x][y-1] = P(x,y);   
            que.push(P(x,y-1));
            if(x == 4 && y== 4){
                    break;
                }           
        }   
    }
    
    stack<P> st;
    int x1=4,x2=4;
    st.push(P(x1,x2));
    while(pre[x1][x2].first>=0&&pre[x1][x2].second>=0){
        st.push(P(pre[x1][x2].first,pre[x1][x2].second));
        
        int x11 = pre[x1][x2].first;
        int x22 = pre[x1][x2].second;
        x1 = x11;
        x2 = x22;
    }
    while(!st.empty()){
        cout<<"("<<st.top().first<<", "<<st.top().second<<")"<<endl;
        st.pop();
    }
    return 0;
}

6. 廣度優先搜索的剪枝

題目 遊戲
時間: 1.0s
內存: 256.0MB
問題描述: 小明在玩一個電腦遊戲,遊戲在一個n×m的方格圖上進行,小明控制的角色開始的時候站在第一行第一列,目標是前往第n行第m列。方格圖上有一些方格是始終安全的,有一些在一段時間是危險的,若是小明控制的角色到達一個方格的時候方格是危險的,則小明輸掉了遊戲,若是小明的角色到達了第n行第m列,則小明過關。第一行第一列和第n行第m列永遠都是安全的。每一個單位時間,小明的角色必須向上下左右四個方向相鄰的方格中的一個移動一格。通過不少次嘗試,小明掌握了方格圖的安全和危險的規律:每個方格出現危險的時間必定是連續的。而且,小明還掌握了每一個方格在哪段時間是危險的。如今,小明想知道,本身最快通過幾個時間單位能夠達到第n行第m列過關。
輸入: 輸入的第一行包含三個整數n, m, t,用一個空格分隔,表示方格圖的行數n、列數m,以及方格圖中有危險的方格數量。接下來t行,每行4個整數r, c, a, b,表示第r行第c列的方格在第a個時刻到第b個時刻之間是危險的,包括ab。遊戲開始時的時刻爲0。輸入數據保證rc不一樣時爲1,並且當rnc不爲m。一個方格只有一段時間是危險的(或者說不會出現兩行擁有相同的rc)。
輸出: 輸出一個整數,表示小明最快通過幾個時間單位能夠過關。輸入數據保證小明必定能夠過關。樣例輸入:3 3 3 2 1 1 1 1 3 2 10 2 2 2 10 樣例輸出:6。樣例說明:第2行第1列時刻1是危險的,所以第一步必須走到第1行第2列。第二步能夠走到第1行第1列,第三步走到第2行第1列,後面通過第3行第1列、第3行第2列到達第3行第3列。
評測用例規模與約定: 前30%的評測用例知足:0 < n, m ≤ 10,0 ≤ t < 99。全部評測用例知足:0 < n, m ≤ 100,0 ≤ t < 9999,1 ≤ r

分析

  • 毫無疑問是bfs,用隊列

  • 用struct存儲二維點,比用pair進出隊列要快!

  • 剪枝:vis數組標記某個點是否走過。但這裏有個坑,並非走過的就不能走了,由於某點的毒的時間是一個區間,可能必須繞回到某點來躲避毒。因此vis標記的應該是同一時刻,某個點不能重複到達!這個是剪枝的坑,即同一時刻到達過某點,若再繞回到某點,則不能理他。因此vis是三維的,\(vis[x][y][t]=1\)表示在t時刻,(x,y)點已經訪問過了。

  • 這種可能超時的題目,能不調用函數就不調用。調用額外花不少時間!

解決

#include<bits/stdc++.h>
using namespace std;
struct node{
    int x,y,time;
    node(int xx,int yy,int t):x(xx),y(yy),time(t){}
};

int vis[400][300][300];
int n,m,t;
int dis[4][2] = {1,0,0,1,-1,0,0,-1};
int l[104][104];
int r[104][104];
int main(){
    cin>>n>>m>>t;
    memset(vis,0,sizeof(vis));
    queue<node> que;
    
    que.push(node(1,1,0));
    vis[1][1][0] = 1;
    for(int i = 0;i<t;i++){
        int a,b;
        cin>>a>>b;
        cin>>l[a][b]>>r[a][b];
    }
    int ans = 0;
    while(!que.empty()){
        node no = que.front();
        que.pop();
        if(no.x == n && no.y == m){
            ans = no.time;
            break;
        }
        for(int i = 0;i<4;i++){
                int x = no.x+dis[i][0];
                int y = no.y+dis[i][1];
                int t = no.time;
                if(x>=1&&x<=n&&y>=1&&y<=m && vis[x][y][t+1]==0){ //剪枝
                    //看是否沒毒
                    if((l[x][y]==0 && r[x][y] ==0 )) {
                        vis[x][y][t+1] = 1;
                        que.push(node(x,y,t+1));
                    }else if(t+1>=l[x][y] && t+1 <=r[x][y]){
                        continue;
                    }else{
                        vis[x][y][t+1] =1;
                        que.push(node(x,y,t+1));    
                    }
                }               
        }
    }
        
    cout<<ans<<endl;    

    return 0;
}

7. 總操做步數固定枚舉問題

練習這個題。

問題1: 算24

總時間限制: 3000ms 內存限制: 65536kB

*描述
給出4個小於10個正整數,你可使用加減乘除4種運算以及括號把這4個數鏈接起來獲得一個表達式。如今的問題是,是否存在一種方式使得獲得的表達式的結果等於24。

這裏加減乘除以及括號的運算結果和運算的優先級跟咱們日常的定義一致(這裏的除法定義是實數除法)。

好比,對於5,5,5,1,咱們知道5 * (5 – 1 / 5) = 24,所以能夠獲得24。又好比,對於1,1,4,2,咱們怎麼都不能獲得24。

*輸入
輸入數據包括多行,每行給出一組測試數據,包括4個小於10個正整數。最後一組測試數據中包括4個0,表示輸入的結束,這組數據不用處理。

*輸出
對於每一組測試數據,輸出一行,若是能夠獲得24,輸出「YES」;不然,輸出「NO」。

*樣例輸入
5 5 5 1
1 1 4 2
0 0 0 0
    
*樣例輸出
YES
NO

分析

  • 總步數必定,也就是操做4次,每次任選兩個數進行計算
  • 不能\(dfs(pre,step)\),由於這樣實際上枚舉的次數不夠,上次的計算結果pre必定參與進行下一次運算,但這是不對的,好比可能前兩個數運算,後兩個數運算,而後再運算。
  • 正確的思路是:dfs步數,每一步任選兩個數,vis控制選過沒有,將運算結果放置在a[i]中,一輪dfs結束恢復a[i]爲原數。

解決

#include<bits/stdc++.h>
#define eps 1e-5
using namespace std;


double a[10];
int vis[10];
int flag;
int sign(double x){
    if(fabs(x) < eps){
        return 0;
    }
    if(x > eps) return 1;
    else return -1;
}
void dfs(int k){
    if(k == 4){
        for(int i = 0;i<4;i++){
            if(vis[i] == 0){
                if(sign(a[i] - 24.0) == 0){
                    flag = 1;
                    break;
                }
            }
        }
        return;
    }
    
    for(int i = 0;i<4;i++){
        if(!vis[i]){
            for(int j = i+1;j<4;j++){
                if(!vis[j]){
                    vis[j] = 1;
                    
                    double x = a[i];
                    double y = a[j];
                    
                    //六個方向dfs
                    a[i] = x+y;dfs(k+1);
                    a[i] = x-y;dfs(k+1);
                    a[i] = y-x;dfs(k+1);
                    a[i] = x*y;dfs(k+1);
                    if(sign(y)!=0) a[i] = x/y;dfs(k+1);
                    if(sign(x)!=0) a[i] = y/x;dfs(k+1);                 
                    
                    //恢復
                    a[i] = x;
                    a[j] = y;
                    vis[j] = 0;              
                }
            }           
        }
    }
}

int main(){
    while(cin>>a[0]>>a[1]>>a[2]>>a[3]){
        if(sign(a[0]+a[1]+a[2]+a[3]) == 0){
            break;
        }
        memset(vis,0,sizeof(vis));
        flag = 0;
        dfs(1);
        if(flag)
            cout<<"YES"<<endl;
        else
            cout<<"NO"<<endl;
    }
    return 0;
}

4、數學問題

1. 高精度計算

問題1: 大數加法

#include<bits/stdc++.h>
#define MAX 100101
using namespace std;
//輸入cin>> char a[MAX] char b[MAX] 定義 result[MAX+10]足夠了
char* add(char*a,char*b){
    
    int aa[MAX],bb[MAX],temp[MAX*2+10];
    char result[MAX*2+10];
    memset(aa,0,sizeof(aa));
    memset(bb,0,sizeof(bb));
    memset(temp,0,sizeof(temp));
    memset(result,0,sizeof(result));
            
    int lena = strlen(a);
    int lenb = strlen(b);
    
    //注意把順序倒過來 
    for(int i =0;i<lena;i++){
        aa[i] = a[lena-i-1] - '0';
    }
    for(int j = 0;j<lenb;j++){
        bb[j] = b[lenb-j-1] - '0';
    }
    
    for(int i = 0;i<max(lena,lenb);i++){
        temp[i] = aa[i]+bb[i];
    }
    
    int c = 0; //進位
    //注意最多max(lena,lenb)+1位 
    for(int i = 0;i< max(lena,lenb) + 1;i++) {
        int tmpx = c + temp[i];
        temp[i] = tmpx%10;
        c = tmpx/10;
    }
    
    //去除前導0 
    int ind = max(lena,lenb);
    while(temp[ind] == 0 && ind>=0) ind--; 
    if(ind<0) result[0] = '0';//全是0
    else{
        for(int i = 0;i<=ind;i++){
            result[ind-i] = temp[i] + '0';
        }
        result[ind+1] = '\0';
    }
    return result;
}

int main(){
    char a[MAX];
    char b[MAX];
    scanf("%s%s",a,b);
    char* result = add(a,b);
    cout<<result<<endl;
    return 0;
}

問題2: 大數乘法

#include<bits/stdc++.h>
#define MAX 10010
using namespace std;

char result[MAX*2+10];

void Multipy(char*a,char*b){
    int aa[MAX],bb[MAX],temp[MAX];
    int lena = strlen(a),lenb = strlen(b);
    memset(aa,0,sizeof(aa));
    memset(bb,0,sizeof(bb));
    memset(temp,0,sizeof(temp));
    
    for(int i = 0;i<lena;i++){
        aa[i] = a[lena-1-i] - '0';
    }
    for(int i = 0;i<lenb;i++){
        bb[i] = b[lenb-1-i] - '0';
    }
    //雙重循環求和,注意temp下標!temp[i+j]
    for(int i = 0;i<lena;i++){
        for(int j = 0;j<lenb;j++){
            temp[i+j] += (aa[i]*bb[j]);
        }
    }
    //求結果,注意範圍,最多有lena+lenb位
    int c = 0; //進位 
    for(int i = 0;i<lena+lenb+1;i++){
        int mid = temp[i] + c;
        temp[i] = mid % 10;
        c = mid / 10;
    }
    //去除前導0
    int ind = lena+lenb;
    while(temp[ind]==0 && ind>=0){
        ind--;
    }
    if(ind < 0){
        result[0] = '0';
        result[1] = '\0';
    }else{  
        for(int i = 0;i<=ind;i++){
            result[ind-i] = temp[i] + '0';
        }
        result[ind+1] = '\0';
    }
}
int main(){
    char a[MAX];
    char b[MAX];
    scanf("%s%s",a,b);
    Multipy(a,b);
    cout<<result<<endl;

    return 0;
}

問題3: 2的N次方

描述
任意給定一個正整數N(N<=100),計算2的n次方的值。

輸入
輸入一個正整數N。

輸出
輸出2的N次方的值。

樣例輸入
5

樣例輸出
32

解決

直接用long double能夠過

#include<bits/stdc++.h>
using namespace std;

int main(){
    long double x;
    int n;
    cin>>n;
    x = pow(2,n);
    printf("%.0Lf\n",x);
    return 0;
}

高精度乘法運算

爲啥結果同樣,但過不了?

#include<bits/stdc++.h>
#define MAX 100
using namespace std;

char result[100];
//char result2[1001];

void Multipy(char*a,char*b){
    int aa[MAX],bb[MAX],temp[MAX];
    int lena = strlen(a),lenb = strlen(b);
    memset(aa,0,sizeof(aa));
    memset(bb,0,sizeof(bb));
    memset(temp,0,sizeof(temp));
    
    for(int i = 0;i<lena;i++){
        aa[i] = a[lena-1-i] - '0';
    }
    for(int i = 0;i<lenb;i++){
        bb[i] = b[lenb-1-i] - '0';
    }
    //雙重循環求和,注意temp下標 
    for(int i = 0;i<lena;i++){
        for(int j = 0;j<lenb;j++){
            temp[i+j] += (aa[i]*bb[j]);
        }
    }
    //求結果,注意範圍,最多有lena+lenb位
    int c = 0; //進位 
    for(int i = 0;i<lena+lenb+1;i++){
        int mid = temp[i] + c;
        temp[i] = mid % 10;
        c = mid / 10;
    }
    //去除前導0
    int ind = lena+lenb;
    while(temp[ind]==0 && ind>=0){
        ind--;
    }
    if(ind < 0){
        result[0] = '0';
        result[1] = '\0';
    }else{  
        for(int i = 0;i<=ind;i++){
            result[ind-i] = temp[i] + '0';
        }
        result[ind+1] = '\0';
    }
}
int main(){
    while(1){
        memset(result,0,sizeof(result));
        int a;
        cin>>a;
        char b[2];
        b[0] = '2';
        result[0] = '1';
        for(int i = 0;i<a;i++){
            Multipy(result,b);
        //  cout<<result<<endl;
        }
            
        cout<<result<<"---"<<endl;
        
        long double x;
        x = pow(2,a);
        
        printf("%.0Lf\n",x);        
    }
    
    return 0;
}

問題3:浮點數的大數加法

分析

  • 大數加法
  • 注意小數點,對齊小數點
  • 去除前導0、還要記得去除後導0
  • 輸出再添加上小數點

解決

#include<bits/stdc++.h>
using namespace std;
char result[10010];
void add(char *a,char*b){
    int aa[105],bb[105],temp[205];
    memset(aa,0,sizeof(aa));
    memset(bb,0,sizeof(bb));
    memset(temp,0,sizeof(temp));
    
    int lena = strlen(a);
    int lenb = strlen(b);
    //先反轉 
    for(int i = 0;i<lena;i++){
        aa[lena-1-i] = a[i] - '0';
    }
    for(int i = 0;i<lenb;i++){
        bb[lenb-1-i] = b[i] - '0';
    }
    //找到point小數點
    int pointa = -1,pointb = -1;
    for(int i = 0;i<lena;i++){
        if(aa[i] + '0' == '.'){
            pointa = i;
            break;
        }
    }
    for(int i = 0;i<lenb;i++){
        if(bb[i] + '0' == '.'){
            pointb = i;
            break;
        }
    } 
    //小數點對齊 
    if(pointa > pointb){
        int ab = pointa - pointb;
        for(int i = lenb-1+ab;i>=1;i--){
            bb[i] = bb[i-ab];
        }
        lenb = lenb + ab; //更新 
        //bb[0] = 0;
        for(int i = 0;i<ab;i++){
            bb[i] = 0;
        }
    }
    if(pointb > pointa){
        int ab = pointb - pointa;
        for(int i = lena-1+ab;i>=1;i--){
            aa[i] = aa[i-ab];
        }
        lena = lena+ab; //更新 
        for(int i = 0;i<ab;i++) {
            aa[i] = 0;          
        }
    }
    //去掉小數點
    int point = max(pointa,pointb);
    for(int i = point;i<lena-1;i++){
        aa[i] = aa[i+1];
    }
    aa[lena-1] = 0;
    lena--;
    for(int i = point;i<lenb-1;i++){
        bb[i] = bb[i+1];
    }
    bb[lenb-1] = 0;
    lenb--; 
    //相加
    for(int i = 0;i<max(lena,lenb)+1;i++) {
        temp[i] = aa[i]+bb[i];
    }
    int cnt = 0; //進位
    for(int i = 0;i<max(lena,lenb)+2;i++) {
        int mid = temp[i] + cnt;
        temp[i] = mid % 10;
        cnt = mid / 10;
    }
    //添加小數點 point位置
    for(int i = max(lena,lenb)+1;i>point;i--) {
        temp[i] = temp[i-1];
    }
    temp[point] = '.'-'0';
    //去除前導0
    int ind = max(lena,lenb)+1;
    while(temp[ind] == 0) ind--;
    //去除後導0
    int fnd = 0;
    while(temp[fnd] == 0)  fnd++;
    
    if(ind < 0){
        result[0] = '0';
        result[1] = '\0';
    }else{
        if(temp[ind] == '.' - '0'){
            ind++;
        }
        int cntt = 0;
        for(int i = fnd;i<=ind;i++){
            result[cntt] = temp[ind-cntt] + '0';
            cntt++;
        }
        result[ind+1] = '\0';
    }
}

int main(){
    
    char a[1000];
    char b[1000];
    cin>>a>>b;
    add(a,b); 
    printf("%s",result);
        
    return 0;
}

2. 素數和質因子分解

問題1: 素數 - 歐拉篩法

用於篩選出某一範圍內的素數,常先打表。歐拉篩法快於艾氏篩法,其核心是:每次測試數 i 時,篩選掉小於等於 i 的素數同 i 的乘積構成的合數。

  • 這個容易忘記,多看一下
#include<bits/stdc++.h>
using namespace std;

int isprime[10010];
int prime[10010];
void getPrime(int n){

    fill(isprime,isprime+n,1);
    isprime[0] = isprime[1] = 0;
    int cnt = 0;
    for(int i = 2;i<=n;i++){
        if(isprime[i]){
            prime[cnt++] = i;
        }
        
        for(int j = 0;j<cnt&&i*prime[j]<=n;j++){
            isprime[i*prime[j]] = 0;
        }
    }
    //最後:素數表是prime, 個數是cnt 
    
}

int main(){
    getPrime(10000);
    for(int i = 0;i<100;i++){
        printf("%d ",prime[i]);
    }
    return 0;
}

問題2: 質因子分解

如: \(180 = 2^2 * 3^2*5^1\),每一個合數均可以寫成幾個質數相乘的形式,其中每一個質數都是這個合數的因數。

#include<bits/stdc++.h>
using namespace std;

//先創建素數表
int isprime[10010];
int prime[10010];
int p_index;
struct node{
    int x; //因子 
    int e; //指數 
}Node[1001];

void getPrime(int n){
    fill(isprime,isprime+n,1);
    p_index = 0;
    isprime[0] = isprime[1] = 0;
    for(int i = 2;i<=n;i++){
        if(isprime[i]){
            prime[p_index++] = i;
        }       
        for(int j = 0;j<p_index && i*prime[j]<=n;j++){
            isprime[i*prime[j]] = 0;
        }
    }
}

int main(){
    
    int n,n_tmp;
    cin>>n;
    n_tmp = n;
    //獲取素數表
    getPrime(n/2+1);
    //遍歷全部2-n/2素數 
    int cnt = 0;//項數下標
    for(int i = 0;i<p_index;i++){
        if(n%prime[i] == 0){
            Node[cnt].x = prime[i];
            Node[cnt].e = 0;
            //找指數
            while(n%prime[i] == 0) {
                Node[cnt].e ++;
                n /= prime[i];
            }
            cnt++;
        }
    }
    
    if(n>1){ //說明n是個質數
        Node[0].x = n_tmp; 
        Node[0].e = 1;
        cnt = 1;
    }
    
    //打印結果
    cout<<n_tmp<<" = ";
    for(int i = 0;i<cnt-1;i++) {
        cout<<Node[i].x<<"^"<<Node[i].e<<" * ";
    }
    cout<<Node[cnt-1].x<<"^"<<Node[cnt-1].e<<endl;  
    return 0;
}

3. 歐幾里得和擴展歐幾里得算法

問題1: 歐幾里得算法

即展轉相除法求最大公約數。利用的性質是:\(gcd(a,b)=gcd(b,a\%b)\),而\(gcd(a,0)=a\),這樣,展轉除下去,當第二個參數爲0,第一個參數就是最大公約數。

#include<bits/stdc++.h>
using namespace std;
int gcd(int a,int b){
    while(b!=0){
        int tmp = a%b;
        a =  b;
        b = tmp;
    }
    return a;
}
int main(){ 
    int a,b;
    cin>>a>>b;
    int c = gcd(a,b);
    cout<<c;
    return 0;   
}

問題2: 擴展歐幾里得算法

EcGcd算法: 擴展歐幾里得算法不只能夠用來求最大公約數,還能夠求逆元知足ax+by=gcd(a,b)的x和y。

基於的原理是:ax+by=gcd(a,b)必定存在解(x,y)。

一個用處就是:問ax+by=1是否有解,就是看a,b是否互質,即gcd(a,b)=1。

求知足ax+by=gcd(a,b)的(x,y)的過程,就是證實ax+by=gcd(a,b)必定有解的過程。

\(ax+by=gcd(a,b)=gcd(b,a\%b)=bx_2+a\%by_2\)

\(a\%b=a-a/b*b\)

所以 \(ax+by =bx_2+(a-a/b*b)y_2 =ay_2+b(x_2-a/b*y2)\)

從而 \(;x = y2;y=x_2-a/by_2\)

層層遞歸下去,最終當\(b=0\)時,返回\(gcd(a,b)=a\),此時有一組解\(x=1,y=0\),回溯,利用上式求出\(x,y\)

從而,這就創建了要求的(x,y)和前一狀態的關係,算法的遞歸實現以下:

#include<bits/stdc++.h>
using namespace std;


int exgcd(int a,int b,int& x,int& y){
    if(b == 0){
        x = 1;
        y = 0;
        return a;
    }
    int gcd = exgcd(b,a%b,x,y);
    int tmp = x;
    x = y;
    y = tmp - a/b*y;
    
    return gcd;
}

int main(){
    
    int a,b;
    cin>>a>>b;
    int x,y;
    int gcd = exgcd(a,b,x,y);
    cout<<a<<" * ("<<x<<") + " <<b <<" * ("<<y<<") = "<<gcd<<endl;
    return 0;
}

問題3: 求最小逆元

  • 逆元:\(ax == 1 mod(m)\),則稱\(x\)\(a\)關於\(m\)的逆元。
  • 最小逆元:知足上式的結果\(x\)可不止一個,每每要求的是最小的(且正)。
  • 求:\(ax==1mod(m)\) \(=>\) \(ax+my=1\)有解,求此式的解中最小的\(x\)便可。即求出此式\(x\)\(x=x\%m\),若是\(x<0\)\(x+=m\)
  • \(ax+my=1\)有解的條件是\(gcd(a,m)=1\)

求解代碼藉助exgcd。

//求最小逆元
#include<bits/stdc++.h>
using namespace std;
int exgcd(int a,int b,int &x,int &y){
    if(b == 0){
        x = 1;
        y = 0;
        return a;
    }
    int gcd = exgcd(b,a%b,x,y);
    int tmp = x;
    x = y;
    y = tmp - a/b * y; //這個公式會推 
    return gcd;
}

int cal(int a,int m){
    //求a關於m的逆元
    //ax+my=1  ax+by=1有解的條件是:gcd(a,b)=1 
    int x,y;
    int gcd = exgcd(a,m,x,y);
    if(gcd != 1) return -1; //逆元不存在 
    x = x % m;
    if(x < 0) x = x + m;
    return x;
}
int main(){
    int a,m;
    cin>>a>>m;
    cout<<"逆元:"<<cal(a,m)<<endl;
    return 0;
}

問題4: 青蛙約會

挺好的一題。佔坑

4. 進制轉換問題

問題1: 十進制轉R進制

#include<bits/stdc++.h>
using namespace std;

void  tenToR(int a,int r, char* result){
    
    int cnt = 0;
    while(a){
        result[cnt++] = a % r + '0';
        a /= r;
    }
    if(r == 16){
        for(int i = 0;i<cnt;i++){
            if(result[i] == 10+'0'){
                result[i] = 'A' + '0';
            }else if(result[i] == 11+'0'){
                result[i] = 'B' + '0';
            }else if(result[i] == 12+'0'){
                result[i] = 'C' + '0';
            }else if(result[i] == 13+'0'){
                result[i] = 'D' + '0';
            }else if(result[i] == 14+'0'){
                result[i] = 'E' + '0';
            }else if(result[i] == 15+'0'){
                result[i] = 'F' + '0';
            }
        }       
    }
    result[cnt] = '\0';
    //cout<<resul<<endl;
    for(int i = 0;i<cnt;i++){
        //cout<<result[i] - '0'<<endl;
        //printf("%c",result[i]-'0');
        result[i] = result[i] - '0';
    }
}

int main(){
    
    int a;
    int r;
    cin>>a>>r;
    
    char result[100];
    tenToR(a,r,result);
    cout<<result<<endl;
    return 0;
}

問題2: R進制轉10進制

#include<bits/stdc++.h>
using namespace std;
int RtoTen(char* a,int r){
    int len = strlen(a);
    int result = 0;
    for(int i = 0;i<len;i++){
        result*=r;
        result+=a[i]-'0';
    }
    return result;
}

int main(){
    char a[100];
    int r; //r進制
    cin>>a;
    cin>>r;
    cout<<RtoTen(a,r);
    return 0;
}

5. 日期計算問題

問題1: 兩日期天數差

  • 已知兩日期,求中間間隔天數
  • 日期格式化輸入
#include<bits/stdc++.h>
using namespace std;

int month[2][13]; //第一行12個數爲不是閏年的12個月的天數 

bool isLeapYear(int y){ //判斷是不是閏年
    if(y % 400 == 0)  return 1;
    if(y % 4 == 0 && y % 100 != 0)   return 1;
    return 0;
}

void getMonthDay(){
    for(int i = 0;i<=1;i++){
        month[i][1] = 31;
        month[i][2] = 28;
        month[i][3] = 31;
        month[i][4] = 30;
        month[i][5] = 31;
        month[i][6] = 30;
        month[i][7] = 31;
        month[i][8] = 31;
        month[i][9] = 30;
        month[i][10] = 31;
        month[i][11] = 30;
        month[i][12] = 31;                      
    }
    month[1][2] = 29;
}

int getDays(int year1,int m1,int d1,int year2,int m2,int d2){
    
    //先求年
    int days = 0;
    if(year1+1 < year2)
        for(int i = year1 +1;i<year2;i++){
            int leap = isLeapYear(i);
            for(int j = 1;j<=12;j++)
                days += month[leap][j];
        }
    
    if(year1+1 <= year2){
        //再求剩下的月
        for(int i = m1;i<=12;i++) {
            days += month[isLeapYear(year1)][i];
        }
        
        for(int i = 1;i<=m2;i++){
            days += month[isLeapYear(year2)][i];
        }
        
        //去掉多餘的
        days -= d1;
        days -= (month[isLeapYear(year2)][m2] - d2);        
    }else{
        for(int i = m1;i<=m2;i++){
            days += month[isLeapYear(year1)][i];    
        }
        days -= d1;
        days -= (month[isLeapYear(year2)][m2] - d2);                
    }

    return days;
}

int main(){
    getMonthDay();
    int y1,m1,d1,y2,m2,d2;
    cin>>y1>>m1>>d1>>y2>>m2>>d2;
    cout<<getDays(y1,m1,d1,y2,m2,d2);
    return 0;
}

問題2: 計算下一個日期

  • 已知該日期以及間隔時間,求下一個日期
  • 實際狀況根據題目要求,求出的日期可能減一
  • 沒有進行日期標準化
#include<bits/stdc++.h>
using namespace std;
int month[2][13]; //第一行12個數爲不是閏年的12個月的天數 

bool isLeapYear(int y){ //判斷是不是閏年
    if(y % 400 == 0)  return 1;
    if(y % 4 == 0 && y % 100 != 0)   return 1;
    return 0;
}
void getMonthDay(){
    for(int i = 0;i<=1;i++){
        month[i][1] = 31;
        month[i][2] = 28;
        month[i][3] = 31;
        month[i][4] = 30;
        month[i][5] = 31;
        month[i][6] = 30;
        month[i][7] = 31;
        month[i][8] = 31;
        month[i][9] = 30;
        month[i][10] = 31;
        month[i][11] = 30;
        month[i][12] = 31;                      
    }
    month[1][2] = 29;
}
void getNextDay(int year1,int m1,int d1,int days,int &year2,int &m2,int &d2){
    while(days--){
        d1++;
        if(d1 == month[isLeapYear(year1)][m1]){
            d1 = 1;
            m1 ++;
        }
        if(m1 == 12 + 1){
            m1 = 1;
            year1 ++;
        }
    }
    year2 = year1;
    m2 = m1;
    d2 = d1;
}
int main(){
    
    getMonthDay();
    int y1,m1,d1,y2,m2,d2,days;
    cin>>y1>>m1>>d1>>days;
    getNextDay(y1,m1,d1,days,y2,m2,d2);
    cout<<y2<<"-"<<m2<<"-"<<d2<<endl;
    return 0;
}

6. 二分法和排序

問題1: STL的二分函數

#include <algorithm>

//sort排序
int a[MAX];//對已經排好序的數組直接二分可使用stl

//升序數組返回第一個大於等於num的指針
lower_bound(a,a+MAX,num);

//升序數組返回第一個大於等於num的下標
lower_bound(a,a+MAX,num)-a;

//升序數組返回第一個大於num的指針
upper_bound(a,a+MAX,num);

//升序數組返回第一個大於num的下標
upper_bound(a,a+MAX,num)-a;

//升序數組返回num的個數
upper_bound(a,a+MAX,num)-lower_bound(a,a+MAX,num);

//降序數組返回第一個小於等於num的指針
lower_bound(a,a+MAX,num,greater<int>() );

問題2: 二分模板

浮點數

//要求解空間和要求的結果有線性關係(單增單減)
//定義精度 #define eps 1e-5
double solve(double L,double R)
{
    double left = L,right = R,mid;
    while(right-left>eps)//注意精度
    {
        mid = left+(right-left)/2.0;
        if(結果大了) right =mid;//右不縮進
        else left = mid;//作不縮進
    }
    return mid;
}
//原文:https://blog.csdn.net/zongza/article/details/80908964

整數

//1 二分查找區間內某個數字的下標(存在且惟一),不存在返回-1:
int search(int l,int r,int x)
{
    int mid;
    while (l<=r)//惟一一個須要帶=的二分
    {
        mid=(l+r)>>1;
        if(a[mid]==x) return mid;//三分支存在且惟一
        if(a[mid]<x) l=mid+1;//左縮進
        else r=mid-1;//右縮進
    }
    return -1;
}

//2 查詢區間內<=x的最大值(有多個最大值時返回最靠右的座標):
int search(int l,int r,int x)
{
    int mid;
    while (l<r)
    {
        mid=(l+r+1)>>1;
        if(a[mid]<=x) l=mid; //左不縮進,若是查詢<x最大值直接改爲<便可
        else r=mid-1;//(由於是返回靠右的座標)右縮進
    }
    return l;
}

//3 查詢區間內>=x的最小值(有多個最小值時返回最靠左的座標)
int search(int l,int r,int x)
{
    int mid;
    while (l<r)
    {
        mid=(l+r)>>1;
        if(a[mid]>=x) r=mid;//右不縮進
        else l=mid+1;//(由於是返回靠左的座標)左縮進
    }
    return l;
}

問題3: 木材加工問題

總時間限制: 1000ms 內存限制: 65536kB

描述
木材廠有一些原木,如今想把這些木頭切割成一些長度相同的小段木頭,須要獲得的小段的數目是給定了。固然,咱們但願獲得的小段越長越好,你的任務是計算可以獲得的小段木頭的最大長度。
木頭長度的單位是釐米。原木的長度都是正整數,咱們要求切割獲得的小段木頭的長度也要求是正整數。

輸入
第一行是兩個正整數N和K(1 ≤ N ≤ 10000, 1 ≤ K ≤ 10000),N是原木的數目,K是須要獲得的小段的數目。
接下來的N行,每行有一個1到10000之間的正整數,表示一根原木的長度。
 
輸出
輸出可以切割獲得的小段的最大長度。若是連1釐米長的小段都切不出來,輸出"0"。

樣例輸入
3 7
232
124
456
    
樣例輸出
114
#include<bits/stdc++.h> 
using namespace std;
int a[10010];
int main(){
    int n,k;
    cin>>n>>k;
    int sum = 0;
    for(int i = 0;i<n;i++){
        cin>>a[i];
        sum+=a[i];
    }
    if(sum < k){
        cout<<"0"<<endl;
    }
    else if(sum == k){
        cout<<"1"<<endl;
    }
    else{
        int l = 1,r = sum/k;
        int maxlen = 0;
        while(l<r){
            int mid = (l+r)/2;
            //cout<<l<<"--" <<r<<"--" <<mid<<endl;
            int sumNode = 0;
            for(int i = 0;i<n;i++){
                sumNode += a[i]/mid;
            }
            if(sumNode >= k){ //能夠切割 
                l = mid+1;
                if(maxlen < mid){
                    maxlen = mid;
                }
            }else{
                r = mid;
            }
        }
        cout<<maxlen<<endl;
    }
    return 0;
}

問題4: 排序

#include<algorithm>
bool cmp();
sort(首地址,尾地址,cmp);
stable_sort(首地址,尾地址,cmp);//值相同的狀況下保證是穩定排序

//結構體類型的排序
vector<結構體> vt;
sort(vt.begin(),vt.end(),cmp);

//普通類型排序
int/char/double a[MAX];
sort(a,a+MAX,cmp);

//堆排序
//結構體類型的排序
struct cmp
{
    bool operator() (結構體 j1,結構體 j2)
    {return j1.num>j2.num?j1.num:j2.num;}
};


priority_queue<結構體,vector<結構體>, cmp >;
//普通類型的堆排序
priority_queue<int/char/double, vector<int/char/double>, greater<int/char/double> >;
priority_queue<int/char/double, vector<int/char/double>, less<int/char/double> >;

排序原理:連接:八大排序算法詳解

7. 其餘數學問題

問題1: 快速冪和矩陣快速冪

快速冪模板

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

ll quick_pow(int a,int m){
    ll ans = 1;
    while(m){
        if(m&1) ans *= a;
        m>>=1; 
        a*=a;
    }
    return ans;
}

int main(){
    cout<<quick_pow(2,10);
    return 0;
}

矩陣快速冪模板

//矩陣快速冪
#include<bits/stdc++.h>
#define N 2
using namespace std;
//求矩陣相乘,放於ans
int cal(int a[N][N],int ans[N][N]){
    int temp[N][N];
    for(int i = 0;i<N;i++){
        for(int j = 0;j<N;j++){
            temp[i][j] = 0;
            for(int k = 0;k<N;k++){
                temp[i][j] += a[i][k] * ans[k][j];
            }
        }
    }
    memcpy(ans,temp,sizeof(int)*N*N);
}

int main(){
    
    //a的n次方
    int n = 3;
    int a[N][N] = {1,0,0,1};
    int ans[N][N] = {1,0,0,1};
    //矩陣快速冪
    while(n!=0){
        if(n&1){
            cal(a,ans);
        }
        n>>=1;
        cal(a,a);
    }
    for(int i = 0;i<2;i++) {
        for(int j = 0;j<2;j++){
            cout<<ans[i][j]<<" ";
        }
        cout<<endl;
    }   

    return 0;
}

矩陣快速冪典型例題:

題目:壘篩子

    壘骰子
    
    賭聖atm晚年迷戀上了壘骰子,就是把骰子一個壘在另外一個上邊,不能歪歪扭扭,要壘成方柱體。
    通過長期觀察,atm 發現了穩定骰子的奧祕:有些數字的面貼着會互相排斥!
    咱們先來規範一下骰子:1 的對面是 4,2 的對面是 5,3 的對面是 6。
    假設有 m 組互斥現象,每組中的那兩個數字的面緊貼在一塊兒,骰子就不能穩定的壘起來。 
    atm想計算一下有多少種不一樣的可能的壘骰子方式。
    兩種壘骰子方式相同,當且僅當這兩種方式中對應高度的骰子的對應數字的朝向都相同。
    因爲方案數可能過多,請輸出模 10^9 + 7 的結果。
    
    不要小看了 atm 的骰子數量哦~
    
    「輸入格式」
    第一行兩個整數 n m
    n表示骰子數目
    接下來 m 行,每行兩個整數 a b ,表示 a 和 b 數字不能緊貼在一塊兒。
    
    「輸出格式」
    一行一個數,表示答案模 10^9 + 7 的結果。
    
    「樣例輸入」1;
    2 1
    1 2
    
    「樣例輸出」
    544
    
    「數據範圍」
    對於 30% 的數據:n <= 5
    對於 60% 的數據:n <= 100
    對於 100% 的數據:0 < n <= 10^9, m <= 36
    
    
    資源約定:
    峯值內存消耗 < 256M
    CPU消耗  < 2000ms
    
    
    請嚴格按要求輸出,不要多此一舉地打印相似:「請您輸入...」 的多餘內容。
    
    全部代碼放在同一個源文件中,調試經過後,拷貝提交該源碼。
    
    注意: main函數須要返回0
    注意: 只使用ANSI C/ANSI C++ 標準,不要調用依賴於編譯環境或操做系統的特殊函數。
    注意: 全部依賴的函數必須明確地在源文件中 #include <xxx>, 不能經過工程設置而省略經常使用頭文件。
    
    提交時,注意選擇所指望的編譯器類型。

求解

求解

    #include<bits/stdc++.h>
    #define mod 1000000007
    #define N 7
    using namespace std;
    
    long long rule[7][7];
    void cal(long long  a[N][N],long long ans[N][N]){
        long long temp[N][N];
        for(int i = 1;i<N;i++){
            for(int j = 1;j<N;j++){
                temp[i][j] = 0;
                for(int k = 1;k<N;k++){
                    temp[i][j] = (temp[i][j] % mod + ((a[i][k] %mod * ans[k][j]%mod) %mod) %mod)%mod;
                }
            }
        }
        memcpy(ans,temp,sizeof(int)*N*N);
    }
    
    long long power(long long a,long long b){
        long long ans = 1;
        a = a %mod;
        while(b) {
            if(b&1){
                ans = (ans%mod * a%mod)%mod;
            }
            b>>=1;
            a = (a%mod*a%mod)%mod;
        }
        return ans;
    }
    int main(){
        int n,m;
        cin>>n>>m;
        int a,b;
        for(int i = 1;i<=6;i++){
            fill(rule[i]+1,rule[i]+7,1);
        }
    //  for(int i = 1; i <=6;i++){
    //      for(int j = 1;j<=6;j++){
    //          cout<<rule[i][j]<<" ";
    //      }
    //      cout<<endl;
    //  }
        for(int i = 0;i<m;i++){
            cin>>a>>b;
            rule[a][b] = rule[b][a] = 0;
        }
        long long nn = n-1;
        long long anss[N][N];
        for(int i = 1;i<N;i++){
            for(int j = 1;j<N;j++){
                anss[i][j] = 0;
                if(i == j) anss[i][j] = 1;
            }
        }
        while(nn){
            if(nn&1){
                cal(rule,anss);
            }
            nn>>=1;
            cal(rule,rule);
        }
        int ans = 0;
        for(int i =1;i<=6;i++){
            for(int j = 1; j<=6;j++){
                ans = (ans % mod + anss[i][j] %mod) %mod;
            }
        }
        cout<<(ans*power(4,n)) %mod <<endl;
        return 0;
    }

問題2: 前綴和、區間和

[佔坑,後補]

相關文章
相關標籤/搜索