2021-TKK-ICPC Summer Training Camp Round #2 题解

如果代码有bug,或数据有异常,欢迎私聊。

1:局长抓卷王

解析

  • 算法标签:递归,暴力,对标cf 1500

我们遇到max的时候 后面必须有两个元素,当其中一个元素是max的时候,又要再向后找两个元素,因此考虑递归解决,最后检查一下下标是否刚好停留在最后一个位置,直接看代码就能理解了。

#include<bits/stdc++.h>
#include<vector>
typedef long long ll;
using namespace std;
vector<string>v;
string res;
int le;
int idx;
void f(){
    if(idx>=le)return;
    if(v[idx]=="max"){
        res+="max(";
        idx++;
        f();
        res+=",";
        idx++;
        f();
        res+=")";
    }
    else{
        res+=v[idx];
    }
}
//标程
void solve(){
    v.clear();
    res="";
    idx=0;
    int n;
    cin>>n;
    string s;
    while(n--){
        cin>>s;
        v.push_back(s);
    }
    le=v.size();
    f();
    if(idx==le-1)cout<<res<<";"<<endl;
    else cout<<"NO"<<endl;
}

//标程
void get_ans(){
    int t ;
    cin>>t;
    while(t--){
        solve();
    }
}

int main() {
    get_ans();
    return 0;
}

2:CCPC

解析

  • 算法标签:二分+贪心,对标cf 1800

由于直接求最大值的话我们考虑的情况太多,因此不好直接求解。

假设我们已知“CCPC”个数的值为\(mid\)​,我们能不能证明这个字符串至少有\(mid\)​个“CCPC”?

我们设置4个变量\(a1,a2,a3,a4\)​​,这个变量用于记录4个状态。

\(mid\)​代表假设的"CCPC"的个数。

\(a1\)代表的是子序列C的个数,这个C作为子序列CCPC的前缀

\(a2\)​代表的是子序列CC的个数,这个CC作为子序列CCPC的前缀

\(a3\)​代表的子序列是CCP的个数,这个CCP作为子序列CCPC的前缀

\(a4\)​​​代表的子序列是CCPC的个数,代表子序列CCPC

我们可以发现,上面5个变量状态按顺序转移\(mid->a1->a2->a3->a4\)​​​​

我们现在探讨一下优先权的问题,先给出结论。

  1. 当前字符为C时,以下状态的转移优先权依次递减。

    \(a1->a2\)​​​

    \(mid->a1\)

    \(a_3->a_4\)

    -----分割线------

    • \(a1->a2\)

    如果\(a1\)​​​​有值,当前字符为"C",优先转移到\(a2\)​​​​因为如果\(a2\)​​​状态出现晚了,遇到"P"就会错过​。

    • \(mid->a1\)

    如果没有第一个C,即\(a1\)没值,优先去拓展最开头的那个C,即mid向\(a1\)转移。因为如果影响到\(a1\)值的增加就会影响到\(a2\)值得增加,从而错过\(p\)​。

    • \(a_3->a_4\)

    添加最后一个C的优先权最低。

  2. 当前字符为P时,直接讨论下类状态的转移。

    \(a2->a3\)

所以,我们已知“CCPC”个数的值为\(mid\),我们可以证明这个字符串至少有\(mid\)个“CCPC”。

假设字符串s里,”CCPC"最多出现的次数为m,那么我们check的mid<=m时,结果都是成立的,故出现次数具有单调性。因此考虑对答案进行二分。

标程

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
bool check(char s[],int mid,int n){// check mid ccpc
    int x=mid;
    int a1=0,a2=0,a3=0,a4=0;
    for(int i=0;s[i];i++){
        if(s[i]=='C'){
            if(a1)a1--,a2++;
            else if(mid)a1++,mid--;
            else if(a3)a3--,a4++;

        }
        else if(s[i]=='P'){
            if(a2)a2--,a3++;
        }
    }
    return a4>=x;
}
//标程
void solve(){
    char s[100005];
    scanf("%s",s);
    int n=strlen(s);
    int l=0,r=n/4;
    while(l<r){
        int mid=(l+r+1)>>1;
        check(s,mid,n)?l=mid:r=mid-1;
    }
    printf("%d\n",l);
}

//标程
void get_ans(){
    int t ;
    cin>>t;
    while(t--){
        solve();
    }
}

int main() {
    get_ans();
    return 0;
}   

3:消灭珠子

解析

算法标签:数学,对标cf 1000

结论:最大值为\(\frac{3n}{2}\)​​​,证明如下。​

证明

设:\(n\)​为双方珠子的个数,\(k\)​为先手第一次消灭的珠子的个数。

第一回合:先手第一次消灭了后手\(k\)个珠子

第二回合:后手只剩下\(n-k\)个珠子,因此最大只能消灭\(n-k\)个珠子。

第三回合:此时先手只剩下\(k\)​个珠子,最大能消灭对方k个珠子,但是如果后手的珠子\(n-k<=k\)​个,我们就只能消灭\(n-k\)​个。

故分类讨论

  1. \(n-k<=k\),​​​​​即\(\frac{n}{2}<=k\)​​​​。

    最大得分为\(res\)\(res=k+n-k+n-k=2n-k(\frac{n}{2}<=k)\)

    最大值当且仅当\(k=\frac{n}{2}\)\(res=2n-\frac{n}{2}\)

  2. \(n-k>=k\),即\(\frac{n}{2}>=k\)​。

    最大得分为\(res\)​​​,\(res=k+n-k+k=n+k(\frac{n}{2}>=k)\)​​​​

    最大值当且仅当\(k=\frac{n}{2}\)​​,\(res=n+\frac{n}{2}\)

考虑n为奇数,可分别带入n+1,n-1,由于珠子个数不能为半个,向下取整,最大值不变,证毕。

标程

    void solve(){
        int x;
        scanf("%d",&x);
        printf("%d\n",x+(x/2));
    }
posted @ 2021-08-06 22:07  Fanxuwei  阅读(148)  评论(0编辑  收藏  举报