2021牛客暑期多校训练营1(补题) 部分题解

题目链接:点这里

A.Alice and Bob

  • 题意
    有两堆石子,Alice和Bob轮流进行操作:从一堆石子中取出 k ( k > 0 ) k(k > 0) k(k>0)个石子,从另一堆石子中取出 s × k ( s ≥ 0 ) s \times k(s ≥0) s×k(s0)个石子,当不能进行操作的时候则输掉游戏,其中Alice先手。

  • 解题思路
    经典的博弈问题,首先我们要清楚一点, 该游戏不存在平局,所以存在必败态和必胜态,字面意义上即是当前的局面对于当前的操作者是必输还是必赢的状态。 那么这状态的转化我想也很清楚了,如果对于当前操作者是必胜态,那么说明当前操作者进行完操作后接下来的局面对于对手必定是必败态,同理必败态也如此。所以我们可以用 ( i , j ) (i,j) (i,j)来表示局面,那么易知对于 ( 0 , 0 ) (0,0) (0,0)为先手必败态,则我们可以通过初始的必败态转移,即反过来进行操作,那么也就是说如果操作者进行完操作后能到达必败态那么对于对手是必输的。 则我们对所有的必败态直接枚举所有石子的转移。值得注意的是,我们一开始需要假定所有的状态都是必败态,然后通过最初的 ( 0 , 0 ) (0,0) (0,0)去得到所有的必胜态,那么对于未转移到的状态必定是必败态。
    总时间复杂度为 O ( N 3 l o g N ) O(N^3logN) O(N3logN),预处理就能过。

  • AC代码

/**
  *@filename:A
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-20 01:03
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 5000 + 5;
const int P = 1e9+7;

bool dp[N][N];
int t,n,m;
//找到所有必败态。
void cal(int n,int m){
    for(int k = 1; n + k < N; ++ k){
        for(int s = 0; m + s * k < N; ++ s){
            dp[n + k][m + s * k] = 1;//由于能转到必败态,所以这种情况必赢。
        }
    }
    for(int k = 1; m + k < N; ++ k){
        for(int s = 0; n + s * k < N; ++ s){
            dp[n + s * k][m + k] = 1;//由于能转到必败态,所以这种情况必赢。
        }
    }
}
void init(){
    //此为我们所知的必败态。
    cal(0,0);
    for(int i = 1; i < N; ++ i){
        for(int j = 0; j < N; ++ j){
            if(!dp[i][j]){
                cal(i,j);
            }
        }
    }
}
void solve(){
    printf("%s\n",dp[n][m] ? "Alice" : "Bob");
}
int main(){
    init();
    scanf("%d", &t);
    while(t -- ){
        scanf("%d%d", &n, &m);
        solve();
    }
    return 0;
}

B.Ball Dropping

  • 题意
    判断一个球是否能卡在一个直角等腰梯形的内部,如果能,求卡着的球圆心距底边的高度。

  • 解题思路
    作辅助线,利用相似三角形的特性,看图即懂。
    在这里插入图片描述

  • AC代码

#include<bits/stdc++.h>

using namespace std;

double r,a,b,h;
int main(){
    cin >> r >> a >> b >> h;
    if(2 * r < b){
        cout << "Drop" << endl;
    }
    else{
        cout << "Stuck" << endl;
        double y = b * h / (a - b);
        double temp = 2 * r * y / b;
        temp *= temp;
        temp -= r * r;
        printf("%.10f\n",sqrt(temp));
    }
    return 0;
}

D.Determine the Photo Position

  • 题意
    给出一个 n × n n \times n n×n 01 01 01矩阵,问是否能用一个 1 × m 1 \times m 1×m的矩阵(不能旋转放置,只能水平放置)去覆盖掉一段 0 0 0,求出方案数。

  • 解题思路
    清楚题意此题其实就迎刃而解了,我们直接遍历寻找每段 0 0 0的长度,判断是否 ≥ m ≥m m,如果可行那么此段贡献方案数自然是 l e n g t h − m + 1 length - m + 1 lengthm+1,累加即是答案。

  • AC代码

#include<bits/stdc++.h>

using namespace std;

int n,m;
char s[2010][2010];
char temp[2010];
int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; ++ i){
        cin >> s[i] + 1;
    }
    cin >> temp + 1;
    int ans = 0,cnt = 0;
    for(int i = 1 ; i <= n; ++ i){
        cnt = 0;
        for(int j = 1; j <= n; ++ j){
            if(s[i][j] == '0'){
                cnt ++;
            }
            else{
                if(cnt >= m){
                    ans += cnt - m + 1;
                }
                cnt = 0;
            }
        }
        if(cnt >= m){
            ans += cnt - m + 1;
        }
    }
    cout << ans << endl;
    return 0;
}

F.Find 3-friendly Integers

  • 题意
    定义一个整数为 3 − f r i e n d l y 3 - friendly 3friendly当且仅当其存在至少一个字串(包含前导 0 0 0)为 3 3 3的倍数。现在给定一个区间 [ l , r ] [l,r] [l,r],求出该区间中是 3 − f r i e n d l y 3-friendly 3friendly的个数。

  • 解题思路
    我们知道,判断一个数是不是 3 3 3的倍数即判断位数之和是不是能整除 3 3 3那么对于一个 3 3 3位数以上的数,其各个位上的数必定是 0 , 1 , 2 0,1,2 0,1,2这三种可能,而 0 0 0即表示能整除 3 3 3,如果存在一个位上的数为 0 0 0,那肯定是 3 − f r i e n d l y 3-friendly 3friendly,那假设其各个位数上对 3 3 3取余结果只能是 1 , 2 1,2 1,2,那么我们发现,我们可以进行组合使它们的和能整除 3 3 3 所以,我们可以得到一个结论,即 3 3 3位数以上的数一定是 3 − f r i e n d l y 3 - friendly 3friendly,那么我们只需要处理 100 100 100以内的数,对每个数判断即可。

  • AC代码

/**
  *@filename:F
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-19 13:48
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 100000 + 5;
const int P = 1e9+7;

int t,flag[100];
//3位数以上的一定满足。
ll l,r;
bool check(string x){
    if(x.size() == 1){
        if((x[0] - '0') % 3 == 0){
            return true;
        }
        return false;
    }
    else{
        if((x[0] - '0') % 3 == 0 || (x[1] - '0') % 3 == 0 || ((x[0] - '0') * 10 + (x[1] - '0')) % 3 == 0){
            return true;
        }
        return false;
    }
}
void init(){
    for(int i = 1; i < 100; ++ i){
        if(check(to_string(i))){
            flag[i] = true;
        }
    }
}
int main(){
    cin >> t;
    init();
    while(t -- ){
        cin >> l >> r;
        ll ans = 0;
        if(l >= 100){
            ans += r - l + 1;
        }
        else if(r >= 100){
            ans += r - 99;
        }
        for(ll i = l; i <= r && i < 100; ++ i){
            if(flag[i])ans ++;
        }
        cout << ans << endl;
    }
    //solve();
    return 0;
}

G.Game of Swapping Numbers

  • 题意
    给定序列 A 、 B A、B AB,需要交换恰好 k k k A A A中不同的两个数,使得 s u m = ∑ i = 1 n ∣ A i − B i ∣ sum = \sum_{i = 1}^n|A_i - B_i| sum=i=1nAiBi最大。

  • 解题思路
    我们试想,倘若交换次数不限,那么按照贪心原则实际上就是让大的和小的组合在一起,那么相当于将 A , B A,B A,B合在一起排序,取前 n n n个数和后 n n n个数作差即可。
    那么回到这个问题,此时的交换次数是有限的,而 n > 2 n > 2 n>2的时候,我们注意到,我们可以进行无意义的操作来实现不交换的场景,所以我们只要最大化进行完最优的交换即可。我们需要特判n = 2的情况,因为此时必须要交换。
    那么如何最优进行交换呢?
    我们可以把 A i , B i A_i,B_i Ai,Bi看成是一段区间,那么我们只需要考虑每次交换操作的贡献。那么对于区间 [ A i , B i ] , [ A j , B j ] [A_i,B_i],[A_j,B_j] [Ai,Bi],[Aj,Bj],若这两个区间有交集,那么无论怎么处理都不会更优,甚至会损失,而当区间无交集的时候,此时作交换,会得到 2 ( m i n ( A i , B i ) − m a x ( A j , B j ) ) 2(min(A_i,B_i) - max(A_j,B_j)) 2(min(Ai,Bi)max(Aj,Bj)),所以我们需要将将所有的 m i n ( A i , B i ) min(A_i, B_i) min(Ai,Bi) m a x ( A i , B i ) max(A_i, B_i) max(Ai,Bi) 排序,依次取前 k 大取出相应的贡献即可。注意由于我们使用区间处理,所以我们需要保证 A i ≤ B i A_i \leq B_i AiBi的。

  • AC代码

/**
  *@filename:G
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-20 01:27
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 500000 + 5;
const int P = 1e9+7;

int n,k;
int a[N],b[N];
void solve(){
}
int main(){
    scanf("%d%d", &n, &k);
    ll ans = 0;
    for(int i = 1; i <= n; ++ i){
        scanf("%d", &a[i]);
    }
    for(int i = 1; i <= n; ++ i){
        scanf("%d", &b[i]);
        if(a[i] > b[i])swap(a[i],b[i]);//这里保证为区间[ai,bi]
        ans += abs(a[i] - b[i]);
    }
    if(n == 2){
        //特判n = 2的情况,此时必须要交换。
        if(k & 1){
            ans = abs(a[1] - b[2]) + abs(a[2] - b[1]);
        }
        else{
            ans = abs(a[1] - b[1]) + abs(a[2] - b[2]);
        }
    }
    else{
        //而n > 2的时候,我们注意到,我们可以进行无意义的操作来实现不交换的场景,所以我们只要最大化进行完最优的交换即可。
        sort(a + 1, a + 1 + n, greater<int>() );
        sort(b + 1, b + 1 + n);
        for(int i = 1; i <= k && i <= n; ++ i){
            if(a[i] > b[i]){
                ans += 2 * (a[i] - b[i]);//也就是说,min里面最大的那个比max里面最小的那个大,此时我们可以交换使得多贡献了2倍。
            }
            else{
                break;
            }
        }
    }
    printf("%lld\n", ans);
    solve();
    return 0;
}
posted @   unique_pursuit  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示