sc2017新高二&初三模拟赛11 总结

来源:互联网 发布:mac itunes资料库在哪 编辑:程序博客网 时间:2024/04/29 07:40

T1:选数(SMOJ2111)

题目分析:这分明就是我做过的CQOI2015的原题啊……
由于HL<=105,所以如果在这些数中选了两个或两个以上不同的数,它们的最大公约数一定小于等于105。不妨记f[x]表示选出至少两个不同的数,最大公约数刚好为xK的方案数。并用g[x]记录L~H中xK的倍数有多少个,那么选出至少两个不同的数,且gcd是xK的倍数的方案数为g[x]ng[x]。要求gcd刚好为xK的方案数,就有:

f[x]=g[x]ng[x]i=1HLf[ix]

于是我们从H-L开始倒序计算f,f[1]即为选了至少两个不同的数,gcd刚好为K的方案数。但如果L<=K<=H的话,n个数全部选K也是一种可行方案,于是f[1]要加上1再输出。时间复杂度O((HL)ln(HL))
其实也可以用莫比乌斯反演做,但时间会很卡(顺便膜一波PoPoQQQ大爷的莫反题解:http://blog.csdn.net/popoqqq/article/details/44917831)。

CODE:

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxn=100100;const int M=1000000007;int num[maxn];int n,k,l,h;int Fast_power(int a,int x){    if (x==1) return a;    long long mid=Fast_power(a,x>>1);    mid=mid*mid%(long long)M;    if (x&1) mid=mid*(long long)a%(long long)M;    return (int)mid;}int main(){    freopen("2111.in","r",stdin);    freopen("2111.out","w",stdout);    scanf("%d%d%d%d",&n,&k,&l,&h);    l=(l+k-1)/k;    h=h/k;    for (int i=h-l; i>=1; i--)    {        int x=(l+i-1)/i-1;        int y=h/i-x;        num[i]=Fast_power(y,n);        num[i]=(num[i]-y+M)%M;        for (int j=2; i*j<=h-l; j++) num[i]=(num[i]-num[i*j]+M)%M;    }    if ( l==1 && h>=1 ) num[1]=(num[1]+1)%M;    printf("%d\n",num[1]);    return 0;}

T2:等差数列(SMOJ2112)

题目分析:这题我考试的时候写的是O(n264)的二进制压位(调了我好久),但其实有O(nlog(n))的方法。
我们只需要检验是否有长度为3的等差数列就行了,于是可以从左往右枚举中间的那个数i,并开两个bool数组f,g记录左边和右边的数。f[L]=true表示L这个数在i的左边出现过,g[n-R+1]=true表示R在右边出现过。然后将f[ a[i] ]与g[n-a[i]+1]对齐,看f与g的对应位置是否有一位都为true,存在一位则有等差数列。如果将64位bool存为一个long long类型,移位后使用&运算就可以判断,每次比较的时间为O(n264)
然而后来看了正解发现我的方法既暴力又傻B。其实在上面的方法中,f与g的对应位置(假设是x对应y)都为false也会存在等差数列,因为f[x],g[y]都为false说明x在i右边,n-y+1在i左边,而等差数列{x,a[i],n-y+1}倒转之后依然是等差数列。那么现在就是要快速判断f的某一段和g的某一段是否有对应位置都为true或false,我们不妨改变g数组的意义,令g[n-y+1]=false表示y在右边出现了,用g[n-y+1]=true表示y没出现。这样只有当f和g整段都相等时,才没有等差数列。判断一段字符串是否相等可以用Hash,而要动态修改Hash值再套棵线段树即可。

CODE(压位):

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxn=40010;const int maxm=630;typedef unsigned long long ULL;ULL temp1[maxm];ULL temp2[maxm];ULL temp3[maxm];int p[maxn];int t,n,m;void Work(ULL *X,int a){    a--;    int b=a>>6;    a-=(b<<6);    a=63-a;    b++;    ULL c=1;    c<<=a;    X[b]^=c;}ULL Get(int a){    ULL sum=0;    for (int i=0; i<=a; i++) sum|=(1<<i);    return sum;}bool Judge(ULL *X,ULL *Y,int a){    for (int i=1; i<=m; i++) temp3[i]=0;    int b=a>>6;    a&=63;    a=63-a;    ULL c=Get(a);    for (int i=1; i+b<=m; i++)        temp3[i]=(((Y[i+b]&c)<<(63-a))|(Y[i+b+1]>>(a+1)));    for (int i=1; i<=m; i++) if ((X[i]&temp3[i])!=0ULL) return true;    return false;}int main(){    freopen("2112.in","r",stdin);    freopen("2112.out","w",stdout);    scanf("%d",&t);    while (t--)    {        scanf("%d",&n);        for (int i=1; i<=n; i++) scanf("%d",&p[i]);        memset(temp1,0,sizeof(temp1));        memset(temp2,0,sizeof(temp2));        for (int i=1; i<=n; i++) Work(temp2,i);        Work(temp1,p[1]);        Work(temp2,n-p[1]+1);        Work(temp2,n-p[2]+1);        m=(n+63)>>6;        bool sol=false;        for (int i=2; i<n; i++)        {            int j=n-p[i]+1;            if (p[i]<=j) sol|=Judge(temp1,temp2,j-p[i]);            else sol|=Judge(temp2,temp1,p[i]-j);            if (sol)            {                printf("Y\n");                break;            }            Work(temp1,p[i]);            Work(temp2,n-p[i+1]+1);        }        if (!sol) printf("N\n");    }    return 0;}

T3:吃烤饼(SMOJ2228)

题目分析:这题就是一道很水的DP……
我们知道值*概率=期望,于是不妨先算出所有摆烤饼方案的美味度总和。接下来要将原问题转化为子问题:假设当前烤饼堆了len(len>1)层,最上面的烤饼长度为i,那么枚举i下面的烤饼长度j(j>i),这就转化为了子问题F(len1,j)。记f[len][i]表示所有烤饼层数为len,且最上面的烤饼长度为i的摆烤饼方案的美味度之和,那么枚举一个j,将f[len-1][j]加进f[len][i]就可以了?当然不是,我们还要算上i的美味度。但我们不知道i的美味度被加了几次,也就是不知道方案数,于是我们用num[len][i]来记录方案数,即有:
f[len][i]=nj=i+1f[len1][j]
num[len][i]=nj=i+1num[len1][j]
最后f[len][i]+=num[len][i]d[i]即可。
现在我们考虑f的每一个元素对答案的贡献。假设一种摆烤饼方案的长度为len,那么它被选择的概率为1leni=1(ni+1);假设最上层的烤饼长度为i,那么到它这个状态就无法往下放烤饼的概率(就是下一步选到比i大的烤饼的概率)为max(0,nilen+1nlen)。由于一开始算出美味度之和再乘概率会爆long long,所以要用double类型一边算一边乘以概率。时间复杂度O(n3)

CODE:

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxn=255;double f[maxn][maxn];double num[maxn][maxn];int d[maxn];int t,n;int main(){    freopen("2228.in","r",stdin);    freopen("2228.out","w",stdout);    scanf("%d",&t);    while (t--)    {        scanf("%d",&n);        for (int i=1; i<=n; i++) scanf("%d",&d[i]);        for (int i=1; i<=n; i++)            for (int j=1; j<=n; j++)                f[i][j]=num[i][j]=0.0;        for (int i=1; i<=n; i++)            num[1][i]=1.0/(double)n,f[1][i]=(double)d[i]/(double)n;        for (int i=2; i<=n; i++)            for (int j=1; j<=n; j++)            {                for (int k=j+1; k<=n; k++)                    num[i][j]+=num[i-1][k],f[i][j]+=f[i-1][k];                num[i][j]/=( (double)(n-i+1) );                f[i][j]/=( (double)(n-i+1) );                f[i][j]+=( (double)d[j]*num[i][j] );            }        double ans=0.0;        for (int i=1; i<=n; i++)            for (int j=1; j<=n; j++)                if (n-i-j+1>0)                    ans+=( f[i][j]*(double)(n-i-j+1)/(double)(n-i) );        ans+=f[n][1];        printf("%.7lf\n",ans);    }    return 0;}

总结:这次比赛我居然被初二升初三的学弟吊打?!比赛是8:00开始的,然而我起床后赶到电脑室已经9:00了……由于T1做过,我直接开始想T2,然而我想了很久都只会压位的方法(智商感人),以为n=40000+7组数据要被卡(后来才知道其实还是很快的嘛)。结果耗了45min想T2还是没什么进展,还剩下75min的时候开始码压位,比想象中的难码,花了将近1h,交了3,4次才对。最后一点时间才想T3,发现55分很好拿,结果DFS都没调出来比赛就结束了,有一个初三刚好比我多10分……
后来我想了想,T3好像很简单啊。这次虽然因为迟起床没了1h,但T1做过也算扯平了。主要是在T2耗太久了,以为O(n264)会被卡常;而且我也没有写过很多压位的题目,代码调出N个错误。要是把想T2的时间留一些给T3就能做出T3了,而且就算不去细想T3,也要先敲完T3的暴力再去敲压位才对的,主要是当时对自己的代码能力太自信了,以为很快就能敲出T2QAQ。以后要增强自己的代码能力,而且要合理分配好时间,优先确保暴力。还有,我们要相信SMOJ评测姬的运行速度……

原创粉丝点击