1. 程式人生 > 實用技巧 >Codeforces Round #656 (Div. 3) 題解

Codeforces Round #656 (Div. 3) 題解

A. Three Pairwise Maximums #構造

題目連結

題意

給定三個正整數\(x,y,z\),要求找出正整數\(a,b,c\),滿足\(x=max(a,b), y=max(a,c),z=max(b,c)\)

分析

我們可以先將\(x,y,z\)降序排序得到\(z\leq y\leq x\)。由於\(x\)\(a,b,c\)三者最值,且通過三個關係中\(x\)所代表的數字一定出現兩次,可以推斷出,\(y=x\),如果最值沒有出現兩次,說明我們不可能構造出\(a,b,c\)

既然題目讓我們構造,構造且要滿足\(max(a,b)=max(a,c)\),那麼不妨設\(a\)為最大值,即\(a=x=y\)

。由於\(z\)能推出\(b,c\)關係,我們又不妨將\(b\)賦為\(z\)(三值中第二大)。三者最小值不易準確確定,直接將\(c\)賦值為1,作為三者中的最小值,十分穩妥。

#include <algorithm>
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 5;
const int MOD = 1e4 + 7;
int n, m, q;
int main(){
    scanf("%d", &q);
    while(q--){
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        if(x<y) swap(x,y);
        if(x<z) swap(x,z);
        if(y<z) swap(y,z); //排序一下
        if(x != y) {
            printf("NO\n");
            continue;
        }
        else{
            printf("YES\n");
            printf("%d %d %d\n", x, z, 1);
        }
    }
    return 0;
}

C. Make It Good #貪心

題目連結

題意

“好陣列”定義為,一個數組\(b\),我們只從該陣列最左邊,或者最右邊,將所有元素依次取出並放到\(c\)陣列,該\(c\)陣列是個不降序列,則稱\(b\)陣列為“好陣列”。

現給定陣列\(a\),你需要從陣列\(a\)前幾個元素刪去,得到一個“好陣列”。現要你求出刪除的前幾個元素至少需要多少。比如陣列a={4 3 3 8 4 5 2},你至少需要刪除前面4個元素,得到的陣列b={4 5 2}才是個好陣列。

分析

不難分析,“好陣列”中的元素關係必然是\(b_1 \leq b2 \leq ... \leq b_{mx} \geq ... \geq b_k\)

,其中\(b_{mx}\)為陣列\(b\)中最大值(不一定是陣列\(a\)中最大值),簡單來說,我們就是要從\(a\)陣列中找到“山峰”。

由於我們只能刪除陣列\(a\)中前面幾個元素,因而後面元素受到的影響很少,於是我們用一右指標\(hi\),從陣列\(a\)的後面往前面遍歷,只要\(a[hi-1]\geq a[hi]\)就往前進(相當於走上坡),一旦遇到\(a[hi-1] \leq a[hi]\)說明到達極值點。我們再繼續往前面(往陣列左端)遍歷,只要\(a[hi-1]\leq a[hi]\)就往前進(相當於走下坡),一旦遇到\(a[hi-1] \geq a[hi]\)說明到達我們到達山底,即\(a[1, ...hi-1]\)的元素都需要刪去,\(a[hi, n]\)方為好陣列。敲程式碼時注意下邊界。

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const int MAXN = 2e5 + 5;
int n, m, q;
int a[MAXN];
int main(){
    scanf("%d", &q);
    while(q--){
        scanf("%d", &n);
        for(int i =1 ; i <= n; i++) scanf("%d", &a[i]);
        int hi = n;
        while(hi >= 1 && a[hi-1] >= a[hi]) hi--; //走上坡
        while(hi >= 1 && a[hi - 1] <= a[hi]) hi--; //走下坡
        if(hi - 1 >= 0) printf("%d\n", hi - 1);
        else printf("0\n");
    }
    return 0;
}

D. a-Good String #暴力深搜 #分治

題目連結

題意

\(a\)-好串”定義為,不小於一個元素的串,滿足以下其中一個條件即可:

  • 若長度為1,且包含的字元恰好為\(a\)
  • 若長度大於1,且它的左半部分所有字元均為\(a\),而另一半的串是“\(a+1\)-好串”(\(a+1\)字元,即為字元a在字母表中下一個字元)
  • 若長度大於1,且它的右半部分所有字元均為\(a\),而另一半的串是“\(a+1\)-好串”

\(t(\leq2\times 10^{5})\)組詢問,給定長度為\(n(其中\sum n \leq 2\times 10^{5})\)串,你可以對串中任意字元轉變為其他任意字元,每個字元的轉變作為一次操作,現要你求出將串轉變為“\(a-\)好串”的最少次數

分析

先將串中所有種字元進行字首和統計,然後對於串的前後部分暴力搜尋一下即可,因為遞迴下來,大約有\(logn\)種子串,層數大約為十多層,\(O(nlogn)\)複雜度能夠通過\(t\)組詢問。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <string>
using namespace std;
typedef long long ll;
const int MAXN = 150000;
int q, n, sum[30][MAXN];
string str;
int dfs(int lo, int hi, int cur){
    int mid = (lo + hi) >> 1, len = hi - lo + 1;
    if(len <= 1) //邊界情況
        return len - sum[cur][hi] + sum[cur][hi - 1];
    int pre = (len >> 1) - sum[cur][mid] + sum[cur][lo - 1];
    int lat = (len >> 1) - sum[cur][hi] + sum[cur][mid];
    int res = min(dfs(lo, mid, cur+1) + lat, dfs(mid+1, hi, cur+1) + pre);
    return res;
}
void preCal(){
    for (int i = 1; i <= 26; i++){
        for (int j = 0; j < str.length(); j++){
            sum[i][j + 1] = sum[i][j];
            if(str[j] - 'a' + 1 == i)
                sum[i][j + 1]++;
        }
    }
}
int main(){
    scanf("%d", &q);
    while(q--){
        scanf("%d", &n); cin >> str;
        preCal();
        printf("%d\n", dfs(1, n, 1));
    }  
}

E. Directing Edges #拓撲排序

題目連結

題意

給定一個圖,裡面既包含有向邊,也包含無向邊,並保證初始情況下的圖不存在平行邊與自環,現要你將圖中所有無向邊改變為有向邊(方向自定義),使得圖不存在任何一個有向環。如果無法保證不出現有向環,輸出"NO"。否則需要你輸出所有邊的連線資訊。

分析

容易知道,初始情況下的無向邊並不會影響圖是否存在有向環,應關注於當前的所有有向邊所組成的圖。如何判斷是否存在有向環,利用拓撲排序演算法即可,但別忘了要將拓撲序列存下來,這是用於判斷無向邊指向的方向。如果一條無向邊中的頂點\(a\)的拓撲序小於頂點\(b\),那麼\(a\)應該指向\(b\),反之,讓\(b\)指向\(a\)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <stack>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
const int MAXN = 2e5+5;
int q, n, m;
struct Edge{ //用於輸出
    int u, v;
} E[MAXN << 1];
struct BuildEdge{ //用於拓撲排序
    int to, nextNbr;
} BE[MAXN << 1];
int H[MAXN], tot = 0, InD[MAXN], num = 0;
int ans[MAXN];
void addEdge(int u, int v){
    tot++;
    BE[tot] = {v, H[u]};
    H[u] = tot;
}
bool ToSort(){
    queue<int> myque;
    int res = 0;
    for(int i = 1; i <= n; i++){
        if(InD[i] == 0){
            myque.push(i);
            ans[i] = ++res; //記錄拓撲序
        }  
    }
    while(!myque.empty()){
        int cur = myque.front();
        myque.pop();
        for(int i = H[cur]; i >= 0; i = BE[i].nextNbr){
            int v = BE[i].to; InD[v]--;
            if(InD[v] == 0){
                myque.push(v);
                ans[v] = ++res; //記錄拓撲序
            }
        }
    }
    return (res != n); //如果不相等,說明存在有向環
}
void Init(){ //初始化
    memset(H, -1, sizeof(H));
    memset(ans, 0, sizeof(ans));
    memset(InD, 0, sizeof(InD));
    tot = num = 0;
    for (int i = 1; i <= m; i++) BE[i] = {-1, -1};
}
int main(){
    scanf("%d", &q);
    while(q--){
        scanf("%d%d", &n, &m);
        Init();
        for (int i = 1, u, v, opt; i <= m; i++){
            scanf("%d%d%d", &opt, &u, &v);
            E[++num] = {u, v};
            if(opt == 1){ //有向邊建圖
                addEdge(u, v);
                InD[v]++;
            }
        }
        bool isLoop = ToSort();
        if(isLoop) printf("NO\n");
        else{
            printf("YES\n");
            for(int i = 1; i <= m; i++){
                int u = E[i].u, v = E[i].v;
                if(ans[u] < ans[v]) printf("%d %d\n", u, v);
                else printf("%d %d\n", v, u);
            }
        }
    }
    return 0;
}