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 分钟比同学慢巨大多,复健大失败。

posted @ 2022-05-06 14:39  筵阑  阅读(422)  评论(2)    收藏  举报