牛客周赛 Round 29 题解

牛客周赛 Round 29 题解

代码风格

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

#include
//#include
//#include
//#include
//#include
//#include
​
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")<s2[i]?"kou":"yukari")<

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;
    vectorb(s.size());
    for(int i=0;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;
    //为更好处理下标问题,可以选择牺牲一个单位空间进行处理
    vectora(n+1),b(n+1);
    vectorans(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]>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
#define pll pair
//#define int long long  一般不启用 如果启用那么main那里需要要改signed
bool cmp( pii  x, pii  y) {
    return x.first > y.first;
}
​
​
void solve() {
    //用一个pll来存储即可,map也行,但无需去排序处理,避免浪费时间
    vectora;
//因为任何合数一定可以拆解成质因数,所以我们从小的数开始去逐渐拆解即可
    ll x; //10^19
    cin >> x;
    //处理前,需要去特判一下,可以看到下面的循环是x从4开始的,所以可以对x<4进行处理即可
    //同时由于x=2,3可以在后续处理掉,所以这里不进行特判
    if(x==1){
        cout<<-1< 1) a.push_back({1, x}), sum += 1;  //当x=2,3是也可以这样处理
    sort(a.begin(),a.end(),cmp);
    //或者第三个参数为 greater()
    if(a[0].first*2>sum+1){ //插空法判断  sum-n>=n-1才行
        cout<<-1<ans(sum);
        //然后依次隔空放即可
        ll j=0;
        for(ll i=0;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>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<
​
​
​
​

你可能感兴趣的:(算法,ACM题解,算法)