1. 程式人生 > 其它 >Codeforces Round #787 (Div. 3) 題解

Codeforces Round #787 (Div. 3) 題解

目錄

A - Food for Animals

判一下 \(a+c>=x,b+c>=y,a+b+c>=x+y\) 即可。

aaaaaaaaaaacccccccccccbbbbbbbbbb
xxxxxxxxxxxxx.....yyyyyyyyyyyyyy

大概就是這樣的分配方式,感性理解一下正確性。

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

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin>>T;
    while(T--){
        int a,b,c,x,y;
        cin>>a>>b>>c>>x>>y;
        cout<<(a+c>=x&&b+c>=y&&a+b+c>=x+y?"YES\n":"NO\n");
    }

    return 0;
}

B - Make It Increasing

貪心地從後往前遍歷一遍陣列,在當前位置的值不小於後一個位置的值的時候不斷除以二。

當非序列第一個元素的值為 \(0\) 的時候沒有合法答案。

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

void mian(){
    int n;
    cin>>n;
    vector<int> a(n);
    for(int &x:a)cin>>x;
    int ans=0;
    for(int i=a.size()-2;i>=0;i--){
        if(a[i+1]==0){
            cout<<"-1\n";
            return;
        }
        while(a[i]>=a[i+1]){
            a[i]/=2;
            ans++;
        }
    }
    cout<<ans<<'\n';
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin>>T;
    while(T--)mian();

    return 0;
}

C - Detective Task

如果能確定某一個是小偷,其他的就都不用懷疑了(為什麼不顯式說明這個啊啊啊),所以首先排掉開頭說 \(0\) 或者末尾說 \(1\) 的。

之後看到如果一個人說了 \(0\) 但後面有人說 \(1\) 他就是嫌疑人,反之亦然。這一步可能會有 \(1\)\(2\) 個嫌疑人(\(2\) 個有且僅有一種情況,就是一對 \(0\)\(1\) 反了,看程式碼),有就能直接輸出不用做下一步了。

現在所有人說的話都是合法的,所以可以懷疑最後一個 \(1\) 到第一個 \(0\) 之間的所有人。

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

int n,p0[200005],s1[200005];
char s[200005];

void mian(){
    cin>>s+1;
    n=strlen(s+1);
    if(s[1]=='0'||s[n]=='1'){
        cout<<"1\n";
        return;
    }
    for(int i=1;i<=n;i++)p0[i]=p0[i-1]||s[i]=='0';
    s1[n+1]=0;
    for(int i=n;i>=1;i--)s1[i]=s1[i+1]||s[i]=='1';
    int th=0;
    for(int i=1;i<=n;i++)if(s[i]=='0'&&s1[i])th++;
    for(int i=1;i<=n;i++)if(s[i]=='1'&&p0[i])th++;
    if(th){
        cout<<th<<'\n';
        return;
    }
    int l=1;
    while(s1[l+1])l++;
    int r=n;
    while(p0[r-1])r--;
    cout<<r-l+1<<'\n';
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin>>T;
    while(T--)mian();

    return 0;
}

D - Vertical Paths

你用一些路徑合併出這個樹,本質就是一個有 \(C\) 個兒子(下掛結點)的結點,要斷開它連向兒子其中的的 \(C-1\) 條邊。然後怎麼斷都不會影響答案,所以 DFS 就行了。

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

int n,rt;
vector<int> g[200005];
vector<stack<int>> ans;

int dfs(int x){
    if(g[x].empty()){
        ans.emplace_back();
        ans.back().emplace(x);
        return ans.size()-1;
    }else{
        bool f=1;
        int res=0;
        for(int y:g[x])if(f){
            ans[res=dfs(y)].emplace(x);
            f=0;
        }else{
            dfs(y);
        }
        return res;
    }
}

void mian(){
    for(int i=1;i<=n;i++)g[i].clear();
    ans.clear();
    cin>>n;
    for(int i=1;i<=n;i++){
        int fa;
        cin>>fa;
        if(fa==i)rt=i;
        else g[fa].emplace_back(i);
    }
    dfs(rt);
    cout<<ans.size()<<'\n';
    for(auto &s:ans){
        cout<<s.size()<<'\n';
        while(!s.empty()){
            cout<<s.top()<<' ';
            s.pop();
        }
        cout<<'\n';
    }
    cout<<'\n';
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin>>T;
    while(T--)mian();

    return 0;
}

E - Replace With the Previous, Minimize

考慮字典序最小,就是先最小化第一個字元,然後第二個,再第三個,以此類推。

然後就從頭到尾這麼跑一遍,每次不斷嘗試縮小當前位置的字元,直到當前字元變成 a(顯然沒有縮小 a 的必要對吧)。

注意到你有一個 b 要變成 a,和一個 c 要變成 a,你可以先 c 變成 b,再 b 變成 a(和 b 與 c 在字串中的位置無關),所以你可以記錄一下每一個字元有沒有變小,就不用重複做變小的操作了。

所以其實 k 最大 25,但是可能比較提示你所以題面就沒寫。

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

int n,k;
string s;
bool p[26];

void mian(){
    cin>>n>>k;
    cin>>s;
    memset(p,0,sizeof(p));
    for(char c:s){
        if(c!='a'){
            int i=c-'a';
            while(k&&i&&!p[i]){
                p[i--]=1;
                k--;
            }
        }
    }
    for(char &c:s){
        while(p[c-'a'])c--;
    }
    cout<<s<<'\n';
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin>>T;
    while(T--)mian();

    return 0;
}

F - Vlad and Unfinished Business

把起點和終點之間的路徑搞出來,然後一路上有要訪問的點的分支就進去 DFS 一遍。

我們把起點和終點和要訪問的點都塗黑,發現答案就是這樣的:

把包含所有黑點的最小的一個子樹的邊數叫做 \(C\),起點和終點之間距離叫做 \(D\),答案就是 \(2 \times C - D\),表示起點到終點的路徑上每個有黑點的分支都要進去 DFS 一次,然後起點到終點的路徑多算了一次所以減去。

這個 \(C\) 你可以維護所有黑點的集合,每次把深度最大的黑點往上移一次,記錄經過的邊,最後所有黑點會聚到一起,此時所有經過的邊總數就是 \(C\)

大概複雜度是 \(O(n \log_2 n)\) 的。(算 \(C\) 的時候達到這個複雜度,\(D\) 應該 BFS 或 DFS 都行)

程式碼我就只貼連結了,因為計算 \(C\) 的方式寫複雜了。https://codeforces.com/contest/1675/submission/155959860

G - Sorting Pancakes

一眼 DP。

考慮固定一個字首的煎餅數量,每次列舉下一個位置的煎餅數量,同時為了保證列舉完全部的煎餅後,總數對的,我們再記錄一個字首煎餅數量總和。

那麼狀態就是 \(f_{i,h,s}\)\(i\) 表示字首長度,\(h\) 表示最後一個煎餅堆的高度,\(s\) 表示字首煎餅數量和。

然後就是計算代價了,我們考慮在第 \(i\)\(i+1\) 堆煎餅之間移動了 \(c_i\) 次煎餅,那麼答案就是 \(\sum c_i\)

於是你就記 \(b_i = \sum_{j=1}^{j<=i} a_j\),那麼:

\[f_{i,h,s} = \min f_{i-1,ph,s-h} + |s - b_i| \]

最後求 \(answer = \min f_{n,h,m}\)

看上去是 \(O(n m^3)\) 不可過的,實際上合法的狀態中 \(i \times h \le s\),也就是說所有位置最大的 \(h\) 的和大約是 \(m \log_2 m\) 級別的(證明搜調和級數),然後就成為了 \(O(n m^2 \log m)\)不是很懂這裡的對數的底數是啥,反正不小於 \(2\) 就是了。

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

const int inf=0x3f3f3f3f;
int n,m,a[255],b[255],f[255][255][255];

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        b[i]=b[i-1]+a[i];
    }
    memset(f,0x3f,sizeof(f));
    f[0][m][0]=0;
    int ans=inf;
    for(int i=1;i<=n;i++){
        for(int ph=m;ph>=0;ph--){
            for(int ps=m;ps>=ph*(i-1);ps--)if(f[i-1][ph][ps]!=inf){
                for(int h=min(m-ps,ph);h>=0;h--){
                    f[i][h][ps+h]=min(f[i][h][ps+h],f[i-1][ph][ps]+abs(ps+h-b[i]));
                    if(i==n&&ps+h==m)ans=min(ans,f[i][h][ps+h]);
                }
            }
        }
    }
    cout<<ans<<'\n';

    return 0;
}

瞎扯

感覺 E < D < C。

最後一題做了 19 分鐘比同學慢巨大多,復健大失敗。