网易游戏编程题

标签(空格分隔): 算法 C++ 笔试


第三题:
描述
小王最近在开发一种新的游戏引擎,但是最近遇到了性能瓶颈。于是他打算从最基本的画线功能开始分析优化。画线其实就是调用一次drawline命令,根据给出的两端坐标,在屏幕画出对应的线段。但是小王发现,很多的drawline其实可以合并在一起,譬如下图中的线段(2,3)-(4,5)和线段(3,4)-(6,7),其实可以合并为一次drawline命令,直接画出线段(2,3)-(6,7)。当然有些线段是无法合并的,如线段(-3,8)-(1,8)和线段(3,8)-(6,8),就必须调用两次drawline命令。


14579403027824.jpg-62.6kB
14579403027824.jpg-62.6kB

画线示意图。注意颜色只是用于区分,实际线段都是黑色
给出N条drawline指令以及对应的线段坐标,小王想知道,实际最少用多少次drawline指令就可以画出来。
小王想先从最简单的情况开始分析优化,所以线段只包含四种情况:水平线段,垂直线段以及正反45度的线段。
输入
每个输入数据包含多个测试点。
第一行为测试点的个数 S ≤ 10。之后是 S 个测试点的数据。
每个测试点的第一行为 N(N ≤ 105)。之后是 N 行,每行包含4个整数:x0, y0, x1, y1,表示线段(x0,y0)-(x1,y1),坐标的范围在[-108, 108],保证线段的长度大于0。

输出
对于每个测试点,对应的结果输出一行,表示最少用多少次指令即可完成所有的画线。

样例输入

2
4
3 8 6 8
-3 8 1 8
2 3 4 5
3 4 6 7
5
1 1 2 2
2 2 3 3
3 3 4 2
4 2 5 1
1 0 100 0
样例输出
3
3
#include<iostream>
#include<algorithm>
#include<vector>
#include<tr1/unordered_map>
using namespace std::tr1;
using namespace std;

bool gongyueshu(vector<int> vec, vector<int> vec2,float xie){
    if(abs(vec[0]-vec2[2])==0&&xie!=-1) return false;
    else if(abs(vec[0]-vec2[2])==0&&xie==-1){
        if((vec[1]-vec2[3])<=((vec[1]-vec[3])+(vec2[1]-vec2[3]))) return true;
        else return false;
    }
    else if(abs(vec[0]-vec2[2])!=0){
        float duijiao = fabs(float(vec[1]-vec2[3]))/fabs(float(vec[0]-vec2[2]));
        if(duijiao-xie>0.01) return false;
        int len1 = abs(vec[3]-vec[1]);
        int len2 = abs(vec[2]-vec[0]);
        int len3 = abs(vec2[3]-vec2[1]);
        int len4 = abs(vec2[2]-vec2[0]);
        int len5=0,len6=0;
        if(vec[0]<vec2[0]){    
            len5 = abs(vec2[3]-vec[1]);
            len6 = abs(vec2[2]-vec[0]);
        }else{
            len5 = abs(vec[3]-vec2[1]);
            len6 = abs(vec[2]-vec2[0]);     
        }
        float len7 = sqrt(len1*len1+len2*len2);
        float len8 = sqrt(len3*len3+len4*len4);
        float len9 = sqrt(len5*len5+len6*len6);
        if(len9<=(len7+len8)) return true;
        else return false;
    }
}

bool less_(vector<int> v1,vector<int> v2){
    return v1[0]<v2[0]?true:false;
}

void print_vec(vector<vector<int>> vec){
    for(int i=0;i<vec.size();i++){
        cout<<vec[i][0]<<endl;
    }
} 

void sort_map(unordered_map<float,vector<vector<int>>>& map_){
    unordered_map<float,vector<vector<int>>> ::iterator it;
    for(it=map_.begin();it!=map_.end();++it){
        sort(it->second.begin(),it->second.end(),less_); //需要对整体进行排序
    }
}

int test(unordered_map<float,vector<vector<int>>> map_){
    int result=0;
    unordered_map<float,vector<vector<int>>> ::iterator it;
    for(it=map_.begin();it!=map_.end();++it){
        vector<vector<int>> vec = it->second;  
        if(vec.size()==1) ++result;
        else{
            ++result; 
            for(int j = 0;j<vec.size()-1;j++){
                if(gongyueshu(vec[j],vec[j+1],it->first));
                else ++result;
            }
        }
    }
    return result;
}

int main(){
    int num=0;
    cin>>num;
    while(num){
        unordered_map<float,vector<vector<int>>> map_;
        int row=0;
        cin>>row;
        while(row){
            vector<int> vec;
            int tmp;
            for(int j =0;j<4;j++){
                cin>>tmp;
                vec.push_back(tmp);
            }
            if(vec[2]-vec[0]==0) map_[-1].push_back(vec);
            else{
                float xl = (vec[3]-vec[1])/(vec[2]-vec[0]); //将同一个斜率的线放在一起。 
                map_[xl].push_back(vec);
            }
            row--;
        }
        num--;
        sort_map(map_);
        cout << endl<<test(map_);
    }
    return 0;
}
/*
2
4
3 8 6 8
-3 8 1 8
3 3 5 5
3 4 6 7

>> 4
*/

第二题:“源代码编译” 描述
在网易游戏的日常工作中,C++ 是一门常用的语言。面对众多的 C++ 代码,等待源文件编译的漫长时间是个令人糟心的时刻,一直以来大家对此怨声载道。终于有一天,大家找到了你,一位优秀的程序员,请你来帮忙分析一下编译速度的瓶颈。

经过一番调查和研究,你发现一些源代码之间是有依赖关系的。例如,某个源文件 a.cpp 编译链接生成了动态链接库 a.dll,而 b.cpp 编译链接生成的 b.dll 依赖于 a.dll。这个时候,必须等待 a.dll 生成之后才能生成 b.dll。为了表达简单,我们这个时候称 b.cpp 依赖于 a.cpp。

网易游戏内部使用了一个分布式并行的编译平台,可以同时编译多个不互相依赖的文件,大大提高了源代码的编译速度。然而当某些依赖链很长的时候,这个编译平台也无能为力,只能按照依赖顺序一个一个完成编译,从而造成了很长的编译时间。

为了验证这个想法,你决定着手通过代码分析这些文件之间的编译顺序。已知这些文件的文件名,以及这些文件所依赖的其他文件,你需要编写一个程序,输出一个可行的编译所有源文件的编译顺序。如果有多种可行的序列,请输出所有文件名序列中字典序最小的那一个(序列 (a1, a2, ..., an) 字典序小于序列 (b1, b2, ..., bn),当且仅当存在某个 i ,使得 ai 的字典序小于 bi,并且对于任意 j < i ,都有 aj = bj)。

输入
输入包含多组测试数据。

输入的第一行包含一个整数 T(T ≤ 100),表示输入中一共包含有 T 组测试数据。

每组测试数据第一行是一个整数 N(N ≤ 1000),表示一共有 N 个源代码文件。随后一共有 N 行数据,其中第 i(0 ≤ i < N) 行数据包含序号为 i 的源代码文件的依赖信息。每一行开头是一个字符串,表示这一个文件的文件名,随后一个整数 m(0 ≤ m ≤ N),表示编译这个源文件之前需要先编译 m 个依赖文件。之后是 m 个整数 j0 ... jm-1,表示这 m 个依赖文件的序号(0 ≤ j < N) 。所有的文件名仅由小写字母、数字或“.”组成,并且不会超过 10 个字符。保证 n 个源代码文件的文件名互不相同。

输出
对于每一组输入,按照编译先后顺序输出一组可行的编译顺序,一行一个文件名。如果有多种可行的序列,请输出所有文件名序列中字典序最小的那一个。如果不存在可行的编译顺序,输出一行 ERROR。每组测试数据末尾输出一个空行。

样例输入

3
2
a.cpp 0
b.cpp 1 0
2
cb 0
c 0
2
a.cpp 1 1
b.cpp 1 0
样例输出
a.cpp
b.cpp

c
cb

ERROR

这里用到的其实只是普通的拓扑排序

#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
//#include<tr1/unordered_map>
#include<map>
using namespace std;

int combine(vector<string>& res, int index, map<string,vector<int>> map_,vector<string>& sta){
     map<string,vector<int>> ::iterator it=map_.begin();
     while(index>0){
        ++it;
        index--;
     }
     vector<int> vec = it->second; 
     if(vec.size()==0){
        if(find(res.begin(),res.end(),it->first)==res.end()) res.push_back(it->first);
     } 
     else{
        if(find(sta.begin(),sta.end(),it->first)==sta.end()) sta.push_back(it->first);
        else return -1;
        int lens = vec.size(); //得到所依赖的文件
        for(int i=0;i<lens;i++) {
            int sign=combine(res,vec[i],map_,sta);
            if(sign == -1){
                return -1;
            }
            sta.pop_back();
        }
        if(find(res.begin(),res.end(),it->first)==res.end()) res.push_back(it->first);
     }
     return 1;  
}


void test(map<string,vector<int>> map_){
    int result=0;
    map<string,vector<int>> ::iterator it;
    vector<string> res(map_.size(),""); //定义结果集合 
    vector<string> record;
    for(int i =0;i<map_.size();i++){
        int sign = combine(res,i,map_,record);
        if(sign ==-1){
            cout<<"ERROR";
            return;
        } 
    }
    int size = res.size();
    if(size==0) cout<<"ERROR";
    else{
        for(int i=0;i<size;i++){
            cout<<res[i]<<endl;
        }
    }
}

int main(){
    int num=0;
    cin>>num;
    while(num){
        map<string,vector<int>> map_;
        int row=0;
        cin>>row;
        while(row){
            string file_name = "";
            cin>>file_name;
            vector<int> indexs;
            int index_nums;
            cin>>index_nums;
            while(index_nums>0){
                int index=0;
                cin>>index;
                indexs.push_back(index);
                index_nums--;
            }
            map_[file_name] = indexs;
            row--;
        }
        num--;
        test(map_);
        cout<<endl;
    }
    return 0;
}

第一道题的暴力解法:

#include <iostream>
#include <map>
#include <vector>
using namespace std;

map<int, vector<int>> dict;
(这里有几个点很有特殊性,首先,init()进行一个数组的初始化,这样省去了很多的细节的判断)
void init() {
    vector<int> v1 = {0, 0, 1, 0, 0, 1, 0};
    vector<int> v2 = {1, 0, 1, 1, 1, 0, 1};
    vector<int> v3 = {1, 0, 1, 1, 0, 1, 1};
    vector<int> v4 = {0, 1, 1, 1, 0, 1, 0};
    vector<int> v5 = {1, 1, 0, 1, 0, 1, 1};
    vector<int> v6 = {1, 1, 0, 1, 1, 1, 1};
    vector<int> v7 = {1, 0, 1, 0, 0, 1, 0};
    vector<int> v8 = {1, 1, 1, 1, 1, 1, 1};
    vector<int> v9 = {1, 1, 1, 1, 0, 1, 1};
    vector<int> v0 = {1, 1, 1, 0, 1, 1, 1};
    dict[0] = v0;
    dict[1] = v1;
    dict[2] = v2;
    dict[3] = v3;
    dict[4] = v4;
    dict[5] = v5;
    dict[6] = v6;
    dict[7] = v7;
    dict[8] = v8;
    dict[9] = v9;
}
(搞明白这几个参数的意义,pos是每一轮的index坐标,num是上一轮得到的最大值,result是结果值)这是个很好的递归的方法)
void helper(vector<vector<int>> &leds, int pos, int num, int N, int &result) {
    if (pos >= leds.size()) {
        if (num < N)
            result += 1;
        return;
    }
    for (int i = 0; i <= 9; i++) {
        bool isOk = true;
        for (int j = 0; j < leds[pos].size(); j++) {
            int led = leds[pos][j];
            if (dict[i][led - 1] == 0) {
                isOk = false;
                break;
            }
        }
    if (isOk) helper(leds, pos + 1, num * 10 + i, N, result);
    }
}

int solve(vector<vector<int>> &leds, int N) {
    int result = 0;
    helper(leds, 0, 0, N, result);
    return result;
}

int main() {
    init();
    int cases = 0;
    cin>>cases;
    for (int i = 0; i < cases; i++) {
        int K, N;
        cin>>K>>N;
        vector<vector<int>> leds;
        cin.get();
        for (int j = 0; j < K; j++) {
            char str[100];  //这个比较困难一点,因为中间有若干个空格,所以,这里需要进行判断
            cin.get(str, 100);
            vector<int> tmp;
            for (int i = 0; i < 100; i ++) {
                if (str[i] == ' ') continue;
                else if (str[i] >= '0' && str[i] <= '9')
                    tmp.push_back(str[i] - '0');
                else break;
            }
            leds.push_back(tmp);
            cin.get();
        }
        cout<<solve(leds, N)<<endl;
    }
}
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int long_vector(string str ,int i){
    int res=0;
    int j=1;
    while(i-j>=0&&i+j<str.size()){
        if(str[i-j]==str[i+j]){
            res++;
            j++;
        } 
        else break;
    }
    return res*2+1;
}

//找中间有一个数,两边相等的最长回文 的个数 
int find_best(string str){
    int res=1;
    for(int i=0;i<str.size();i++){
        if(long_vector(str,i)>res){
            res = long_vector(str,i);
        }
    }
    return res+1; 
}

bool check(string s){
    for(int i=0;i<s.size()-1;i++){
        if(s[i]==s[i+1]) return false;
    }
    return true;
}

int long_string_(string s,string& res){
    string str;
    char sign =' ';
    for(int i=0;i<s.size()-1;i++){
        if(sign!=' '&&s[i]==sign) continue;
        else if(sign!=' '&&s[i]!=sign){
            sign=' ';
        }
        if(str.size()==0||s[i]!=s[i+1]){
            str= str +s[i]; 
            if(i == s.size()-2) str= str +s[i+1];   
        } 
        else if(s[i]==s[i+1]){
            sign = s[i];
        }
    }

    int result = s.size()-str.size();
    if(check(str) ==false) result += long_string_(str,res);
    else res = str;
    return result;  
}

int main(){
    int row=0;
    cin>>row;
    while(row>0){
        string str="";
        string res="";
        cin>>str;
        int lens = long_string_(str,res);
        if(lens==str.size()) cout<< lens+1;
        else cout<< lens+find_best(res);
        row--;
    }
    return 0;
}

找回文子串如果是这么找就一定会超时:

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

int long_vector(string str ,int i){
    int res=0;
    int j=1;
    while(i-j>=0&&i+j<str.size()){
        if(str[i-j]==str[i+j]){
            res++;
            j++;
        } 
        else break;
    }
    return res*2+1;
}

//找中间有一个数,两边相等的最长回文 的个数 
int find_best(string str){
    int res=1;
    for(int i=0;i<str.size();i++){
        if(long_vector(str,i)>res){
            res = long_vector(str,i);
        }
    }
    return res; 
}

int main(){
    int row=0;
    cin>>row;
    while(row>0){
        string str="";
        cin>>str;
        cout<< find_best(str);
        row--;
    }
    return 0;
}

后缀数组如何解决最长回文子串:

如下目标字符串:bananas 其长度为7,则后缀数组的长度为7,分别是以b开头的子串(长度为7),以a开头的子串(长度为6),以n开头的子串(长度为5)....最后一个是以s开头的子串(长度为1)。

最朴素的算法是,让后缀数组之间两两比较,找出最长的公共子串(注意,这里的最长的公共子串必须是以首字符参与匹配的,如果首字母都不匹配,那么长度为0,比如,后缀[0]和后缀1之间的首字母不匹配,则两者的最长公共子串长度为0.),但是时间复杂度为O(n^2)。

后缀数组的应用:
例1:最长公共前缀
(给定一个字符串,询问某两个后缀的最长公共前缀)

这道题好像最好的方法是用到后缀树

后缀是指从某个位置i开始到整个串末尾结束的一个特殊子串。字符串r的从第i个字符开始的后缀表示为Suffix(i),也就是Suffix(i) = r[i...len(r)]。

后缀数组(SA[i]存放排名第i大的子串首字符下标)
后缀数组 SA 是一个一维数组,它保存1...n 的某个排列 SA1,SA2,......,SA[n],并且保证Suffix(SA[i])< Suffix(SA[i+1]), 1<=i< n. 也就是将S 的n个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入SA中。

后缀数组举例:
如下目标字符串:bananas 其长度为7,则后缀数组的长度为7,分别是以b开头的子串(长度为7),以a开头的子串(长度为6),以n开头的子串(长度为5)....最后一个是以s开头的子串(长度为1)。

名次数组 Rank[i] 保存的是 Suffix(i) 在所有后缀中从小到大排列的“名次”。

关于字符串大小的比较:关于字符串的大小比较,是指通常所说的“字典顺序”比较,也就是对于两个字符串u、v,令i从1开始顺次比较u[i]和v[i],如果u[i]=v[i]则令i加1,否则若u[i]<v[i]则认为u<v,u[i]>v[i]则认为u>v(也就是v<u),比较结束。如果i>len(u)或者i>len(v)仍未比较结果,那么若len(u)<len(v)则认为u<v,若len(u) ==
len(v)则认为u = v,若 len(u)>len(v)则u>v。

从字符串的大小比较定义来看,S的开头两个位置后缀u和v进行比较的结果不可能是相等,因为u==v的必要条件len(u)=len(v) 是不可能满足的。

对于约定的字符串S,从位置i开头的后缀直接写成Suffix(i),省去参数S。

倍增法构造后缀数组
最直接的方法,当然是把S的后缀都看作是一些普通的字符串,按照一般字符串排序的方法对它们从小到大进行排序。 不难看出这样的方法是很笨拙的,因为它没有利用各个后缀之间的有机联系,所以它的效率不高。倍增算法正式充分利用了各个后缀之间的联系,将构造后缀数组的数组的最坏时间复杂度成功降到了O(nlogn);

注:这个是排序的关键字~

算法目标:
求得串的sa数组和rank数组
易知sa和rank互为逆操作,即sa[rank[i]] = i; Rank[sa[i]] = i;(所以我们只要求得其一,就能O(n) 算出另一个)
注:这个结论只在最后完成排序的时候符合。
但sa和rank的定义一直都是适用的。
原因是最后的时候不会存在相同(rank相等)的两个子串。

算法基本流程

  • 设排序的当前长度是h。Suffix(i,h)
  • 先按H=1,对suffix(i,H)(0<i<s.length)排序。

说到回文子串的几种方法:
暴力方法,求出每一个子串,之后判断是不是回文,找到最长的那个。
求每一个子串时间复杂度O(N2),判断子串是不是回文O(N),两者是相乘关系,所以时间复杂度为O(N3)。

string findLongestPalindrome(string &s)
{
    int length=s.size();//字符串长度
    int maxlength=0;//最长回文字符串长度
    int start;//最长回文字符串起始地址
    for(int i=0;i<length;i++)//起始地址
        for(int j=i+1;j<length;j++)//结束地址
        {
            int tmp1,tmp2;
            for(tmp1=i,tmp2=j;tmp1<tmp2;tmp1++,tmp2--)//判断是不是回文
            {
                if(s.at(tmp1)!=s.at(tmp2))
                    break;
            }
            if(tmp1>=tmp2&&j-i>maxlength)
            {
                maxlength=j-i+1;
                start=i;
            }
        }
        if(maxlength>0)
            return s.substr(start,maxlength);//求子串
        return NULL;
}

动态规划
回文字符串的子串也是回文,比如P[i,j](表示以i开始以j结束的子串)是回文字符串,那么P[i+1,j-1]也是回文字符串。这样最长回文子串就能分解成一系列子问题了。这样需要额外的空间O(N2),算法复杂度也是O(N2)。

首先定义状态方程和转移方程:
P[i,j]=0表示子串[i,j]不是回文串。P[i,j]=1表示子串[i,j]是回文串。P[i,i]=1。
P[i,j] =P[i+1,j-1],if(s[i]==s[j])
P[i,j] =0,if(s[i]!=s[j])

string findLongestPalindrome(string &s)
{
    const int length=s.size();
    int maxlength=0;
    int start;
    bool P[50][50]={false};
    for(int i=0;i<length;i++)//初始化准备
    {
        P[i][i]=true;
        if(i<length-1&&s.at(i)==s.at(i+1))
        {
            P[i][i+1]=true;
            start=i;
            maxlength=2;
        }
    }
    for(int len=3;len<length;len++)//子串长度
        for(int i=0;i<=length-len;i++)//子串起始地址
        {
            int j=i+len-1;//子串结束地址
            if(P[i+1][j-1]&&s.at(i)==s.at(j))
            {
                P[i][j]=true;
                maxlength=len;
                start=i;
            }
        }
    if(maxlength>=2)
        return s.substr(start,maxlength);
    return NULL;
}

中心扩展
中心扩展就是把给定的字符串的每一个字母当做中心,向两边扩展,这样来找最长的子回文串。算法复杂度为O(N^2)。
但是要考虑两种情况:
1、像aba,这样长度为奇数。
2、想abba,这样长度为偶数。

string findLongestPalindrome(string &s)
{
    const int length=s.size();
    int maxlength=0;
    int start;

    for(int i=0;i<length;i++)//长度为奇数
    {
        int j=i-1,k=i+1;
        while(j>=0&&k<length&&s.at(j)==s.at(k))
        {
            if(k-j+1>maxlength)
            {
                maxlength=k-j+1;
                start=j;
            }
            j--;
            k++;
        }
    }

    for(int i=0;i<length;i++)//长度为偶数
    {
        int j=i,k=i+1;
        while(j>=0&&k<length&&s.at(j)==s.at(k))
        {
            if(k-j+1>maxlength)
            {
                maxlength=k-j+1;
                start=j;
            }
            j--;
            k++;
        }
    }
    if(maxlength>0)
        return s.substr(start,maxlength);
    return NULL;
}

4、Manacher法 》》算法介绍
Manacher法只能解决例如aba这样长度为奇数的回文串,对于abba这样的不能解决,于是就在里面添加特殊字符。我是添加了“#”,使abba变为a#b#b#a。这个算法就是利用已有回文串的对称性来计算的,具体算法复杂度为O(N),我没看出来,因为有两个嵌套的for循环。
具体原理参考这里。
测试代码中我没过滤掉“#”。

#define min(x, y) ((x)<(y)?(x):(y))
#define max(x, y) ((x)<(y)?(y):(x))
string findLongestPalindrome3(string s)
{
    int length=s.size();
    for(int i=0,k=1;i<length-1;i++)//给字符串添加 #
    {
        s.insert(k,"#");
        k=k+2;
    }
    length=length*2-1;//添加#后字符串长度
    int *rad=new int[length]();
    rad[0]=0;
    for(int i=1,j=1,k;i<length;i=i+k)
    {
        while(i-j>=0&&i+j<length&&s.at(i-j)==s.at(i+j))
            j++;
        rad[i]=j-1; //找出对应的半径。
        for(k=1;k<=rad[i]&&rad[i-k]!=rad[i]-k;k++)//镜像,遇到rad[i-k]=rad[i]-k停止,这时不用从j=1开始比较
            rad[i+k]=min(rad[i-k],rad[i]-k);
        j=max(j-k,0);//更新j
    }
    int max=0;
    int center;
    for(int i=0;i<length;i++)
    {
        if(rad[i]>max)
        {
            max=rad[i];
            center=i;
        }
    }
    return s.substr(center-max,2*max+1);
}

字符串处理题目:
输入
输入包括多行。每行是一个字符串,长度不超过200。
一行的末尾与下一行的开头没有关系。

输出
输出包含多行,为输入按照描述中变换的结果。

样例输入
The Marshtomp has seen it all before.
marshTomp is beaten by fjxmlhx!
AmarshtompB

样例输出
The fjxmlhx has seen it all before.
fjxmlhx is beaten by fjxmlhx!
AfjxmlhxB

#include<iostream>
#include<vector>
#include<cctype>
#include<string>
#include<algorithm>

using namespace std;

void deal_str(string str_lower){
    for(int i=0;i<str_lower.size();i++){
        string tmp =str_lower.substr(i,9);
        transform(tmp.begin(),tmp.end(),tmp.begin(), ::tolower);
        if(tmp.compare("marshtomp")==0){
            str_lower = str_lower.substr(0,i) + "fjxmlhx" + str_lower.substr(i+9);
        } 
    }
    cout<<str_lower<<endl; 
} 

int main(){
    string str;
    while(getline(cin,str)){  在本地虽然
        deal_str(str);
    }
    return 0;
}

01背包(复习下动态规划法)

输入
每个测试点(输入文件)有且仅有一组测试数据。
每组测试数据的第一行为两个正整数N和M,表示奖品的个数,以及小Ho手中的奖券数。
接下来的n行描述每一行描述一个奖品,其中第i行为两个整数need(i)和value(i),意义如前文所述。

测试数据保证
对于100%的数据,N的值不超过500,M的值不超过10^5
对于100%的数据,need(i)不超过2*10^5, value(i)不超过10^3

输出
对于每组测试数据,输出一个整数Ans,表示小Ho可以获得的总喜好值。

样例输入
5 1000
144 990
487 436
210 673
567 58
1056 897
样例输出
2099

#include<iostream>
#include<string>
#include<vector>
using namespace std;
vector<int> c; //重量
vector<int> v; //价值
vector<vector<int>> result; //表

///f()函数,计算在i+1个物品和重量上限j的条件下的最大背包价值

int f(int i,int j) //第i个物品,重量上限j  //0号物品即第一个物品
{
    if (i == 0&&c[i]<=j) //0号物品且重量小于上限
    {
        return v[i]; //把0号物品放入背包,背包价值为第0号物品的价值
    }
    if (i == 0 && c[i] > j)  //0号物品且重量大于上限
    {
        return 0; //物品放不进背包,此时背包为空,背包价值为0
    }
    //不是0号物品的情况
    if (i != 0 && j-c[i] >= 0) //i号物品可以放入背包 
    {
        //判断放入和不放入两种情况下背包的价值,选择价值大的方案
        return (result[i - 1][j - c[i]] + v[i]) > result[i - 1][j] ? (result[i - 1][j - c[i]] + v[i]) : result[i - 1][j];
    }          //把这个物品放入背包                //不放入背包
    else //i号物品不可以放入背包
    return  result[i - 1][j];
}

int getResult(int top, int num)
{
    if (num == 0) //有0个物品
        return 0;
    else
    {
        for (int i = 0; i < num; i++) //第i个物品
        {
            for (int j = 0; j <= top; j++) //重量  我觉得会超时的地方在于这个重量!!,如果重量很大
            {
                result[i][j] = f(i,j);     
                //建表,result[i][j]表示有0,1,2...i个物品和j的重量限制下的最大背包价值
            }
        }
        return result[num-1][top];
    }
}

int main()
{
    int num; //物品数量
    int top; //背包容量
    scanf("%d",&num);
    scanf("%d",&top);
    for (int i = 0; i < num; i++) //第i个物品的重量和价值
    {
        vector<int> row(top+1,0);
        result.push_back(row);
        int need=0,value=0;
        scanf("%d",&need);
        scanf("%d",&value);
        c.push_back(need);
        v.push_back(value);
    }
    cout << getResult(top, num) << endl;
    return 0;
}

最长回文子串 AC的解法:

#include<iostream>  
#include<string>  

int Min(int a, int b)
{
    if (a < b)
        return a;
    return b;
}

int LPS(std::string &s)
{
    std::string new_s="";
    int s_len = s.length();

    new_s.resize(2 * s_len + 3);

    int new_s_len = new_s.length();
    
    new_s[0] = '$';
    new_s[1] = '#';
    new_s[new_s_len - 1] = '@';

    for (int i = 0,j=2; i < s_len; ++i)
    {
        new_s[j++] = s[i];
        new_s[j++] = '#';
    }
    
    int *p = new int[new_s_len + 1];
    int id = 0;//记录已经查找过的边界最靠右回文串的中心
    int maxLPS = 1;
    p[0] = 1;
    for (int i = 1; i < new_s_len-1; ++i)
    {
        if (p[id] + id - 1 >= i)//有交集的情况
            p[i] = Min(p[2 * id - i], p[id] + id - i);
        else//无交集的情况
            p[i] = 1;

        while (new_s[i - p[i]] == new_s[i + p[i]])//确定能伸展多少,上面if的情况是不会执行这个循环的
            ++p[i];

        if (p[id] + id < p[i] + i)//重新确定伸展最右的回文子串中心
            id = i;
        if (p[i]>maxLPS)//保存当前最长回文子串的长度(还要-1)
            maxLPS = p[i];
    }
    delete[] p;
    return maxLPS - 1;
}

int main()
{
    int N;
    std::string s;
    std::cin >> N;
    while (N--)
    {
        std::cin >> s;
        std::cout<<LPS(s)<<std::endl;
    }
    return 0;
}

在无序地情况下进行二分查找(用快排进行实现,但是快排有个最大的问题,就是如果是在有序地情况下会退化成冒泡排序,所以这个地方的种子数据,就需要选的相对随机一点)

输入

第1行:2个整数N,K。N表示数组长度,K表示需要查找的数;
第2行:N个整数,表示a[1..N],保证不会出现重复的数,1≤a[i]≤2,000,000,000。 (注意,这里2000000000< 2147483647 所以用Int 型就可以了,4个字节可以表示的数也是很大的。)

输出

第1行:一个整数t,表示K在数组中是第t小的数,若K不在数组中,输出-1。

样例输入
10 5180
2970 663 5480 4192 4949 1 1387 4428 5180 2761
样例输出
9

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>

using namespace std;

int my_find(vector<int> &nums,int target,int left_,int right_){
    int left=left_,right=right_;
    if(left==right&&nums[left]!=target) return -1;
    swap(nums[left],nums[(left+right)/2]);
    int cur = nums[left];
    while(left<right){
        while(left<right&&nums[right]>=cur) right--;
        if(left<right) nums[left] = nums[right];
        while(left<right&&nums[left]<=cur) left++;
        if(left<right) nums[right] = nums[left];        
    }
    nums[left] = cur;
    
    if(target==cur) return left+1;
    else if(target<cur) return my_find(nums,target,left_,left-1);
    else if(target>cur) return my_find(nums,target,left+1,right_);
}

int main(){
    int row=0,target=0;
    vector<int> nums;
    scanf("%d",&row);
    scanf("%d",&target);
    int num=0;
    while(row>0){
        scanf("%d",&num);
        nums.push_back(num);
        row--;
    }
    cout<<my_find(nums,target,0,nums.size()-1);
    return 0;
}

二分查找之第第K小的数,以及二分查找之第k大的数的标准做法

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>

using namespace std;

int my_find(vector<int> &nums,int target,int left_,int right_){
    int left=left_,right=right_;
    if(left==right&&left!=target) return -1;
    swap(nums[left],nums[(left+right)/2]);
    int cur = nums[left];
    while(left<right){
        while(left<right&&nums[right]>=cur) right--;
        if(left<right) nums[left] = nums[right];
        while(left<right&&nums[left]<=cur) left++;
        if(left<right) nums[right] = nums[left];        
    }
    nums[left] = cur;
    
    if(target==left) return cur;
    else if(target<left) return my_find(nums,target,left_,left-1);
    else if(target>left) return my_find(nums,target,left+1,right_);
}

int main(){
    int row=0,target=0;
    vector<int> nums;
    scanf("%d%d",&row,&target);
    int num=0;
    while(row>0){
        scanf("%d",&num);
        nums.push_back(num);
        row--;
    }
    cout<<my_find(nums,target-1,0,nums.size()-1); //第k小的数
    // cout<<my_find(nums,nums.size()-target,0,nums.size()-1); //第k大的数
    return 0;
}

tip:
对于字符串,用find函数去寻找指定的内容的话,如果能找到了,就是返回下标,如果没有找到,就是返回s.npos

网易游戏编程题
第一题:图像处理


图像处理题.png-107.6kB
图像处理题.png-107.6kB

输入
每个输入数据包含多个测试点。

输入数据的第一行为S(S <= 10),表示测试数据有S个测试点。

测试点的第一行为N, M (1 <= N, M <= 100),表示矩阵的大小。之后N行,每行包含M个数字,表示对应的像素值,数字的范围在[0...255]。

之后一行是数T(1 <= T <= 50)。之后T行,每行表示一个操作。每行的第一个数表示操作类型。

其中,对于操作类型5和6,该行还会包括5个数x0, y0, x1, y1, value(0 < value <= 255)。分别表示区域左上角(x0, y0)以及区域右下角(x1, y1),该区域中所有像素值增加/减少value。对于操作类型7,该行还会包括4个数x0, y0, x1, y1,表示裁剪的区域左上角(x0, y0)和区域右下角(x1, y1)。

保证所有操作均合法,操作中指定的区域一定是矩阵中的合法区域。保证最后的矩形一定非空。

输出
对应每个测试点输出一行,包括四个数字,分别表示最后矩阵的大小,左上角(0, 0)的像素大小,以及所有像素值的总和。

样例输入

2
3 4
1 2 3 4
5 6 7 8
9 0 1 2
3
1
7 1 0 3 1
5 1 0 1 1 5
2 2
7 8 
7 2
1
1

样例输出

3 2 0 34
2 2 7 24 

我的答案:

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>

using namespace std;
vector<vector<int>> case1(vector<vector<int>>& vec){
    int m= vec.size();
    int n= vec[0].size();
    vector<vector<int>> new_vec(n,vector<int>(m));
    for(int i=0;i<m;i++)
        for(int j=0;j<n;j++){
            new_vec[j][m-i-1] = vec[i][j];
        }
    return new_vec;
}

vector<vector<int>> case2(vector<vector<int>>& vec){
    int m= vec.size();
    int n= vec[0].size();
    vector<vector<int>> new_vec(n,vector<int>(m));
    for(int i=0;i<m;i++)
        for(int j=0;j<n;j++){
            new_vec[n-1-j][i] = vec[i][j];
        }
    return new_vec;
}

vector<vector<int>> case3(vector<vector<int>>& vec){
    int m= vec.size();
    int n= vec[0].size();
    vector<vector<int>> new_vec(m,vector<int>(n));
    for(int i=0;i<m;i++)
        for(int j=0;j<n;j++){
            new_vec[m-1-i][j] = vec[i][j];
        }
    return new_vec;
}

vector<vector<int>> case4(vector<vector<int>>& vec){
    int m= vec.size();
    int n= vec[0].size();
    vector<vector<int>> new_vec(m,vector<int>(n));
    for(int i=0;i<m;i++)
        for(int j=0;j<n;j++){
            new_vec[i][n-1-j] = vec[i][j];
        }
    return new_vec;
}

void case5(vector<vector<int>>& vec,int x0,int y0,int x1,int y1,int value){
    int m= vec.size();
    int n= vec[0].size();
    for(int i=x0;i<=x1;i++)
        for(int j=y0;j<=y1;j++){
            vec[i][j]+=value;
            if(vec[i][j]>255) vec[i][j]=255;
        }
}

void case6(vector<vector<int>>& vec,int x0,int y0,int x1,int y1,int value){
    int m= vec.size();
    int n= vec[0].size();
    for(int i=x0;i<=x1;i++)
        for(int j=y0;j<=y1;j++){
            vec[i][j]-=value;
            if(vec[i][j]<0) vec[i][j]=0;
        }
}

vector<vector<int>> case7(vector<vector<int>>& vec,int x0,int y0,int x1,int y1){
    int m= vec.size();
    int n= vec[0].size();
    int m1 = x1-x0+1;
    int n1 = y1-y0+1;
    vector<vector<int>> new_vec(m1,vector<int>(n1));
    for(int i=x0;i<=x1;i++)
        for(int j=y0;j<=y1;j++){
            new_vec[i-x0][j-y0] = vec[i][j];
        }
    return new_vec;
}

void res(vector<vector<int>> vec){
    int m= vec.size();
    int n= vec[0].size();
    int val = vec[0][0];
    int res_=0;
    for(int i=0;i<m;i++)
        for(int j=0;j<n;j++){
            res_+=vec[i][j];
        }
    cout<<m<<" "<<n<<" "<<val<<" "<<res_<<endl;
}

int main(){
    int row=0;
    int m=0,n=0,value=0,line=0,order=0;
    scanf("%d",&row);
    while(row>0){
        scanf("%d",&m);
        scanf("%d",&n);
        vector<vector<int>> vec(m,vector<int>(n));
        for(int i=0;i<m;i++)
            for(int j=0;j<n;j++){
                scanf("%d",&value);
                vec[i][j] = value;
        }
        scanf("%d",&line);
        while(line){
            scanf("%d",&order);
            if(order<5){
                if(order==1) vec=case1(vec);
                if(order==2) vec=case2(vec);
                if(order==3) vec=case3(vec);
                if(order==4) vec=case4(vec);
            }else{
                int x0,x1,y0,y1;
                if(order==5){
                    int value;
                    scanf("%d",&x0);
                    scanf("%d",&x1);
                    scanf("%d",&y0);
                    scanf("%d",&y1);
                    scanf("%d",&value); 
                    case5(vec,x0,x1,y0,y1,value);               
                }
                if(order==6){
                    int value;                  
                    scanf("%d",&x0);
                    scanf("%d",&x1);
                    scanf("%d",&y0);
                    scanf("%d",&y1);
                    scanf("%d",&value); 
                    case6(vec,x0,x1,y0,y1,value);               
                }
                if(order==7){                   
                    scanf("%d",&x0);
                    scanf("%d",&x1);
                    scanf("%d",&y0);
                    scanf("%d",&y1);    
                    vec=case7(vec,x0,x1,y0,y1);             
                }
            }
            --line;
        }
        res(vec);
        row--;
    }
    return 0;
}

一起消消毒

一起消消毒.png-218.8kB
一起消消毒.png-218.8kB

(这道题用bool表进行维护是正确的,因为通过bool表,可以相互不会干扰,方便进行统一赋值)

#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;

bool v[25][25];
char k[25][25];
int n,m,s,xa,ya,xb,yb;
    
int adjust()
{
    int count,p,ret=0;
    memset(v,0,sizeof(v));
    for(int i=0;i<n;i++){
        count=1;
        for(int j=1;j<m;j++){
            if(k[i][j]!=k[i][j-1]){
                if(count>=3){
                    for(int p=j-count;p<j;p++){
                        v[i][p]=1;
                    }
                }
                count=1;
            }else{
                count++;
            }
        }
        if(count>=3){
            for(int p=m-count;p<m;p++){
                v[i][p]=1;
            }
        }
    }
    for(int j=0;j<m;j++){
        count=1;
        for(int i=1;i<n;i++){
            if(k[i][j]!=k[i-1][j]){
                if(count>=3){
                    for(int p=i-count;p<i;p++){
                        v[p][j]=1;
                    }
                }
                count=1;
            }else{
                count++;
            }
        }
        if(count>=3){
            for(int p=n-count;p<n;p++){
                v[p][j]=1;
            }
        }
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(v[i][j]==1){
                if(k[i][j]!='.'){
                    k[i][j]='.';
                    ret++;
                }
            }
        }
    }
    for(int j=0;j<m;j++){
        p=n-1;
        for(int i=n-1;i>=0;i--){
            if(k[i][j]!='.'){
                k[p][j]=k[i][j];
                p--;
            }
        }
        for(;p>=0;p--){
            k[p][j]='.';
        }
    }
    return ret;
}

int main()
{
    int sum,r;
    scanf("%d",&s);
    while(s--)
    {
        sum=0;
        
        //矩阵的初始化 
        memset(k,0,sizeof(k));
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++){
            scanf("%s",k[i]); //原来这样也可以。 
        }
        
        // 这个部分只是做一个交换      
        scanf("%d%d%d%d",&xa,&ya,&xb,&yb);
        char tmp=k[xa][ya];
        k[xa][ya]=k[xb][yb];
        k[xb][yb]=tmp;
        
        while(1)
        {
            r=adjust(); //直接调用adjust函数 
            sum+=r;
            if(r==0){
                break;
            }
        }
        printf("%d\n",sum);
    }
    return 0;
}

题目3 : 神奇的数

时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述

Celin一直认为万物皆数,他总会花上很多的时间去研究和数相关的一些问题。最近他在研究一种神奇的数,这种数包含以下3个特征:
(1) 这个数至少包含('2', '3', '5')中的任意一个数字;
(2) 这个数不能出现'18';
(3) 这个数能被7整除。
如217,280,1393,9520均为同时满足三个条件的神奇的数。
而140,798不符合条件(1),518,1183不符合条件(2),12,1727不符合条件(3),这些数均不是Celin要找的神奇的数。
给出一个范围[N, M],Celin想知道在范围内(包括N和M两个边界在内)一共有多少个符合条件的神奇的数。
输入

每个输入数据包含多个测试点。
第一行为测试点的个数S <= 100,然后是S个测试点的数据。
每个测试点为一行数据,该行包含两个数N, M (1 <= N <= M <= 1018),表示范围。
hihoCoder平台的长整形为long long。数据范围较大,请注意优化您的算法。
输出

对应每个测试点的结果输出一行,表示范围内总共有多少个神奇的数。
样例输入
3
1 100
200 210
1000 1005
样例输出
6
2
0
思路:三个条件相对独立,每个条件单独的都有数位DP的例题,所以这里的处理方式是针对不同的条件设置了不同的状态空间,每个条件的判定相对容易。

#include <stdio.h>  
#include <string.h>  
#include <algorithm>  

using namespace std;  

int dp[20][2][2][7];
int digit[20];  

long long dfs(int len,bool state, int has, int sum, bool fp)  
{  
   if(!len)
   {
      if(has == 1 && sum == 0)  
      {
         return 1;
      }
      else return 0;  
   }

   if(!fp && dp[len][state][has][sum] != -1)  
   {
      return dp[len][state][has][sum]; 
   }

   long long ret = 0 , fpmax = fp ? digit[len] : 9;  
   for(int i = 0; i <= fpmax; i++)  
   {  
      // 不含18
      if(state && i == 8)  
         continue;  

      //含有2,3,5
      int prehas = has;
      int presum = sum;
      sum *= 10;
      sum += i;
      sum %= 7;

      if(!has && (i == 2 || i == 3 || i == 5))
      {
         has = 1;
      }

      ret += dfs(len - 1,i == 1, has, sum, fp && i == fpmax);  
      has = prehas;
      sum = presum;
   }  

   if(!fp)  
   {
      dp[len][state][has][sum] = ret;  
   }

   return ret; 
}  

long long f(long long n)  
{  
   long long len = 0;  
   while(n)  
   {  
      digit[++len] = n % 10;  
      n /= 10;  
   } 
   return dfs(len,false, 0, 0, true);  
}  

int main()  
{  
   long long a,b;  
   memset(dp, -1, sizeof(dp));  

   while(scanf("%lld %lld", &a, &b) != EOF)  
   {   
      printf("%lld\n", f(b) - f(a - 1));  
   }  

   return 0;  
}  

网易游戏有面试一个其他的题目:
A:给你一亿个数字,然后找出两个唯一相同的数字出来:
(首先一亿的数字,占400M内存,内存应该是够的)
因为相比来说,异或是处理速度比哈希的处理速度要快,所以,不妨把所有的数字先异或一遍,这样就能够得到不含相同的数字的异或结果;然后再将每一个数字与该数字进行一次异或,保存在一个unordered_map里,这样查找速度是1,时间复杂度是O(N),空间复杂度为800M

B:(这道题其实有几个变种,第一个变种,就是数字范围在1到1000,然后给你1001个数字,求出里面的重复的数字是多少)
这种其实就是通过异或即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容