2017微软秋季校园招聘在线编程笔试

别的不多说了,记录一下题目吧。找工作有很大一部分因素是缘分,莫强求,保持好心态~

只A了前三道:

第一题:

题意:给出n个数(n≤1000000),可以任意选择两个相邻的数字,如果它们相加的和是奇数,那么可以将这两个数删除。问最后可能剩下的最少的数的数量。

思路:细想可知最后剩下的数的数量是确定的。比如考虑有x个奇数,y个偶数(x>y),那么必然存在某个偶数和奇数相邻可删除。所以数量少的必定最后会全部被删除。最后的答案也就是奇数的数量和偶数数量之差的绝对值咯。

代码如下:

#include <iostream>
#include <cmath>
using namespace std;
int main(){
    int n,x,res = 0;
    scanf("%d",&n);
    for(int i = 0;i<n;i++){
        scanf("%d",&x);
        res += (x&1);
    }
    printf("%d\n",abs((res<<1)-n));
    return 0;
}

第二题:

题意:给出一个长度为n(n<100000)的只包含小写字符的字符串s[0...n-1],再给出m(m<=200)个字符对(如:ab),表示ab和ba不能出现在串中(后文称这个不能出现的集合为黑名单。问最少删除几个字符能够使得输入串是合法的?

思路:只需求合法的串的最长可能。明显思路是dp。一个显然的一维dp是设dp[x]表示考虑前s[0...x]中,且最后一个字符必须是s[x]的情况下的最长可能。那么dp[x]=max(dp[j]+1),0<=j<=x-1且(s[j],s[x])不出现在黑名单中。两点说明:第一,dp的值不是递增的,所以不能从大到小遍历j,找到第一个(s[j],s[x])不在黑名单中就退出。这一点可以考虑如下例子:acccbd,黑名单是(c,b)。第二:这个dp 的复杂度显然是O(n^2),会超时。

还是考虑上述dp,有没有考虑重复的情况呢?其实是有的,在遍历j的过程中,s[j]处可能出现多个相同的字符的情况,实际上这只需要考虑最长的那个即可。这给出了一个启示:只需要维护以特定字符结尾的最长的数量即可。所以dp设计为:dp(i,j)表示考虑前i的字符的子串且最后一个字符为j(0<=j<=25)的最长可能。那么实际上每遇到一个新的s[i],只需要更新以s[i]为最后一个字符的情况,其他的情况都未变。状态方程为:如果j表示的是s[i]的字符:那么dp[i][j] = max(dp[i-1][k]+1),0<=k<=25且(('a'+k), s[i])不出现在黑名单中;否则dp[i][j] = dp[i-1][j]。这个动归的复杂度是O(26n),符合要求。实际上空间上可以只开一个26大小的数组,每次只更新s[i]对应字符位置的dp。

代码如下:

#include <iostream>
#include <cstring>
#include <set>
#define N 100005
#define INF 0x3fffffff
using namespace std;
int n,m;
int dp[27];
bool hh[27][27];
int main(){
    cin >> n;
    string s;
    cin >> s;
    scanf("%d",&m);
    for(int i = 0;i<m;i++){
        getchar();
        char c1,c2;
        c1 = getchar();
        c2 = getchar();
        hh[c1-'a'][c2-'a'] = hh[c2-'a'][c1-'a'] = true;
    }
    int res = 0;
    dp[s[0]-'a'] = 1;
    for(int i = 1;i<n;i++){
        int j = s[i]-'a';
        if(!hh[j][j])//先更新dp[j],这样后面再更新就不会影响dp[j]了
            dp[j]++;
        else
            dp[j] = max(dp[j], 1);
        for(int k = 0;k<26;k++)
            if(k != j && !hh[k][j])
                dp[j] = max(dp[j], dp[k]+1);
    }
    for(int i = 0;i<26;i++)
        res = max(res,dp[i]);
    cout << n-res << endl;
    return 0;
}

第三题:

题意:n个学生(n<=10000)m个(m<=100)地方。每个学生i有一个唯一的id,以及到达校门的时间ti。而且他要按顺序去pi个地方办理手续,这个序列用用(oij,wij)表示,也就是第j个地方要去的是oij这个点,而且要呆wij长时间。已知从校门去到每个地方以及地方和地方之间的转移时间都是k。每个地方只能给一个学生办理手续,办理的次序按照先来先服务,如果同时到达id小的优先。问每个学生结束办理手续的时间分别是多少。

思路:对这个题目非常失望,看描述以为是一道图论题,实际上就是模拟,没什么技术含量。把该表示的表示清楚,用队列按照时间顺序挨个处理就好了。给我的一个教训就是,结构体的构造和析构非常耗时,如果不需要这种构造和析构,那么考虑使用指针。

代码如下:

#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <vector>
#include <queue>
#include <algorithm>
#include <cmath>
#include <map>
#include <set>
#include <iostream>
#define N 10005
#define INF 0x3fffffff
using namespace std;
int n,m,k;
struct node{
    int now,id;
    int index;
    int len;
    int v;
    vector<int> s;
    vector<int> t;
    node(){
        now = id = index = len = v = 0;
    };
};
node *p[N];
int res[N],beg[105];
struct cmp{
    bool operator()(node *a,node *b){
        if(a->now == b->now)
            return a->id > b->id;
        return a->now>b->now;
    }
};
priority_queue<node*, vector<node*>, cmp> q;
int main(){
    scanf("%d %d %d",&n,&m,&k);
    for(int i = 0;i<n;i++){
        int num,a,b;
        p[i] = new node();
        scanf("%d %d %d",&p[i]->id,&p[i]->now,&num);
        p[i]->v = i;
        p[i]->len = num;
        for(int j = 0;j<num;j++){
            scanf("%d %d",&a,&b);
            p[i]->s.push_back(a);
            p[i]->t.push_back(b);
        }
        p[i]->index = 0;
        p[i]->now += k;
        q.push(p[i]);
    }
    while(!q.empty()){
        node *tmp = q.top();
        q.pop();
        int b = max(tmp->now, beg[tmp->s[tmp->index]]);
        tmp->now = beg[tmp->s[tmp->index]] = b+tmp->t[tmp->index];
        if(tmp->index == tmp->len-1){
            res[tmp->v] = tmp->now;
        }else{
            tmp->now += k;
            tmp->index++;
            q.push(tmp);
        }
    }
    for(int i = 0;i<n;i++)
        printf("%d\n",res[i]);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值