牛客周赛 Round 29 题解

牛客周赛 Round 29 题解

代码风格

后续目标代码写在solve()方法中

#include<bits/stdc++.h>
//#include<iostream>
//#include<vector>
//#include<cstdio>
//#include<cmath>
//#include<cstring>
​
using namespace std;
#define ll long long
#define endl "\n"
​
const int N = 2e5 + 10;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int INF = 1e9;
const int MAXN = 2020;
const int mod = 1e8 + 7;
​
int prime[N];
void check() {
    memset(prime, 0, sizeof(prime)); //0 素数 1 不是素数
    prime[1] = 1;
    for (int i = 2; i <= N; i++) {
        if (prime[i] == 0) {
            for (int j = i * 2; j <= N; j += i) {
                prime[j] = 1;
            }
        }
    }
}
​
bool cmp(int x, int y) {
    return x > y;
}
​
void solve() {
​
}
​
int main() {
    //  freopen("input.txt","r",stdin);
    //  freopen("output.txt","w",stdout);
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t;
    cin >> t;
    //  cin.ignore() ;
    //  cin.clear();
    //t = 1;
    //check();
    //这里根据具体情况来变动t,是输入还是默认为1
    while ( t--) {
        solve();
    }
}

A.小红大战小紫

思路

大小比较即可,如果数字很大,可以进行高精度大小比较即可

AC代码
//这里直接给高精度模板了,低精度无意义
void solve() {
    string s1,s2;
    cin>>s1>>s2;
    int len_s1=s1.size();
    int len_s2=s2.size();
    if(len_s1!=len_s2){
        cout<<(len_s1>len_s2?"kou":"yukari")<<endl;
    }else{
        for(int i=0;i<len_s1;i++){
            if(s1[i]!=s2[i]){
                cout<<(s1[i]>s2[i]?"kou":"yukari")<<endl;
                return;
            }
        }
        cout<<"draw"<<endl;
    }
        
}

B.小红的白日梦

思路

模拟即可,每天如果白天没做梦就进行转移获得最大幸福度,否则就正常计算

AC代码
//正常逻辑判断即可
void solve() {
    int n;
    cin >> n;
    string day, night;
    cin >> day >> night;
    int ans = 0;
    for (int i = 0; i < n; i++) {
        if (day[i] == 'N') {
            ans += (night[i] == 'Y' ? 2 : 0);
        } else {
            ans += (2 + ((night[i] == 'Y') ? 1 : 0));
        }
    }
    cout << ans << endl;
}
    

C.小红的小小红

思路

利用find函数寻找子串即可,然后对该子串进行bool处理,随后依次输出即可

AC代码
void solve() {
    string s;
    cin>>s;
    vector<bool>b(s.size());
    for(int i=0;i<s.size();i++) b[i]=false;
    int pos1=s.find("xiao");
    int pos2=s.find("hong");
    for(int i=pos1;i<=pos1+3;i++) b[i]=true;
    for(int i=pos2;i<=pos2+3;i++) b[i]=true;
    cout<<"xiaohong";
    for(int i=0;i<s.size();i++) if(!b[i]) cout<<s[i];
}
​

D.小红的中位数

思路

先进行题目规律的探索,去寻找规律,即明显应该排完序后再看,如果去除几号元素,会发生中位数变化有什么规律,同时可以发现将数据分成奇偶来考虑,同时对半分进行考虑即可

AC代码
struct data{
    double value;
    int pos;
    int pos_sort;
};
​
bool cmp( struct data x,struct  data  y) {
    return x.value > y.value;
}
​
​
void solve() {
    int  n;
    cin>>n;
    //为更好处理下标问题,可以选择牺牲一个单位空间进行处理
    vector<int>a(n+1),b(n+1);
    vector<double>ans(n+1);
    for(int i=1;i<=n;i++) {cin>>a[i]; b[i]=a[i];}
    //利用一个额外vector来记忆原来顺序即可
    //两种常见解法,一种利用中值判断即可
    sort(a.begin(),a.end());
    //排序后得到中间值,利用中间值来判断即可
    if(n%2==0){//偶数情况下  如 1 2 3 4 5 6   6/2=3
        int midNum1=a[n/2];
        int midNum2=a[n/2+1];
        for(int i=1;i<=n;i++){
            //这里其实考虑了相等的情况,可以证明
            if(b[i]<midNum2) ans[i]=midNum2*1.0;
            else ans[i]=midNum1*1.0;
        }
    }else{ //奇数情况下 如  1 2 3 4 5   5/2=2
        int midNum=a[n/2+1];
        for(int i=1;i<=n;i++){
            if(b[i]<midNum) ans[i]=(a[n/2+1]+a[n/2+2])/2.0;
            else if(b[i]==midNum) ans[i]=(a[n/2]+a[n/2+2])/2.0;
            else ans[i]=(a[n/2]+a[n/2+1])/2.0;
        }
    }
    for(int i=1;i<=n;i++) cout<<fixed<<setprecision(1)<<ans[i]<<endl;
    //如果想要得到排序后的数字的具体位置,那么就应该构建结构体来处理
//  struct data mid[N+1];
//  for(int i=1;i<=n;i++){ cin>>mid[i].value; mid[i].pos=i;}
//  sort(mid+1,mid+n+1,cmp);
//  for(int i=1;i<=n;i++){ mid[i].pos_sort=i;}
    //后续处理与上面一致,无需再复杂化,只不过如果想构建,得用结构体才行
}
​
​

E.小红构造数组

此题特别注意数据范围,数据大问题

思路

题目首先进行第一步直观的操作就是要得到一个数的全部质因数,及其对应的个数,这一步我们可以联想到欧式筛选法进行套用,便可以完成

第二步问题在于,我们应该如何去构建这个数组,即如何从数学上保证该数组满足条件,那么最开始我们可以想到的是利用每一次取两个最多的各一个,但这种我们没法去证明,只能模糊的得到当最多的个数>其余总和+1的情况是绝对不满足的,但我们无法证明其相反面是满足的,所以这里我们进一步采用填补法,插空法来论证,因为要求有相邻不同,排列那么很明显用插空法的思想来论证是比较好的,所以我们来采用一种构造方式

我们将最多的质因数先排列,然后第二多的去插空,可以发现,只要最多的n个的n-1个空被插满了,那么就肯定可以构造成功,后续的空是越来越多的,所以论证完毕

AC代码
#define pii pair<int,int>
#define pll pair<long long,long long>
//#define int long long  一般不启用 如果启用那么main那里需要要改signed
bool cmp( pii  x, pii  y) {
    return x.first > y.first;
}
​
​
void solve() {
    //用一个pll来存储即可,map也行,但无需去排序处理,避免浪费时间
    vector<pll>a;
//因为任何合数一定可以拆解成质因数,所以我们从小的数开始去逐渐拆解即可
    ll x; //10^19
    cin >> x;
    //处理前,需要去特判一下,可以看到下面的循环是x从4开始的,所以可以对x<4进行处理即可
    //同时由于x=2,3可以在后续处理掉,所以这里不进行特判
    if(x==1){
        cout<<-1<<endl;
        return;
    }
    ll sum = 0; //记录因子数总和,来进行判断最大是否可行
    for (ll i = 2; i * i <= x; i++) { //因为质因数分布在根号x两侧 或者x/2两侧
        //特别注意这里的变化,如果因为前一个质数的全部没了,所以下一个一定是从i*i开始的
        if (x % i == 0) {
            ll cnt = 0;
            while (x % i == 0) {
                cnt++;
                x /= i;
            }//当2可以约分时,那么x就把2除完了,那么2的所有倍数就不行了,也就是关于2的合数就不可能%了
            a.push_back({cnt, i});
            sum += cnt;
        }
    }
     if(x > 1) a.push_back({1, x}), sum += 1;  //当x=2,3是也可以这样处理
    sort(a.begin(),a.end(),cmp);
    //或者第三个参数为 greater<pii>()
    if(a[0].first*2>sum+1){ //插空法判断  sum-n>=n-1才行
        cout<<-1<<endl;
        return;
    }else{
        vector<ll>ans(sum);
        //然后依次隔空放即可
        ll j=0;
        for(ll i=0;i<sum;i+=2){
            ans[i]=a[j].second;
            a[j].first--;
            if(a[j].first==0)j++;  //隔空来放就可以了,最多的情况是其余刚好给最大的插空完
        }
        for(ll i=1;i<sum;i+=2){
            ans[i]=a[j].second;
            a[j].first--;
            if(a[j].first==0)j++;
        }
        cout<<sum<<endl;
        for(ll i=0;i<sum;i++)cout<<ans[i]<<" ";
    }
    
}
​
​

F.小红又战小紫

思路

题目的知识点进行分解理解,首先是分数取模问题,即逆元问题

x/y 对p取模 本质上就是x*y对p的逆元后再对p取模

(x/y)%p=a a**y%p=x ->

a X y X y^-1 %p=x X y^-1 %p -> a%p=x * y^-1 %p

所以这里就可以得到所谓的求x/y对p取模本质上就是x*y对p的逆元后%p

所以求a,问题在于求y对p的逆元,这里采用最简单的费马小定理和欧拉定理得到的编程实现

结合带mod的快速幂算法和求逆元算法,注意前提条件一定是y,p是互质的,通常取p是素数,即费马小定理这个特殊情况下,才可以运用这个快速幂模版来计算

由于概率是分数,我们就不能够进行直接相除之后再转化,不然那样会失真,所以我们需要将/完全转化为%操作,也就是(1-dpi-1,j+mod)%mod操作 将概率进行取模化

其次DP问题,我在代码注释里进行了更加详细的解释

AC代码
//如果存在某一堆石子为2 必然使用技能1
//如果不存在某一堆石子为2 必然使用技能2,此时胜率为100%
​
//考虑到彼此之间存在特殊的关系,即小红进行操作时,如果石子全为1,则使用技能2必胜
//如果不全为1,则使用技能1,但是使用是有取舍选择的,显然是两种选择,1种是选择堆为2的石子,消去一个,或者是选择堆为1的石子让其消失
//同时,选择的概率也取决于当时的状态 所以明显需要使用到状态转移,动态规划进行考虑
//故设置dp[i][j] 表征当前有i个石子数为1的堆,j个石子数为2的堆,此时先手方(小红)的获胜概率
​
//i+j堆石子 那么 i/(i+j)的概率选择到消去1个石头  j/(i+j)的概率选择到消去2个石头中的1个石头
//所以状态转移就出来了 dp[i][j]=dp[i+1][j-1]*j/(i+j)+dp[i-1][j]*i/(i+j)
//然后可以知道 dp[i][0]=1  然后进行状态转移dp即可
//这里存在一个问题,那就是动态规划必须是底层向前层推进,不可推进过程中收到之前的影响
//如果这里设置 i,j  i表示堆为1的石子 那这样必然存在互相影响,因为转移只能i从小到大
//而这里的i,j状态中i,j需要i+1层,所以肯定有问题
//这里进行转化即可,i重新定义为全部的石子的堆数即可
//dp[i][j]=dp[i-1][j]*(i-j)/i+dp[i][j-1]*j/i;
//这里由于参照了数论中的取模运算,那么对最后的分数取模,我们在同步计算是就必须将/进行转化
//更详细解释参考其他文档
​
ll qkpow(ll a,ll b){
    ll res=1;
    ll mul=a%mod;
    while(b){
        if(b&1) res=res*mul%mod;
        b>>=1;
        mul=mul*mul%mod;
    }
    return res;
}
​
ll getInv(ll x){
    return qkpow(x,mod-2);
}
​
ll dp[MAXN][MAXN]={0};
void solve() {
    int n;
    cin>>n;
    int num[3]={0};
    for(int i=0,x;i<n;i++){
        cin>>x;
        num[x]++;
    }
    //有误!!!不删除进行对比
//  for(int i=1;i<=n;i++) dp[i][0]=1;
//  for(int i=0;i<=n;i++){
//      for(int j=1;j<=n;j++){
//          if(i!=0) dp[i][j]=((1-dp[i-1][j]+mod)%mod)*i*getInv(i+j)%mod+((1-dp[i+1][j-1]%mod))*j*getInv(i+j)%mod;
//          else     dp[i][j]=((1-dp[i+1][j-1]+mod)%mod)*j*getInv(i+j)%mod;
//          dp[i][j]%=mod;
//      }   
//  }
​
​
for(int i=1;i<=n;i++){
    dp[i][0]=1;
    for(int j=1;j<=i;j++){
        //同时这里的mod一定要完全%化 不然要出问题的
        //(1-dp[i-1][j]+mod)%mod) 这一步操作是将概率进行取模化
        //(i-j)*getInv(i)%mod)%mod   getInv本身就很大所以需要去%一次  在计算完后再进行%mod一次
        //这里1减必须要注意,因为我们要考虑谁来操作,那么这一步获胜一定是在前面一人前面一步失败的基础上
        dp[i][j]=((1-dp[i-1][j]+mod)%mod)*((i-j)*getInv(i)%mod)%mod+((1-dp[i][j-1]+mod)%mod)*(j*getInv(i)%mod)%mod;
        dp[i][j]%=mod;//两个已经%mod的数同样需要再次%mod一次
    }
}
    cout<<dp[n][num[2]]<<endl;
}
​
​
​
​
  • 28
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值