Google APAC 2017 University Test Round B

来源:互联网 发布:大数据项目知识点 编辑:程序博客网 时间:2024/06/07 03:40

这场和上场相比,不是代码傻乎乎堆就能多拿分了。想清楚再动手最最最重要。
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]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)*2^2[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
原创粉丝点击