CodeForces - 1330B - B - Dreamoon Likes Permutations 題解+各種錯誤解析

前序

由於我個人問題對本題做一個題解記錄一下
在這裏插入圖片描述
題目在輸出的時候做出了一個 l1l1 and l2l2 長度範圍的限定,這對我第一的代碼中的 for 循環範圍產生了一定設定上的偏差,從左端上看 l1l1 and l2l2 是必定滿足 1l1l21≤l1,l2,由於這個條件所以左端是不可能達到 nn 這個位置的所以範圍應該是 1l1,l2n11≤l1,l2≤n-1

題目鏈接

B-Dreamoon Likes Permutation

題目類型

思維、模擬

題意簡述

給你一段長度爲 nn 的序列,要求你將該序列分爲兩個部分,分別爲序列 p1p1p2p2,長度分別爲 l1l1l2l2,這兩個序列分別滿足滿足序列中的元素可以通過排列變成連續序列,且元素剛好只出現一次,問能分成多少種,輸出每種 l1l1l2l2 的值。

題解

first(TLE-3)

我原先想到的是set解法,將所有的長度枚舉一遍,存儲在 set 容器中,因爲set容器中去重有序的性質,如果最後一個元素與長度不相符,就說明有重複出現是不符合的,或者是元素不是連續的。

時間複雜度

  • 每個長度需要枚舉一遍也就是O(n)
  • 每個次分成的兩個序列都需要插入set也就是 O(n)
  • 每次set中count查詢出現次數都是 o(log n),個人覺得卡的就是查詢
  • 每次還要對set進行清除,set.clear()底層的實現就是通過earse刪除,時間複雜度O(n)
    最後總的時間複雜度爲O(nlogn)

代碼

這裏給出代碼只是一個思路的參考,是TLE的,要注意,應該是可以用 unordered_map 去進行一個優化,但我是懶了

const int maxn = 2e5+50;
set<int> ap;
int a[maxn];
vector<PII> ans;
int main(){
    int t;
    for(scanf("%d",&t);t;t--){
        ans.clear();
        int n; RD(n);
        REP(i, n) RD(a[i]);
        for(int len = 1; len <= n - 1; len++){
            ap.clear();
            bool judge = true;
            //cout << "len :" << len << '\n';
            for(int i = 0; i < len; ++i){
                ap.insert(a[i]);
            }
            int tmp;
            tmp = *(--(ap.end()));
            if (ap.size() != len || ap.size() != tmp) continue;
            ap.clear();
            for(int i = len; i < n; i++){
                ap.insert(a[i]);
            }
            tmp = *(--(ap.end()));
            if (ap.size() != n - len || ap.size() != tmp) continue;
            ans.PB(MP(len,n - len));
        }
        if (ans.size() == 0) {
            cout << 0 << '\n';
        }
        else{
            cout << ans.size() << '\n';
            for(PII v: ans){
                cout << v.fi << " " << v.se << '\n';
            }
        }
    }   
}

second

官方解答

在這裏插入圖片描述
設ma爲a中所有元素的最大值。如果可以將a序列分解爲兩個排列p1p1 and p2p2,那麼ma必須與l1l1l2l2中最大的那一個值相等。
所以最多有兩種情況:

  1. l1=ma,l2=nmal1 = ma, l2 = n - ma
  2. l2=ma,l1=nmal2 = ma, l1 = n - ma

我們可以分別檢查這兩種情況,時間複雜度爲O(n)。

代碼

寫法一(TLE)

//判斷是否訪問過
const int MAXN = 2e5+50;
int used[MAXN];
bool judge_used(int a[], int n){
    

    REP(i, n+1) used[i] = 0;
    REP(i, n) used[a[i]] = 1;
    FOR_1(i, 1, n) if (!used[i]){return false;}
    return true;

}

vector<PII> ans;
const int maxn = 2e5+50;
int a[maxn];

int main(){
    int t;
    for(scanf("%d", &t); t; t--){
        ans.clear();
        int n, ma = 0; RD(n);
        REP(i, n) { RD(a[i]);  ma = max(ma, a[i]);}
        for(int len1 = 1; len1 <= n - 1; ++len1){
            if (judge_used(a, len1) && judge_used(a + len1, n - len1)){
                if (ma == len1 || ma == n - len1) ans.PB(MP(len1, n - len1));
            }
        }
        cout << ans.size() << '\n';
        for(PII v : ans){
            cout << v.fi << " " << v.se << '\n';
        }
    }
}

超時的點主要是枚舉所有長度的時候超時,所以應該直接判斷去做,這裏枚舉長度所造成的時間複雜度是O(n2n^2)超時是必然的

正確代碼

//判斷是否訪問過
const int MAXN = 2e5+50;
int a[MAXN];
int used[MAXN];
int ans[MAXN][2];
int ans_cnt;
bool judge_used(int a[], int n){
    REP(i, n+1) used[i] = 0;
    REP(i, n) used[a[i]] = 1;
    FOR_1(i, 1, n) if (!used[i]){return false;}
    return true;

}
bool judge(int len1, int n){
    return judge_used(a, len1) && judge_used(a + len1, n - len1);
}

int main(){
    int t;
    for(scanf("%d", &t); t; t--){
        ans_cnt = 0;
        int n, ma = 0; RD(n);
        REP(i, n) { RD(a[i]);  ma = max(ma, a[i]);}
        if(judge(n - ma,n)) {
            ans[ans_cnt][0] = n - ma;
            ans[ans_cnt++][1] = ma;
        }
        if(ma * 2 != n && judge(ma,n)) {
            ans[ans_cnt][0] = ma;
            ans[ans_cnt++][1] = n - ma;
        }
        printf("%d\n", ans_cnt);
        for(int i = 0; i < ans_cnt; i++) {
            printf("%d %d\n", ans[i][0], ans[i][1]);
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章