关闭

【在线笔试题解题报告系列】Google APAC 2017 University Test Round B

2947人阅读 评论(12) 收藏 举报
分类:

这场和上场相比,不是代码傻乎乎堆就能多拿分了。想清楚再动手最最最重要。

scoreboard中搜索hdu.toraoh,可以看我的当时实际提交情况。


照惯例,题意就不翻译了,这种英文阅读应该是能搞掂的,不然真没法在IT外企工作了——何况Google至少一轮英语面试。

本文地址:http://blog.csdn.net/fcxxzux/article/details/52346364


Problem A. Sherlock and Parentheses

这个题,勇敢的提出猜想,实现一下,就能通过了。


观察样例解释,最后一组,给的形式是:()()(或者(()()

——很自然的去想,为什么要组成()的形式呢?嵌套行不行?

然后我们可以发现,嵌套比并排要吃亏——嵌套放的话,只能整个括起来,对结果贡献为1,并排放,不仅能自己一对,还能和已有的部分合并起来。

所以,直接尽量地,()()()()()()放过去,就好了。

所以答案为:

params=min(L,R);

answer=params*(params+1)/2;

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;

int T,Case=1;

int main(){
    freopen("A-large.in","r",stdin);
    freopen("A-large.out","w",stdout);
    
    for(scanf("%d",&T);Case<=T;Case++){
        int l,r;
        scanf("%d%d",&l,&r);
        long long ans=min(l,r);
        ans=ans*(ans+1)/2;
        printf("Case #%d: %I64d\n",Case,ans);
    }
    return 0;
}

Problem B. Sherlock and Watson Gym Secrets


我就假设你清楚数论中,同余的基本性质(同加、同乘性)了。

同样,这里不会花时间解释如何实现快速幂,请自行搜索并学习。

(必须要掌握,必须要掌握,必须要掌握,想校招进Google、微软,必须要掌握)


然后我们考虑一个问题:需要枚举多少?

注意到:((i+k)^A)%k==(i^A)%k

也就是说,k=100000的时候,i=1和i=100001的结果是一样的

或者说,我算出来i=1的结果,我就知道了1+k、1+2k、1+3k......的结果

所以,只需要考虑i=1到k的情况就行。


结果要怎么计算呢?

答案=所有(i^A+j^B)%k==0的情况数 - i==j的特例数量

根据前面的讨论,去掉特例的时候,最多只用检查k种情况,之后更多的,与这k种情况等价,一起删掉就行。


那么(i^A+j^B)%k==0的情况数怎么办呢?

考虑枚举i^A%k所有可能的情况。

首先上面展示了,最多只有k个不同的i需要计算,其他的都可以归结到i==1~k的情况

而且,余数显然是0~k-1这k种。

如果我确定了特定的一个(i^A)%k

有且仅有唯一的一种(j^B)%k,才能满足(i^A+j^B)%k==0


所以说,枚举i和j(最多k个本质不同的i),先按余数分别累加统计,多少种i的取值,能让(i^A)%k==x,j^B这里类似

然后枚举(i^A)%k的可能取值,和相对应的(j^B)%k的可能取值相乘,就是我们需要的答案的前半部分了。


到此,这个题需要的所有组成部分,全部搞定了。


#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;

const int mod=1000000007;
int T,Case=1;

ll acnt[100005];
ll bcnt[100005];
ll drop[100005];

ll fastpow(ll x,ll time,ll k){
    ll ans=1,tmp=x;
    while(time){
        if(time&1)ans=(ans*tmp)%k;
        time>>=1;
        tmp*=tmp;tmp%=k;
    }
    return ans%k;
}

int main(){
    freopen("B-large.in","r",stdin);
    freopen("B-large2.out","w",stdout);
    
    for(scanf("%d",&T);Case<=T;Case++){
        ll a,b,n,k;
        scanf("%I64d%I64d%I64d%I64d",&a,&b,&n,&k);
        
        memset(acnt,0,sizeof(acnt));
        memset(bcnt,0,sizeof(bcnt));
        memset(drop,0,sizeof(drop));
        
        ll ans=0;
        for(int i=1;i<=min(n,k);++i){
            ll count=(n/k+(n%k>=i?1:0))%mod;
            int ai=fastpow(i,a,k)%k,bi=fastpow(i,b,k)%k;
            acnt[ai]+=count;
            bcnt[bi]+=count;
            if((fastpow(i,a,k)+fastpow(i,b,k))%k==0){
                drop[i%k]+=count;
            }
        }
        for(int i=0;i<k;++i){
            int other=(k-i)%k;
            acnt[i]%=mod;bcnt[other]%=mod;
            ans+=acnt[i]*bcnt[other]%mod;
            ans-=drop[i];
            ans%=mod;
        }
        ans+=mod;ans%=mod;
        printf("Case #%d: %d\n",Case,(int)ans);
    }
    return 0;
}


Problem C. Watson and Intervals

这是一个经典套路的运用。

首先我们看一个面试真题:

给出一份对某种资源占用的记录(这种资源可以被多个进程共享),记录中包括开始时刻和截止时刻(左闭右开)。
求这种资源共计被占用的时长(如果多个进程同时占用,也只记录一份用时)


比如,
{1,3}
{2,4}
占用时长:3(1时刻1个,2时刻2个,3时刻1个,其他时间没人占用,共计有3个时间有人占用)


没有任何训练,只会C语言的人:搞一个统计数组,一个元素对应一个时刻,然后对每个记录,在统计数组里,从开始到结束,每个元素+1

这个时间复杂度是O(n*Time)的,当然不是这里所鼓励的做法。


更高效的做法是:

将{a,b}拆成2个标记,记成{a,增加一个作业},{b,减少一个作业},得到2n个标记,之后按时间从前到后排序(同时刻的按先减少再增加排序),排序后从前到后扫一遍,记录所有至少1个作业的时间段的长度,累加起来就行了。

比如上面的例子,拆开,并排序:

{1,增加}{2,增加}{3,减少}{4,减少}

之后从左到右:

1时刻,开始有人占用,记下来这个时刻

2时刻,又来一个人,记2人

3时刻,少了一个,记1个人

4时刻,再少一个,没人用了,累加一下之前被占用时间:4-1=3

时间复杂度,这个是O(n+nlogn+n)=O(nlogn)的


然后我们来考虑一下,借鉴这个思路。

(留白,供思考,答案在下面)










首先,就不要考虑枚举删除每一条线段了,这样的时间复杂度,用了上面的思路,还得是O(n^2)的

考虑,删除一条线段后,减少的覆盖区域

——是之前有这条线段的时候,被且仅被这条线段覆盖的区域。

那么这么做:

我们可以用O(nlogn)的时间复杂度找到原来被覆盖的总长度

然后我们减掉独占区域最大的线段  它的独占区域长度,就得到我们要求的答案了。


怎么计算每个线段的独占区域长度?果然要每个线段枚举过去吗?

不用!完全不用!

继续考虑上面的思路。

我们在上面的思路中,知道怎么求至少有一段线段覆盖的长度,那么能不能求,有且仅有一条线段覆盖的长度?

能!

size变成1的时候,进入有且仅有一条线段覆盖的情况,

size从1变成其他的时候,摆脱了有且仅有一条线段覆盖的情况


ok,现在我们能识别出有且仅有一个线段覆盖的区域,如果能知道,这种区域属于哪条线段,问题就解决了。

——很简单,

找到一种数据结构,支持以下操作:

快速地(复杂度<=O(logn))插入、删除一个特定元素,以及快速地取出其中唯一一个元素。如果能顺带快速统计里面有几个元素就更好了。

这样,开始一个线段:把这个线段的编号放进去

离开一个线段:把这个线段的编号从里面擦除

size变成1:接下来这一段,一定属于这个数据结构里唯一一个线段


那么,有没有这样的数据结构?

有啊!HashSet或者TreeSet,都可以。

问题得到解决。


最后整理一下:

线段拆成2个标记,{L,开始},{R,结束},然后排序

之后从左到右一个个扫过去

一方面要解决,求多少长度被覆盖了

另一方面,用类似的思路,解决:到底有多少小段是被一个线段独自覆盖的?这些小段有多长?每个小段被哪个线段独自覆盖?

最后,答案=总覆盖长度-独占覆盖长度最大的线段。


#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
#include <map>
#include <set>
using namespace std;
typedef long long ll;

int T,Case=1;

struct Point{
    int id;
    int idx;
    int type;//0->delete,1->add
    Point(){}
    Point(int a,int b,int c):id(a),idx(b),type(c){}
    bool operator<(const Point&b)const{
        if(idx!=b.idx)return idx<b.idx;
        return type>b.type;
    }
}p[1000005];

int singleLength[500005];

int main(){
    //freopen("C-large.in","r",stdin);
    //freopen("C-large.out","w",stdout);
    
    for(scanf("%d",&T);Case<=T;Case++){
        int n, L1, R1, A, B, C1, C2,m;
        scanf("%d",&n);
        scanf("%d%d%d%d%d%d%d",&L1,&R1,&A,&B,&C1,&C2,&m);
        p[0]=Point(1,L1,1);
        p[1]=Point(1,R1+1,0);
        for(int i=1;i<n;i++){
            int xi,yi;
            xi=((ll)A*L1+(ll)B*R1+C1)%m;
            yi=((ll)A*R1+(ll)B*L1+C2)%m;
            p[2*i]=Point(i+1,min(xi,yi),1);
            p[2*i+1]=Point(i+1,max(xi,yi)+1,0);
            L1=xi;R1=yi;
        }
        sort(p,p+2*n);
        int totLength=0;
        int lastStart=0;
        int lastSingleStart=0;
        int lastSingleId=0;
        map<int,int>single;
        set<int>hasCovered;
        for(int i=0;i<2*n;++i){
            if(p[i].type==1){
                hasCovered.insert(p[i].id);
            }else{
                hasCovered.erase(p[i].id);
            }
            if(p[i].type==1 && hasCovered.size()==1){
                lastStart=p[i].idx;
            }else if(hasCovered.size()==0){
                totLength+=p[i].idx-lastStart;
            }
            if(lastSingleId==0 && hasCovered.size()==1){
                lastSingleStart=p[i].idx;
                lastSingleId=*(hasCovered.begin());
            }else if(lastSingleId!=0 && hasCovered.size()!=1){
                single[lastSingleId]+=p[i].idx-lastSingleStart;
                lastSingleId=0;
            }
        }
        int maxSingle=0;
        for(auto it=single.begin();it!=single.end();++it){
            maxSingle=max(maxSingle,it->second);
        }
        printf("Case #%d: %d\n",Case,totLength-maxSingle);
    }
    return 0;
}



Problem D. Sherlock and Permutation Sorting

这里舔一下zimpha。下面这个3句话题解是zimpha的。

摘录如下:

算出长度为l的primitive chunk个数(就是不能被继续划分的chunk,公式f(l)=l! - \sum f(i)*(l-i)!)
然后n^3的dp就简单了
优化下就n^2了

先解释前2句

我们如果知道每种长度的,不可继续拆分的chunk有几种,那么我们知道了前面一部分,想构造更长的,直接在后面接上去就行了。

每种长度的,不可继续拆分的chunk数的公式:f(l)=l! - Σ(f(i)*(l-i)) 其中 1<=i<l 这个解释一下

l!,就是l的全排列,没问题,然后减去非法的情况。

第1个位置是1肯定不行,之后什么都没用

前2个位置是2 1那也不行,这是长度为2的不可分的chunk,放上去就成了可以单独拿出来的chunk了,后面怎么放都不行

之后的以此类推,这样就能既不遗漏、又不重复的去掉所有非法情况了。

计算的时候,预处理一下到5000的阶乘,之后这里n^2的预处理计算,还是能接受的


然后n^3的dp就简单了(不是我说的):

dp[i][j]表示长度为i的,由j个不可继续分割的chunk拼接而成的排列的方案数。

那么,转移到这个状态,显然由chunk数-1的,长度依次缺少1、2、3......i的方案转移而来(最后补上一个缺少的长度的、不可继续拆分的chunk,就行了)

状态数n^2*转移的时间复杂度n = n^3


最后转化成n^2这一步

首先思考:为什么之前我要n^3呢?

答:我需要知道最细切割后的段数,这样才能给相应的方案数乘上段数的平方。

而对每个长度,枚举从哪个长度转移过来,这件事情是省不了的。

那么,乘上段数的平方这件事情,我能不能在做dp的时候算好呢?如果能,能不能把不同段数的累加在一起,减掉一维状态,来加速呢?

我们来实验一下。

以下,定义 每种长度的,不可继续拆分的chunk数 为f(i)

dp的含义同上面讨论的n^3的情况


首先确定基本性质:

从dp[a][b]转移到dp[a+x][b+1]的时候,

dp[a+x][b+1]+=dp[a][b]*f(x)

——希望大家没有疑问(这也是n^3中转移的基础)。


长度为1时:

ans[1]=dp[1][1]*1^2 = f(1)*1^2

长度为2时:

ans[2]=dp[2][1]*1^2 + dp[2][2]*2^2

=f(2)*1^2 + dp[1][1]*f(1)*2^2

而其中,dp[2][2]*2^2 

dp[1][1]*f(1)*2^2

= dp[1][1]*(1+1)^2*f(1)

dp[1][1]*(1^2+ 2*1 + 1)*f(1)

再来看看长度为3的时候:

ans[3]=dp[3][1]*1^2 + dp[3][2]*2^2+dp[3][3]*3^2

dp[3][1]*1^2=f(3)*1^2

dp[3][3]*3^2

=dp[2][2]*f(1)*3^2

=dp[2][2]*(2+1)^2*f(1)

=dp[2][2]*(2^2+2*2+1)*f(1)

dp[3][2]*2^2= dp[2][1]*f(1)*2^2 + dp[1][1]*f(2)*2^2

=dp[2][1]*(1^2+2*1+1)*f(1) + dp[1][1]*(1^2+2*1+1)*f(2)


观察一下,可以得到一些结论:

1、长度为x的,变为长度为x+y的,计算方案数的时候,*f(y)就行了,不管你分了几段

——有点意思

接下来,我们讨论的,是从长度a,向长度a+x转移(不管能最细地分成几段)。

2、dp[a][b]*b^2变成dp[a+x][b+1]*(b+1)^2的时候

dp[a+x][b+1]*(b+1)^2

=(dp[a][b]*f(x)) * (b^2+2*b+1)

=f(x) * (dp[a][b]*b^2 + dp[a][b]*2*b + dp[a][b])

f(x)可以预先算好,状态转移的时候x是多少是确定的

dp[a][b]*b^2,这个我们认为之前就算好了,

dp[a][b],我们很清楚怎么算。

dp[a][b]*2*b呢?

只要我们能找到一个办法,依赖前项和dp[a][b],就行了。

好运的是,有:

dp[a+x][b+1]*2*(b+1)

=dp[a][b]*f(x)*(2*b+2)

=f(x)* (dp[a][b]*2*b + dp[a][b]*2)


然后让我们灵活运用乘法分配律,让同类项在一起:

转移过程的式子

dp[a+x][0+1]*(0+1)^2 + dp[a+x][1+1]*(1+1)^2 + .... + dp[a+x][a+1]*(a+1)^2

=f(x) * (dp[a][0]*0^2 + dp[a][0]*2*0 + dp[a][0]) + f(x) * (dp[a][1]*1^2 + dp[a][1]*2*1 + dp[a][1]) +.....

=f(x) * (dp[a][0]*0^2 + dp[a][1]*1^2 + ...)  + f(x)* (dp[a][0]*2*0+dp[a][1]*2*1 + ......) + f(x)*(dp[a][0]+dp[a][1]......)


目前离解决问题只有一步之遥,剩下的就是理清楚,我们需要维护什么,怎么维护:

f(x),必须的。预处理一下就行了。

dp[a][b]*b^2,这是我们求解的目标

dp[a][b]*2*b,还有dp[a][b],这是求解目标中需要的辅助信息。

上面这3个信息,我们找到了他们累加在一起的时候,怎么转移到我们需要的状态


(如果看到这里觉得比较晕的,请自己手推一下,还是比较容易整理出来的)


万事俱备,只欠东风,实现一下,然后收工:

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;

int T,Case=1;
ll perm[5005];
ll f[5005];
ll dp[5005][3];
//thisdp[x][0] : dp[x][0]+dp[x][1]+dp[x][2]+......
//thisdp[x][1] : dp[x][0]*2*0+dp[x][1]*2*1+dp[x][2]*2*2+......
//thisdp[x][2] : dp[x][0]*0^2+dp[x][1]*1^2+dp[x][2]*2^2+......

void addMod(ll &a,ll b,ll mod){
    a=((a+b)%mod+mod)%mod;
}

int main(){
    freopen("D-large-practice.in","r",stdin);
    freopen("D-large-practice.out","w",stdout);
    
    for(scanf("%d",&T);Case<=T;Case++){
        int n,m;
        scanf("%d%d",&n,&m);
        perm[0]=1;
        for(int i=1;i<=n;++i){
            perm[i]=perm[i-1]*i%m;
            f[i]=perm[i];
            for(int j=1;j<i;++j){
                addMod(f[i],-(f[j]*perm[i-j]%m),m);
            }
        }
        
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        dp[0][1]=0;
        dp[0][2]=0;
        for(int i=1;i<=n;++i){
            for(int j=1;j<=i;++j){
                addMod(dp[i][0],dp[i-j][0]*f[j]%m,m);
                addMod(dp[i][1],(dp[i-j][0]*2%m+dp[i-j][1])%m*f[j]%m,m);
                addMod(dp[i][2],(dp[i-j][2]+dp[i-j][1]+dp[i-j][0])%m*f[j]%m,m);
            }
        }
        printf("Case #%d: %d\n",Case,(int)dp[n][2]);
    }
    return 0;
}


0
0

猜你在找
【直播】机器学习&数据挖掘7周实训--韦玮
【套餐】系统集成项目管理工程师顺利通关--徐朋
【直播】3小时掌握Docker最佳实战-徐西宁
【套餐】机器学习系列套餐(算法+实战)--唐宇迪
【直播】计算机视觉原理及实战--屈教授
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之矩阵--黄博士
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之凸优化--马博士
【套餐】Javascript 设计模式实战--曾亮
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:45858次
    • 积分:746
    • 等级:
    • 排名:千里之外
    • 原创:25篇
    • 转载:0篇
    • 译文:0篇
    • 评论:44条
    最新评论