wtpavssite

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

2026SMU Summer CF Round2题解

A. Game with Board

链接:https://codeforces.com/group/L9GOcnr1dm/contest/706137/problem/A

题意:

Alice和Bob进行游戏,一开始黑板上有n个全是1的数字,Alice先手,玩家可以选择至少两个相同的数字进行合并,然后在黑板上擦去选择的数字,写上选择的数字的和,当一个选手无法行动时,该玩家获胜

思路:

我们思考最简单的情况,如果当一个角色行动的时候只剩下了两个相同的数字比如,两个1,那么下一个行动的人只有赢,所以先手考虑只给对手留下两个相同的数字,那么先手一定赢。所以大部分情况是Alice赢,但是我们考虑n小于5的情况,无论怎么样Alice先手都无法取胜,因为Alice无法给Bob留下只剩两个相同的数字,因为在自己做出这个之前Bob已经因为无法行动而取胜了

代码:

点击查看代码
void solve(){
    int n;
    cin>>n;
    if(n<=4){
        cout<<"Bob"<<endl;
    }
    else{
        cout<<"Alice"<<endl;
    }
}

B. Fancy Coins

链接:https://codeforces.com/group/L9GOcnr1dm/contest/706137/problem/B

题意:

Monocarp准备买一个价值m的商品,他手上有两种硬币,一种是普通的硬币,一种是fancy coin,这两种硬币都有两个面额,一个是1元,一个是k(>=2)元。Monocarp只有a1和ak数量的普通硬币,问我们购买这个商品能用的最少的fancy coin的数量是多少?

思路:

要最小化fancy的数量我们尽量用普通的硬币,所以我们可以判断用现在有的普通的硬币可以买到多少价值的商品。用所有的普通硬币能买到的最大价值的商品是a1+akk.因为超过akk这个部分我们使用一元的硬币买的,所以这个范围内的所有的价值的商品我们都能通过少用一定数量的一元硬币来取到这个价值;那么我们接着考虑0到akk这个范围的价值的商品,在一个k的区间内,只要一元硬币的数量足够,我们就能取到这个区间所有的价值,所以我们判断a1的数量是否大于等于k,如果是那么0到a1+akk这个范围内的价值都能不使用fancy coin,如果a1的数量小于k那我们需要判断m%k是否小于等于a1,如果是,我们可以先用m/k枚k元硬币和m%k枚一元硬币来获得这个m价值;如果a1没有这个数量我们就使用fancy coin来补齐这个差距,数量为m%k-a1。讨论完a1+akk内的价值,我们考虑m大于这个区间。因为k大于等于2的原因,我们先用k元的fancy coin最快的接近m,数量为(m-a1-akk)/k,类似上面的讨论,我们用一元的fancy coin来使下界达到k,但是因为我们的价值使akk加上了a1,所以我们还可以减少前面一元的普通硬币的使用,使上界降到m元,但值得注意的是,k-(m-a1-akk)%k如果大于a1的话说明我们不能通过减少的方法来达到m元。分类讨论即可

代码:

点击查看代码
void solve(){
    ll m,k,a1,ak;
    cin>>m>>k>>a1>>ak;
    if(m<=a1+ak*k){
        if(m>=ak*k&&m<=a1+ak*k){
            cout<<0<<endl;
        }
        else{
            if(m%k<=a1){
                cout<<0<<endl;
            }
            else{
                cout<<m%k-a1<<endl;
            }
        }
    }
    else{
        ll base = (m - a1 - ak * k) / k;
        ll r = (m - a1 - ak * k) % k;
        ll res;
        if(r == 0){
            res = 0;
        }
        else if(k - r <= a1){
            res = 1;
        }
        else{
            res = r;
        }
        cout << base + res << endl;
    }
}

C. Strong Password

链接:https://codeforces.com/group/L9GOcnr1dm/contest/706137/problem/C

题意:

Monocarp要一个m(1<=m<=10)位的且每位都是0到9的数字密码,对数字密码有以下要求,Monocarp会先给一个数据库(一个string,每位上只有0到9,长度不超过3e5),给出两个长度为m的字符串l和r确保一一对应的位置上li<=ri。密码对应的第i位必须在li和ri的范围内,包括两端。这样构成的密码且不能是数据库里的子序列
子序列是从数据库中任选m个字符保持相对位置不变

思路:

因为我们不想要密码出现在数据库里面,所以我们在选择密码每一位上数字的时候可以故意地选择在数据库中最靠后的一个数字,这样我们可以减少数据库后面的数字,能减少密码被查重的概率。那么需要我们做出一个数组记录数据库中每一位数字右边的各个数字最靠近他的位置,特殊的是如果在选密码的一位数字的时候,在当前位置往后找不到某个可以选的数字,那么直接选这个数字就能避免被查重。预处理时间复杂度是O(|s|*10)。

代码:

点击查看代码
void solve(){
    string s;
    cin>>s;
    int m;
    cin>>m;
    string l,r;
    cin>>l>>r;
    int len=s.size();
    vector<vector<int>>next_pos(len+1,vector<int>(10,-1));
    for(int i=len-1;i>=0;--i){
        if(i+1<len)
            next_pos[i]=next_pos[i+1];
        next_pos[i][s[i]-'0']=i;
    }
    int cur=0;
    for(int i=0;i<m;i++){
        int L=l[i]-'0';
        int R=r[i]-'0';
        int max_pos=-1;
        for(int j=L;j<=R;j++){
            if(next_pos[cur][j]==-1){
                cout<<"YES"<<endl;
                return ;
            }
            int pos=next_pos[cur][j];
            max_pos=max(max_pos,pos);
        }
        cur=max_pos+1;
    }
    cout<<"NO"<<endl;
    return ;
}

D. Game on Permutation

链接:https://codeforces.com/group/L9GOcnr1dm/contest/706137/problem/D

题意:

现在有个n长度的排列,Alice和Bob轮流行动,由Alice先手,他们有一个发卡,这个发卡可以卡在数字上面,每次行动需要选择一个在当前发卡位置左边并且数字小于当前发卡所在位置的数字的数字,特殊地作为先手Alice第一次行动可以把发卡放在任意一个位置上。当一个玩家无法行动的时候,该玩家获胜。两个玩家都足够聪明。在这个游戏中我们将一个位置成为lucky当且仅当Alice把发卡放在这个位置上无论Bob采取什么行动,Alice总能赢,我们要做的就是求出这个排列中有几个lucky。
排列n:1到n中的数字每一个只出现一次,顺序任意。

思路:

我们观察转移的特点,我们只能转移到在左边且数字更小的位置,依旧从最简单的情况来分析。当只有一个数字的时候,Alice放上去一定是Bob胜利,所以一个数字的时候是Alice的必败点。两个数字的时候我们需要考虑两个数字的大小,但是无论数字大小关系如何,最左边的点一定是必败点,如果右边的数字比左边的数字小,那根本无法转移,Alice先手放到这个位置也是输,反之则反,这个时候我们放到右边的数字,我们只给Bob留下了一个能选的位置,且这个位置无法转移。那我们假设Alice选了一个位置,这个位置左边的所有能转移的位置全是不能转移的,更进一步来说也就是必败态,那么这个点就是lucky。因为是比较大小,所以我们用小根堆来储存到第i个位置已经有哪些lucky和必败点。如果这个点左边没有lucky,那么这个点就是lucky,或者这个点左边有lucky但是转移不过去,只能转移必败点,所以这个点也是lucky。那么必败点就是左边存在能转移的lucky。比较lucky的时候,我们只需要比较当前位置的数字和lucky堆里面最小的数字即可。

代码:

点击查看代码
void solve(){
    int n;
    cin>>n;
    vector<int>a(n);
    for(int i=0;i<n;i++){cin>>a[i];}
    priority_queue<int,vector<int>,greater<int>>lose,win;
    for(int i=0;i<n;i++){
        if(i==0)
            lose.push(a[i]);
        else{
            if((win.size()&&win.top()<a[i])||lose.top()>a[i]){
                lose.push(a[i]);
            }else if((win.empty()&&lose.top()<a[i])||win.top()>a[i]){
                win.push(a[i]);
            }
        }
    }
    cout<<win.size()<<endl;
    return ;
}

E. Binary String Copying

链接:https://codeforces.com/group/L9GOcnr1dm/contest/706137/problem/E

题意:

给出一个只有n个0或者1的字符串,然后给出m个单独的副本,对于每个副本i有自己的一个排序区间li,ri。求出对每个副本进行排序之后总共有多少个不同的字符串,最原本的字符串被计数当且仅当被排序后的副本有和原本一样的。

思路:

观察字符串的形式,发现只有0和1,我们对一个区间排序后一定是0在前面,1在后面。所以我们有一些排序实际上得到的是一样的字符串的原因就是我们排序的部分是一样的,我们把这个部分成为核,所以任务就变成有多少个不同的核。我们弄出两个数组来记录,第i个数字最右边的1和第i个数字最左边的0,当一个副本在li和ri区间排序的时候我们找到li最右边的1作为L,ri最左边的0作为R。当L<R的时候说明这个区间被排序了,我们记录一个种类的核;如果li右边没有1那么说明这个li到右边全是0,所以li到ri全是0,这就相当于没排序,和原本的字符串一样;当ri左边没有0,说明0到ri全是1,也是没排序;当li最右边的1出现在ri最左边的0的左边说明这个区间原本就是有序的,也是相当没排序。最后输出核的个数。

点击查看代码
void solve(){
    int n,m;
    cin>>n>>m;
    string s;
    cin>>s;
    s=" "+s+" ";
    vector<int>next_zoer(n+2, -1 ),next_one( n + 2 , -1 );
    for( int i=1;i<=n+1;i++){
        next_zoer[i]=next_zoer[i-1];
        if(s[i-1]=='0')
            next_zoer[i]=i-1;
    }
    for(int i=n;i>=0;i--){
        next_one[i]=next_one[i+1];
        if(s[i+1]=='1')
            next_one[i]=i+1;
    }
    set<pii>core;
    for(int i=0;i<m;i++){
        int l,r;
        cin>>l>>r;
        int L,R;
        if(s[r]=='0')
            R=r;
        else
            R=next_zoer[r];
        if(s[l]=='1')
            L=l;
        else
            L=next_one[l];
        if(L<R&&L!=-1&&R!=-1)
            core.insert({L,R});
        else
            core.insert({-1,-1});
    }
        cout<<core.size()<<endl;
    return ;
}

F. Ranom Numbers

链接:https://codeforces.com/group/L9GOcnr1dm/contest/706137/problem/F

题意:

我们有一个全是由A到E组成的字符串,其中A代表数值1,B代表10,C代表100,D代表1000,E代表10000,当某个位置上的字符右边存在比他大的字符的时候这个字符的数值取负号。现在最多有一次机会,可以把任意一个位置上的字符改成A到E中的任意一个字符,改变过后字符数值的正负随着变化而变化。我们要求如何使整个字符串的总和最大。

思路:

首先贪心地想,如果要变大一个字符,我们最好将这个字符最早出现的一个变大,因为这样影响到的左边的因此取负号的字符最少,如果我们要让一个字符变小,我们最好将这个字符最后出现的一个变小,因为这样可以让最多的左边的字符因此变成正数。所以我们用两个数组来记录每种字符最早出现和最后出现的位置,然后枚举该字符变化后的整个字符的总和,过程取最大值。时间复杂度是O(50*|s|)对于2e5的长度够了。

代码:

点击查看代码
int tr(char c){
    int te;
    if(c=='E')te=10000;
    if(c=='D')te=1000;
    if(c=='C')te=100;
    if(c=='B')te=10;
    if(c=='A')te=1;
    return te;
}
ll cal(const string &s){
    ll res=tr(s[s.size()-1]);
    char mx=s[s.size()-1];
    for(int i=s.size()-2;i>=1;--i){
        if(s[i]>=mx)
            res+= tr(s[i]);
        else
            res-=tr(s[i]);
        mx=max(mx,s[i]);
    }
    return res;
}
int main(){
	int t;
	cin>>t;
	while(t--){
		int n;
		string s;
		cin>>s;
		s=" "+s;
		n=s.size();
        int first[5],last[5];
        for(int i=0;i<5;i++){
            first[i]=n;
            last[i]=-1;
        }
        for(int i=1;i<n;i++){
            int id=s[i]-'A';
            if(first[id]==n)
                first[id]=i;
            last[id]=i;
        }
        ll ans= cal(s);
        for(int i=0;i<5;i++){
            if(first[i]!=n) {
                for (char to = 'A'; to <= 'E'; to++) {
                    char original = s[first[i]];
                    s[first[i]] = to;
                    ans=max(ans, cal(s));
                    s[first[i]] = original;
                }
            }
            if(last[i]!=-1) {
                for (char to = 'A'; to <= 'E'; to++) {
                    char original = s[last[i]];
                    s[last[i]] = to;
                    ans=max(ans,cal(s));
                    s[last[i]] = original;
                }
            }
        }
        cout<<ans<<endl;
    }
	return 0;
}
posted on 2026-07-24 20:22  wt_pav  阅读(8)  评论(0)    收藏  举报