美团点评CodeM编程大赛|资格赛非官方题解

这是个人题解,不保证和出题人想法一致。官方题解的话,不妨去听2017.06.17晚上红q曾耀辉老师的讲解吧。(逃

红q 曾耀辉(quailty,注意不是quality) 参考 题解&&代码: http://static.nowcoder.com/b/codem/codem_qulification.zip
题目在线练习地址: https://www.nowcoder.com/test/5513596/summary

Prob A:根据题意实现即可。注意读题,要求连续的子序列。所以只要把A串在B串上,一个个位置比较过去,取最小值作为答案即可。
#include <stdio.h>
#include <limits.h>
#include <algorithm>
using namespace std;
int a[1005],b[1005];
int n,m;
int f[1005];

void get(int* arr,int& x){
    scanf("%d",&x);
    for(int i=1;i<=x;i++){
        scanf("%d",&arr[i]);
    }
}

int getans(int* a,int* b,int n){
    int ret=0;
    for(int i=0;i<n;i++)ret+=(a[i]-b[i])*(a[i]-b[i]);
    return ret;
}

int main(){
    get(a,n);get(b,m);
    int ans=INT_MAX;
    for(int i=1;i+n-1<=m;i++){
        ans=min(ans,getans(a+1,b+i,n));
    }
    printf("%d\n",ans);
    return 0;
}
Prob B:不需要排序,只需要读入第一个数,然后统计有多少个数>=第一个数,就知道排在倒数第几了。
至于能坚持几轮,最后一名肯定第1轮都过不去,倒数第二名打倒数第一名能挺过去一轮,
倒数第3名的话也只能挺过去一轮——比他弱的2人,一个被他打了,另一个肯定被别人打了,然后这个倒数第三名在下一轮直接出局了。
倒数第4名的话,能做到2轮,以此类推。
显然,n-1个和他一样的/比他弱的+他自己(共n个人),下一轮最多剩下floor(n/2)人。
那就尽量/2,除完之后结果>=1的次数,就是答案。
(或者说,这个排位的二进制表示的最高位,在哪一位。)
#include <stdio.h>
int main(){
    int n,f=-11111111,cnt=0,tmp;
    for(scanf("%d",&n);n--;){
        scanf("%d",&tmp);
        if(f==-11111111)f=tmp;
        if(tmp<=f)++cnt;
    }
    int ans=0;
    while(cnt>1){
        cnt/=2;
        ++ans;
    }
    printf("%d\n",ans);
}
Prob C:把问号放到平衡树/set里,记录并维护一下?(问号)出现的下标。
开几个数组,记录手上有几张第x种优惠券,上次买入变成1张是什么时候,上次卖出变成0张是什么时候。
买入的时候,如果之前手里有1张了,那么找出买入上1张后,到现在,中间的最早的?,把这个?改成卖出操作,这样这个买入就合理了。
卖出的时候,之前已经没了的情况处理类似。
(现在看看代码,感觉实现有bug:如果真实记录里,就只有1~10w这10万种优惠券、只有I和O两种操作,不允许空操作的话,那:
I 1
I 2
I 3
...
I 100000
?
O 1
O 2
O 3
...
O 100000
我的程序会觉得没问题,但实际上有:买入10万张以后,这个?不可以再买入,只能随便卖出1张。
但是随便卖出谁,都会导致后面10万条不同的卖出操作中,有谁实际上卖不出去了。
那个问号,要不可以代表任何操作,要不可以是I 100001,不然我的程序是错的……
UPD:根据红q(quailty,http://codeforces.com/profile/quailtyhttp://osu.ppy.sh/u/6423914 )的意思,是允许有I 100001,那就没问题了
#include <stdio.h>
#include <algorithm>
#include <set>
using namespace std;
int cnt[100005];
int lasto[100005];
int lasti[100005];
int maxid,err;
set<int> left;
char str[2];
int id,m;
int main(){
    while(~scanf("%d",&m)){
        fill(cnt,cnt+maxid+1,0);
        fill(lasto,lasto+maxid+1,0);
        fill(lasti,lasti+maxid+1,0);
        maxid=0;
        err=-1;
        left.clear();
        for(int i=1;i<=m;i++){
            scanf("%s",str);
            if(str[0]=='?'){
                left.insert(i);
            }else{
                scanf("%d",&id);
                maxid=max(maxid,id);
                if(str[0]=='I'){
                    cnt[id]++;
                    if(cnt[id]>=2){
                        auto it=left.lower_bound(lasti[id]);
                        if(it!=left.end()){
                            left.erase(it);
                            --cnt[id];
                        }else{
                            if(err==-1)err=i;
                        }
                    }
                    lasti[id]=i;
                }else{
                    cnt[id]--;
                    if(cnt[id]<0){
                        auto it=left.lower_bound(lasto[id]);
                        if(it!=left.end()){
                            left.erase(it);
                            ++cnt[id];
                        }else{
                            if(err==-1)err=i;
                        }
                    }
                    lasto[id]=i;
                }
            }
        }
        printf("%d\n",err);
        
    }
    return 0;
}
Prob D:有没有解比较,简单:构建反向图(本来是3能到6的,我加一条6到3的边),从n出发,广度优先搜索(bfs)一遍,能到1就有解。
同时记录下哪些点被经过了,这在原图中,就是能到n的点。
有人问了,那什么时候会是无穷长的解?
看下图:

注意我们要字典序最小,而不是长度最小
那么在3这个位置上,我们要不停地选a,而不能选b,否则,字典序上来讲:
aaba......在aaaa.......之后
这里的处理也比较简单:
1、从1出发,每个点贪心选择:如果走a,这个点能到n,那就走a,否则走b。
2、但如果走回到之前走过的点,那说明答案是Infinity。
#include <stdio.h>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;

vector<int> G[100005];
int a[100005],b[100005];
int n;
bool vis[100005];
bool vis2[100005];
char str[100005];

void rev_bfs(int p){
    queue<int> q;
    vis[p]=1;
    q.push(p);
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=0;i<G[x].size();i++){
            if(!vis[G[x][i]]){
                vis[G[x][i]]=1;
                q.push(G[x][i]);
            }
        }
    }
}

void input(int* a){
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        int tmp=i+a[i];
        if(tmp>=1&&tmp<=n)
            G[i+a[i]].push_back(i);
    }
}

int main(){
    scanf("%d",&n);
    input(a);
    input(b);
    rev_bfs(n);
    if(!vis[1]){
        puts("No solution!");
        return 0;
    }
    int p=0;
    vis2[1]=1;
    bool infflag=0;
    for(int x=1;x!=n&&!infflag;){
        int nxt=x+a[x];
        if(nxt>=1&&nxt<=n&&vis[nxt]){
            if(!vis2[nxt]){
                vis2[nxt]=1;
                str[p++]='a';
            }else{
                infflag=1;
            }
            x=nxt;
        }else{
            nxt=x+b[x];
            if(nxt>=1&&nxt<=n&&vis[nxt]){
                if(!vis2[nxt]){
                    vis2[nxt]=1;
                    str[p++]='b';
                }else{
                    infflag=1;
                }
            }else{
                puts("No solution!");
                return 0;
            }
            x=nxt;
        }
    }
    puts(infflag?"Infinity!":str);
    return 0;
}

Prob E:
这个问题,经典起手式:求[a,b]的问题,拆成求[1,b]-[1,a-1]的问题,而处理[1,x]的问题一般容易搞定不少。
OK,[1,x]怎么处理?
首先要意识到:在[1,x]中,有约数y的数有x/y个。
那么,假设x为常数,对函数f(y)=x/y,在y=1,2,3...n上的点用曲线近似,是反比例函数在第一象限的图像。
(图就靠大家自己画了~)
特点:前半段很陡峭,差个1,函数值都差远了;后半段很平坦,n/2个数的函数值都为1……
根据这两段的不同情况,我们采取不同的处理策略:
对前半段(比如,1到10万),我们直接算有多少个数包含这个约数x,直接加到答案上。
对后半段(10万以上),我们计算有多少个约数,只有1个、2个....个数的约数中包含他,这些数的取值范围是多少。
然后我们统计出这些约数中,有多少是以1、2、3...9开头的数,乘上相应的约数个数,加到答案中。
这样搞定了~基本是一个sqrt(n)*log(n)的时间复杂度。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef long long ll;

ll a[10],b[10],out[10];
int highest[100001];

void cnt(int l,int r){
    memset(out,0,sizeof(out));
    char tmp[15];
    while(l<=r){
        sprintf(tmp,"%d",r);
        int t2=tmp[0]-'0';
        for(int i=1;tmp[i];i++)t2=t2*10;
        if(t2<l)t2=l;
        out[tmp[0]-'0']+=r-t2+1;
        r=t2-1;
    }
}

void get(int x,ll* a){
    if(x==0)return;
    for(int i=1;i<=100000;i++){
        a[highest[i]]+=x/i;
    }
    if(x<=100000)return;
    int last=x;
    for(int i=1;;i++){
        int p2=x/(i+1);
        ++p2;
        if(p2<=100000)p2=100001;
        cnt(p2,last);
        for(int j=1;j<=9;j++)a[j]+=out[j]*i;
        last=x/(i+1);
        if(last<=100000)break;
    }
}


int main(){
    for(int i=1;i<=9;i++){
        for(int j=1;j<=10000;j*=10){
            for(int k=0;k<j;k++){
                highest[i*j+k]=i;
            }
        }
    }
    highest[100000]=1;
    int l,r;
    scanf("%d%d",&l,&r);
    get(r,b);get(l-1,a);
    for(int i=1;i<=9;i++)printf("%lld\n",b[i]-a[i]);
}
Prob F:
实现题。开个数组表示棋盘。
3类miss情况,放了子不能再放的miss 1很简单,不赘述。
miss 2,放下去以后自己没气的话,先考虑把对手的、没气的提掉,再检查自己有没有气,没气的话记得还原棋盘。
miss 3,出现重复情况的处理的话,骚年,hash都说得很熟练的话,不妨把这个棋盘也hash起来,用hash来高效查重啊?
反正这题就慢慢写吧~我写+调整了78分钟,红q写+调整了89分钟
(这题有点卡常数,大家要注意自己写出来的代码的效率,我TLE了3发,调了好几下才过……)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <vector>
#include <unordered_set>
using namespace std;
typedef unsigned int ull;

const int di[4][2]={{-1,0},{1,0},{0,-1},{0,1}};

struct Point{
    int x,y;
    Point(int a=0,int b=0):x(a),y(b){}
    Point go(int i)const {
        return Point(x+di[i][0],y+di[i][1]);
    }
    bool check()const {
        return x>=1&&x<=19&&y>=1&&y<=19;
    }
};

struct Board{
    int a[21][21];
    unordered_set<ull> state;
    
    void init(){
        memset(a,0,sizeof(a));
        state.clear();
    }
    void output(){
        char tmp[20]="\0";
        for(int i=1;i<=19;i++){
            for(int j=1;j<=19;j++){
                if(a[i][j]==0)
                    tmp[j-1]='.';
                else if(a[i][j]==1)
                    tmp[j-1]='B';
                else if(a[i][j]==2)
                    tmp[j-1]='W';
            }
            puts(tmp);
        }
    }
    ull hash(){
        ull ans=0;
        for(int i=1;i<=19;i++){
            for(int j=1;j<=19;j++){
                ans*=3;
                ans+=a[i][j];
                ans%=(int)(1e9+7);
            }
        }
        return ans;
    }
    int& ref(const Point& p){
        return a[p.x][p.y];
    }
}board,backup;

bool vis[21][21],vis2[21][21];
bool chi(const Point& p,bool vis[21][21]){
    bool alive=false;
    int chess=board.ref(p);
    queue<Point> q;
    q.push(p);
    vis[p.x][p.y]=1;
    while(!q.empty()){
        Point x=q.front();q.pop();
        for(int i=0;i<4;i++){
            Point y=x.go(i);
            if(!y.check())continue;
            if(board.ref(y)==0)alive=true;
            if(board.ref(y)==chess&&!vis[y.x][y.y]){
                vis[y.x][y.y]=1;
                q.push(y);
            }
        }
    }
    return alive;
}

int put(int type,const Point& p){
    int& place=board.ref(p);
    if(place!=0) return 1;
    
    backup=board;
    place=type;
    memset(vis,0,sizeof(vis));
    memset(vis2,0,sizeof(vis2));
    for(int direct=0;direct<4;direct++){
        Point x=p.go(direct);
        if(!x.check())continue;
        int i=x.x,j=x.y;
        if(!vis[i][j] && board.a[i][j]==3-type){
            if(!chi(Point(i,j),vis)){
                chi(Point(i,j),vis2);
            }
        }
    }
    
    for(int i=1;i<=19;i++){
        for(int j=1;j<=19;j++){
            if(vis2[i][j]){
                board.a[i][j]=0;
            }
        }
    }
    
    if(!chi(p,vis)){
        board=backup;
        return 2;
    }
    
    ull hash_val=board.hash();
    unordered_set<ull>::iterator it=board.state.find(hash_val);
    if(it!=board.state.end()){
        board=backup;
        return 3;
    }
    board.state.insert(hash_val);
    return 0;
}

template <class T>
inline void scan(T &ret) {
    char c; ret=0;
    while((c=getchar())<'0'||c>'9');
    while(c>='0'&&c<='9') ret=ret*10+(c-'0'),c=getchar();
}

template <class T>
inline void scan_c(T &c) {
    while((c=getchar()),!isalnum(c));
}

int main(){
    int T,n;
    for(scan(T);T--;){
        board.init();
        for(scan(n);n--;){
            char tmp[2];
            Point x;
            scan_c(tmp[0]);
            scan(x.x);scan(x.y);
            int ret=put(tmp[0]=='B'?1:2,x);
            if(ret){
                printf("miss %d\n",ret);
            }
        }
        board.output();
    }
    return 0;
}
UPD:这份代码在比赛的时候是通过了的。平时练习环境下,要想通过,需要做一些常数优化,比如:
(以下,按缩短的运行时间从多到少排序)
0、提掉没气的对手的子的时候,一个bfs/dfs过去,直接提了,不要像上面的代码一样堆起来,最后清。
1、手写一个数组模拟的循环队列。
STL的queue,底层用了deque,块状数组,非常非常慢!
手写个数组模拟的循环队列,快好多了!
(这个,自己写吧。)
2、在hash()函数内,把ans%=(int)(1e9+7)删掉。
这里写这句,是考虑到,很容易构造hash冲突的例子。http://codeforces.com/blog/entry/4898
但是后来了解到,要hash冲突,串长要挺长的……
3、miss 2类型的情况下,你不需要把整个棋盘还原回去,只要把这个棋子提起来即可。

当然,这个代码还有很多可以做常数优化的空间(应该还能优化出很多吧?),这就靠大家去慢慢折腾了。
0、1、2、3 这4点结合使用,时间基本稳定在1000ms左右。
UPD2:红q曾耀辉的代码真快,150ms。
全部评论
膜拜大神,官方的题解都是链接到你这里
点赞 回复 分享
发布于 2017-06-19 10:18
厉害
点赞 回复 分享
发布于 2017-06-17 18:34
厉害
点赞 回复 分享
发布于 2017-06-17 18:51
先给你手动点个赞,曾老师的直播马上就要开始了,直播地址:https://www.nowcoder.com/live/121
点赞 回复 分享
发布于 2017-06-17 19:07
6666
点赞 回复 分享
发布于 2017-06-17 19:59
大赞,永远都这么详细与用心。
点赞 回复 分享
发布于 2017-06-17 20:43
qls的题解,仅供参考:http://static.nowcoder.com/b/codem/codem_qulification.zip
点赞 回复 分享
发布于 2017-06-17 21:20
题目在线练习地址:https://www.nowcoder.com/test/5513596/summary   楼主帮忙补充到主贴吧~~
点赞 回复 分享
发布于 2017-06-17 21:22
六神这个讲解十分详细
点赞 回复 分享
发布于 2017-06-17 22:20
上来就是FFT
点赞 回复 分享
发布于 2017-06-18 23:08
谢lz! 资格赛时我一直是围棋那题过不了,TLE了好几发。这回在在线练习地址上复制lz的代码,依旧是TLE,一次是case通过率为25.00%另一次是case通过率为65.00%(哭
点赞 回复 分享
发布于 2017-06-19 03:31
优惠券那题,同一个编号的优惠券用掉之后还可以再次卖出吗?题目里面只说“每张优惠券有一个唯一的正整数编号 ”。另外,例子里的问号竟然是中文问号。太坑了!
点赞 回复 分享
发布于 2017-06-20 22:23
厉害
点赞 回复 分享
发布于 2018-05-21 19:40

相关推荐

早餐10,午餐25,晚餐35,饮品18,光吃就88😋
沟头学院:你这什么饮品呢?这么贵,还天天喝吗?还有饭钱,吃的啥呀一顿35😂,天天这么搞可受不了啊
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务