co_fun 社区提供 微软秋招笔试 8.6 题解

微软秋招笔试 8.6 题解

本文由 co_fun 内推社区 GreyKa 提供,欢迎加入社区qq群 618358435,三十几家大厂员工为你内推。

题目1
题意:
有一个长度为 1000000 的字符串,由 . 和 x 组成,每次你能选择字符串中的一个子串(要求子串中的所有字符都是 x),把他变成 . 。每次操作的开销为子串长度 +1。给定最大开销,问你最多能讲多少 x 变为 .。

解题思路:

可以很显然的发现,因为每次操作都会有额外一点开销,因此对尽可能长的串进行操作的浪费最小。因此我们可以把连续的 x 的串的长度统计出来,每次操作优先选取最大的,直到剩余的开销不足以对当前最大的段进行操作时,截取最大的段当中的一部分进行操作即可。

代码
#include<bitsdc++.h>
using namespace std;

priority_queue<int> pq;
int solution(string &S, int B) {
    S=S+".";
    int rec=0;
    for(int i=0;i<S.size();i++){
        if(S[i]=='x'){
            rec++;
        }else{
            if(rec>0){
                pq.push(rec);
                rec=0;
            }
        }
    }
    int ans=0;
    while((B>1)&&pq.size()){
        int temp=pq.top();
        pq.pop();
        int now=min(B-1,temp);
        ans+=now;
        B-=(now+1);
    }
    return ans;
}

题目2
题意:
给你一个由 R 和 W 组成的长度为 1e6 的字符串,你每次操作可以选择其中两个位置相邻的字符进行交换,问你最少操作多少次,可以使得所有的 R 字符变成连续的一段。

解题思路

这题是很经典的一个中位数的题的变种。
  我们可以猜测,所有的 R 字符中,会有一个是不用变化位置的,而其他所有的R会以它为最中间的位置靠拢
证明如下:
  首先我们先假设已经知道了变换后的 R 是原串中的某一段,我们可以找到一个位置,使得他两边的 R 都是原串的左边和右边移动过来的。这种情况下面,可以发现,若左边的元素数量小于右边,则我们把分割线移动到右边的第一个元素的左侧,能够获得更优解,如图中的绿色线。若分割线为红线,则右边的 3 个元素都会移动一次 x 的距离,对答案产生 3x 的贡献,若将绿色线作为分割线,则左边的 2 个元素都会再移动一次 x 的距离,产生 2x 的贡献,更优。反之亦然,而且当两边元素数量一样时,也能保证不会使得答案更差。
在这里插入图片描述

知道了这个结论以后,我们怎么确定哪个 R 是不动的,又如何计算值呢?如下图,假设第三个 R 是不动的那个,则我们观察左右两边的 R 在向中间靠拢的时候,第二个 R 所走的那段距离会被走两次(他自己走一次,他左边的 R 也会走一次)。因此我们发现,最外侧的第 1 个 R 到第 2 个 R 的距离会被走 1 次,而第 2 个 R 到第 3 个 R 的距离会被走 2 次,第 n 个 R 到第 n+1 个 R 的距离会被走 n 次。因此总答案的计算如图,从外围到中心,第 i 个间距的值乘以 i,再全部相加,就是结果。因此这题就变成了给间距去从外层到里层分配系数。明显可以知道,分割线左右两边的间距数量越接近答案越优。
在这里插入图片描述

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int solution(string& S)
{
    ll ans=0;
    int last=-1;
    vector<int> v;
    for(int i=0;i<S.size();i++)
    {
        if(S[i]=='R')
        {
            if(last!=-1)
                v.push_back(i-last-1);
            last=i;
        }
    }
    for(int i=0;i<v.size()/2;i++)
        ans+=(i+1)*v[i];
    for(int i=0; i<(v.size()+1)/2;i++)
        ans+=(i+1)*v[v.size()-i-1];
    // 题目要求答案大于1e9输出-1
    if(ans>1000000000)
        return -1;
    return ans;
}

题目3
有 n 个病人和 m 个医生,每个病人有两个期望医生,每个医生只能接待一个病人。问有没有一种安排方法,能让每个病人都看上自己期望的医生。(n,m为 100000 和 200000)

解题思路

  很明显的二分图匹配问题,因此这题如果不会做可以直接套二分图匹配算法 匈牙利算法 板子,复杂度不够但是能骗到不少分。如果学过网络流,用 Dinic 算法应该也能通过本题。不过这都不是企业招聘笔试算法考纲内的东西。下面我们来说正解。(感谢 uniecho1 学弟的启发)

  我们先将病人和期望的两个医生连接,变成一个二分图。我们考虑一个联通的二分图(很显然如果原图上有多个连通块,不同连通块之间互相不可能产生影响。两个连通块之间医生和病人之间没有任何关系,匹配不会互相影响。),若图中所有的连通块都能满足匹配需求,则能满足题目的匹配要求,反之有一个连通块不能满足要求,则直接匹配失败。

  对于一个联通图,我们可以知道如果当中医生的数量如果少于病人,则会直接匹配失败
在这里插入图片描述

  那么我们只需要考虑医生数量大于等于病人的情况。首先如果医生数量大于病人数量,我们会发现,医生数量必然是病人数量 +1,只有这样才能保证这个图联通(如果把病人当作连接两个医生的一条边,会发现这张图其实就是一棵树)。此时就很明显,所有的病人都能匹配上。
在这里插入图片描述

当医生和病人数量相同的时时候,分为两种情况:
  第一种情况是,所有的医生都有两个相关病人。这时候发现原图会形成一个环(二分图上,每个点都有两个度,说明每个点都可以被进入和走出一次,最终走完整张图),此时显然能够完全匹配上
在这里插入图片描述

  第二种情况是,存在医生关联大于或者小于 2 个病人。因为医生的数量大于等于病人,而每个病人会提供 2 条边,若存在医生拥有超过 2 个相关病人,则必然会有医生只有一个相关病人(不然总边数会大于 2n)。很显然这个只有一个相关病人的医生直接和那个病人贪心匹配是最优的。这时候我们会发现有另外一个医生的连边会减少 1,但是不会导致这个医生的连线变成 0 (否则这三个点会变成单独一个连通块,和我们题设不符)。因此每次贪心操作会导致一个医生和一个病人的减少。有没有发现,出现了递归的子问题
在这里插入图片描述

因此在医生和病人数量相同的情况下,只有两种情况:第一种情况下,直接匹配完成。第二种情况下,必然可以贪心拿走一对病人和医生,依然是最初病人和医生相同的状态。因此根据归纳法,我们可以知道病人和医生数量相同时,必然能够完全匹配

综上所述,问题变成了对于每个二分图连通块,判断其中医生数量是否大于等于病人,如果是则这个连通块匹配成功,反之失败。我们只需要一遍搜索,对每个连通块计算病人和医生数量即可。

代码
#include<iostream>
#include<vector>
#define MAXN 400000
using namespace std;
vector<int> e[MAXN];
bool vis[MAXN];
void dfs(int now,int& p,int &d,const int& N)
{
    if(vis[now])return;
    vis[now]=true;
    if(now<=N)p++;
    else d++;
    for(int i=0;i<e[now].size();i++)
        dfs(e[now][i], p, d, N);
}
bool check(int now,int N)
{
    int p=0,d=0;
    dfs(now,p,d,N);
    if(p<=d)return true;
    return false;
}
int solution(int A[], int B[], int N, int S)
{
    for(int i=0;i<N;i++)
    {
        e[i+1].push_back(A[i]+N);
        e[A[i]+N].push_back(i+1);
        e[i+1].push_back(B[i]+N);
        e[B[i]+N].push_back(i+1);
    }
    for(int i=1;i<=N;i++)
        if(!check(i,N))
            return 0;
    return 1;
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值