第八週程序設計課解題報告

第八週程序設計課解題報告
    下週期末考試啦,所以提前發佈解題報告,留點時間給大家複習吧~
    在寫報告前:這周的題略微有點boss的感覺了,因爲不再是單純的暴力可行了,需要你去想如何優化才行了呢,其實這個也是日後程序設計的核心問題之一。很多問題直接暴力寫的話很容易,但是往往會消耗(非常多)^n的時間或者空間。那麼我們就要去找到題目潛在的規律去優化他,減少不必要的花銷。如果不願意開動腦子的話,當上ceo,迎娶白富美(高富帥)?哼哼,還是安心當一個只會打字的“碼農”吧。
    
那麼進入正題~
1000. Easy Program
題目大意:給出n個數,然後有m個詢問,每次問原來的n個數當中第d個數的值是多少。
解:這個題目其實就是對數組的裸應用,真的不難。不過貌似有些同學是理解錯了題意才跪的。我們讀入詢問d後直接輸出a[d]即可(當然我的存儲方式是[1,n]而非[0,n-1],使用後者的同學注意下標減一) 
程序:
#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>

using namespace std;

#define SQR(x) ((x)*(x))
#define MIN(a,b) ((a<b)?a:b)
#define MAX(a,b) ((a>b)?a:b)

int main(){
    int n, m, a[1111];
    cin>>n>>m;
    for (int i=1; i<=n; i++) cin>>a[i];
    for (int i=1; i<=m; i++){
        int tmp;
        cin>>tmp;
        cout << a[tmp] << endl;
    }
    return 0;
}                        

1001. Reversal
題目大意:首先給出n個數,然後給出m個操作,每次操作給出區間[x,y],假設原來n個數是放置在[1,n]上的,那麼每次都把[x,y]這個區間進行翻轉操作。最後再輸出經過m次操作以後[1,n]上的數的數值。
解:不斷進行交換操作就好了,但是我發現很多同學喜歡把交換的區間映射到[1,y-x+1]上,然後再進行翻轉,其實這樣不是不行,時間也不會慢,但是會使得你的下標比較容易寫錯。其實交換步驟變成x與y交換,然後x--,y++,直到x>y就好了。
哦對了,還有輸出格式的問題,注意最後一個數後面是【沒有空格】的,同時記得【換行】! 


程序:
#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>

using namespace std;

#define SQR(x) ((x)*(x))
#define MIN(a,b) ((a<b)?a:b)
#define MAX(a,b) ((a>b)?a:b)

int main(){
    int n, m, a[1111];
    cin>>n>>m;
    for (int i=1; i<=n; i++) cin>>a[i];
    for (int i=1; i<=m; i++){
        int x, y, kok;
        cin>>x>>y;
        while (x<=y){
            kok=a[x]; a[x]=a[y]; a[y]=kok;
            x++; y--;
        }
    }
    for (int i=1; i<=n; i++){
        cout << a[i];
        if (i<n) cout <<' ';
        else cout << endl;
    }
    return 0;
}                                  

1002. The Highest Guy
題目大意:給出n個數,再給m個詢問,每次詢問第b個人到第n個人中最高的人的身高(區間[b,n]上的最大數)
解:哼哼哼,這題很簡單啊輸入,然後每次找區間最大的就好了。我交,臥槽,怎麼tle了?
還記得我之前所講的tle如何查錯麼,算一算如果出數據的人滿懷惡意的話這題會怎麼出數據卡你呢?自然是讓你每次的詢問都花費時間最大就好了。每次都詢問1到n的最大值。而暴力尋找區間最大值的循環次數是區間長度(就是n),那麼加上m次詢問的話【時間複雜度】就是O(n*m),題目數據n,m,都去到10w,也就是說,最壞情況下,這題的複雜度達到100億,然而我們的計算機一秒大概跑1億次,而時間限制只有1秒。所以肯定會超時咯。

怎麼優化呢?m次詢問肯定少不了了,那麼每次詢問是否一定要n次呢?有沒有非常神的辦法呢?對於1個數,那麼我們肯定1眼看出來了吧。那麼對於多個數,能否做到1眼看出來呢?

注意題目有一個特殊性質,詢問的是第b個人到n個人的最大值。那麼對於b-1呢?是不是會有重複的東西? 

當詢問爲b==n時,我們一眼望出來了,對於b==n-1時,是不是可以看兩眼?對於b==n-2,望3眼?等等!我在看b==n-1時,如果把[n-1, n]的最大值保存下來放到help[n-1]裏,是不是不用看[n-1,n]的那兩眼了。好的,我們看了a[n-2],help[n-1]後,把最大值放入help[n-2]裏,在觀察[n-3,n]的時候,是不是只需要觀察a[n-3]和help[n-2]就好了?如此類推,我們要完成整個help數組的記錄,只需要2*n-1次即可了?實際上help[i]的意義就是記錄了從第 i個數到第n個數的最大值。所以我們讀入n後進行預處理得出help數組,詢問的時候直接用上就好了,每次詢問的處理顯然是O(1)的。

那麼原程序的時間複雜度已經下降到O(n+m)了(爲啥少了東西?因爲時間複雜度忽略常數)。這顯然可以滿足題目的要求了
對了,輸入輸出的次數多的時候記得不要用cin和cout,這兩個東西非常非常非常非常非常慢!
程序:
#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>

using namespace std;

#define SQR(x) ((x)*(x))
#define MIN(a,b) ((a<b)?a:b)
#define MAX(a,b) ((a>b)?a:b)

int a[111111], maxx[111111];

int main(){
    int n, m;
    cin>>n>>m;
    for (int i=1; i<=n; i++) scanf("%d", &a[i]);
    maxx[n]=a[n];
    for (int i=n-1; i; i--){
        maxx[i]=MAX(a[i], maxx[i+1]);

    }
    int tmp;
    for (int i=1; i<=m; i++){
        scanf("%d", &tmp);
        printf("%d\n", maxx[tmp]);
    }
    return 0;
}               

1003. Yaroslav and Permutations                  
題目大意:給出n個數,問是否能通過若干次交換,反正就是能夠把他變成相鄰兩個數不會相等即可。
解:很容易猜想到, 這個問題的關鍵是n個數中重複次數最多的數不要超過(n+1)/2個即可存在一種辦法把數字都隔開。所以問題的關鍵在於找出最多的數有多少個。排序後統計?其實不必,因爲觀察到題目中有性質n個數的數值都在1~1000內,所以我們開一個數組f,f[i]表示i這個數字出現了幾次即可。(這個辦法其實也叫基數排序)詳情看程序可能好理解一點。

程序:
#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>

using namespace std;

#define SQR(x) ((x)*(x))
#define MIN(a,b) ((a<b)?a:b)
#define MAX(a,b) ((a>b)?a:b)

int a[111111], maxx[111111], f[1111];

int main(){
    int n;
    scanf("%d", &n);
    memset(f, 0, sizeof(f));
    for (int i=1; i<=n; i++){
        int tmp;
        scanf("%d", &tmp);
        f[tmp]++;
    }
    bool flag=true;
    for (int i=1; i<=1000; i++) if (f[i]>(n+1)/2){
        flag=false; break;
    }
    if (flag) cout << "YES" << endl;
    else cout << "NO" << endl;
    return 0;
}         

 
1004. Sum                                                    
題目大意:給出n個數,每次詢問[x,y]這個區間內的所有數的和是多少。
解:這題爲啥會超時的分析可以參見第三題。我們考慮如何優化。這題是否能夠利用已有的信息使得詢問的時候不用重複觀察呢?我們先考慮如果詢問特殊化把,每次都會詢問[1,y],那麼自然可以用一個數組預處理出[1,y]的和。但是題目要求的是[x,y]怎麼辦?其實,詢問的區間是連續的,那麼[x,y]是否等價於[1,y]-[1,x-1]呢?

程序:
#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>

using namespace std;

#define SQR(x) ((x)*(x))
#define MIN(a,b) ((a<b)?a:b)
#define MAX(a,b) ((a>b)?a:b)

int a[111111], maxx[111111], f[1111];

int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i=1; i<=n; i++){
        scanf("%d", &a[i]);
        a[i]+=a[i-1];
    }
    a[0]=0;
    int x, y;
    for (int i=1; i<=m; i++){
        scanf("%d%d", &x, &y);
        printf("%d\n", a[y]-a[x-1]);
    }

    return 0;
}                               

最後想說兩段話:
1.不知道有多少同學有去聽週三晚林翰老師的信導課呢?在關於解題思路這一段,其實本週作業就有契合的地方。一是不必要詳細證明自己的猜想 ,這便是第四題,我們得到一個用常識判斷是對的策略,然而要數學證明就難了,有時候不必拘泥於這種問題。二是可以從小的數據出發去推出全局策略,我在第三題的優化步驟其實就是從小的出發,把問題特殊化,從區間爲1,2,3(長度)慢慢推出來,再總結能用於一般化的策略,最後就完成了優化了。
2.時候看自己寫的報告其實還是有不少錯誤的,如果有錯,歡迎各種方式指正,如果有疑問或者想進一步瞭解的知識點,歡迎以各種方式找我。能幫助到大家,纔是我寫這份解題報告的最大快樂:)
發佈了20 篇原創文章 · 獲贊 5 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章