高二&高一&初三模拟赛14 总结

来源:互联网 发布:日本轻小说软件 编辑:程序博客网 时间:2024/04/29 04:58

T1:完全平方数(SMOJ2236)

题目分析:一道莫比乌斯反演的例题……
记f(k)表示1~k中没有完全平方数因子的数的个数,很明显f单调不降,于是可以二分答案。那么我们如何求f(k)呢?我们可以枚举平方因子p,这样我们就要减去p2的倍数。为了不算重,我们要用容斥原理,即先减去22,32的倍数,再加上算重了的62的倍数……依此类推。于是:

f(k)=p=1kμ(p)kp2

试一试就会知道当k=109时,答案不会超过2109。时间复杂度O(klog(k))

CODE:

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxn=50010;int miu[maxn];bool vis[maxn];int prime[maxn];int cur=0;int t,k;void Make(){    miu[1]=1;    for (int i=2; i<maxn; i++)    {        if (!vis[i]) miu[i]=-1,prime[++cur]=i;        for (int j=1; j<=cur && i*prime[j]<maxn; j++)        {            int k=i*prime[j];            vis[k]=true;            if (i%prime[j]) miu[k]=-miu[i];            else            {                miu[k]=0;                break;            }        }    }}int Get(int x){    int temp=0;    for (int i=1; i*i<=x; i++) temp+=miu[i]*(x/(i*i));    return temp;}int Binary(){    int L=1,R=k<<1;    while (L+1<R)    {        long long mid=(long long)L+(long long)R;        mid>>=1;        if ( Get(mid)<k ) L=mid;        else R=mid;    }    return R;}int main(){    freopen("2236.in","r",stdin);    freopen("2236.out","w",stdout);    Make();    scanf("%d",&t);    while (t--)    {        scanf("%d",&k);        int ans=Binary();        printf("%d\n",ans);    }    return 0;}

T2:字符染色(SMOJ2237)

题目分析:还记得省赛前SemiWaker跟我们讲补偿转移DP,听得我半懵半懂,现在遇见一题,才真正领悟了些许。
我们不妨记f[i][j][s]表示现在处理到字符串的第i位,其中第i位填的是j(j=0代表B,j=1代表W),而且状态为s(s=0代表之前还没有出现连续k个B,s=1表示之前出现了连续k个B,但还没有连续k个W,s=2代表之前已有连续k个B,连续k个W)。那么f[i][0][s]=(f[i1][0][s]+f[i1][1][s])[iBX]f[i][1][s]=(f[i1][0][s]+f[i1][1][s])[iWX]
但这样明显是不对的,就拿s=0为例,我们要减去到第i-k+1位~第i位才刚好组成了连续k个B的情况。即当字符串的第i-k+1~i位没有W的时候(这个可以用前缀和判断),f[i][0][0]要减去f[i-k][1][0],f[i][0][1]要相对应地加上这个值。那么为什么要求第i-k位一定是W呢?因为如果第i-k位是B,第i-k~i-1位就构成了连续的k个B,和我们“到第i-k+1位~第i位才刚好组成了连续k个B“的要求不符。这样做时间复杂度O(n)
其实这题说是补偿转移有点牵强,用低级的话来说就是容斥原理。然而我考试的时候,以及考完试想了很久都想不出来,最后看了网上的题解才明白,可能我的DP还是太差了。

CODE:

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxn=1000100;const long long M=1000000007;typedef long long LL;LL f[maxn][3][2];int b[maxn];int w[maxn];char s[maxn];int n,k;int main(){    freopen("2237.in","r",stdin);    freopen("2237.out","w",stdout);    scanf("%d%d",&n,&k);    scanf("%s",&s);    for (int i=0; i<n; i++)    {        if (s[i]=='B') b[i+1]=1;        if (s[i]=='W') w[i+1]=1;    }    for (int i=2; i<=n; i++) b[i]+=b[i-1],w[i]+=w[i-1];    f[0][0][1]=1;    for (int i=1; i<=n; i++)    {        bool fb=false,fw=false;        if (s[i-1]!='W') fb=true;        if (s[i-1]!='B') fw=true;        if (fb)        {            for (int j=0; j<=2; j++)                f[i][j][0]=(f[i-1][j][0]+f[i-1][j][1])%M;            if ( i>=k && w[i]==w[i-k] )            {                f[i][0][0]=(f[i][0][0]-f[i-k][0][1]+M)%M;                f[i][1][0]=(f[i][1][0]+f[i-k][0][1])%M;            }        }        if (fw)        {            for (int j=0; j<=2; j++)                f[i][j][1]=(f[i-1][j][0]+f[i-1][j][1])%M;            if ( i>=k && b[i]==b[i-k] )            {                f[i][1][1]=(f[i][1][1]-f[i-k][1][0]+M)%M;                f[i][2][1]=(f[i][2][1]+f[i-k][1][0])%M;            }        }    }    LL ans=(f[n][2][0]+f[n][2][1])%M;    printf("%lld\n",ans);    return 0;}

T3:乌鸦喝水(SMOJ2238)

题目分析:一道很简单的模拟题,然而我还是写炸了……
我们先算出每一个水井会在下降多少次之后不能喝,记在一个数组t中。假设乌鸦飞一遍所有水井,会喝x个水井的水,那么所有水井的t值就会全部减去x。x值什么时候会发生变化呢?就是在某一个水井的t值变为0或以下的时候。于是我们按t值对水井排序,然后看一下哪些水井可能在下一轮喝水之后t值降为0或以下(即t值小于当前的x),暴力计算这些水井的是否能在下一轮喝到,以及它t值实际会降到多少。

具体的操作是这样:假设第i个水井到第j个水井可能会在下一轮被喝光(t值小于x),我们将它们按位置从左到右再排序,然后用树状数组查看某个水井左边有几个是t值>=x的(即在这一轮一定会被喝到),并记录一下它左边t值小于x的水井实际喝了几个,然后判断一下它当前的t值是否大于等于上面的两个值之和,是的话就可以喝它。

但像上面那样做并不能保证每一个水井只被操作一遍,如下例:
t={1,1,4,1,4}
所有水井的t值都小于5,于是我们要将它们一起处理,处理完一轮之后t值变为下面这样:
t={-2,-2,1,-2,1}
此时还有两个水井没有喝完。
为什么会出现这种情况呢?因为某些水井当前的t值虽然小于x,但由于另外一些水井实际上并没有被喝,导致所有t值的减小量不足x,它就有可能t值还是大于0。
于是我们没法保证时间复杂度了吗?

不妨假设现在有n个水井一起被处理,其中有x个水井被喝到了水(也就是说有n-x个水井无法再喝)。若x<n2,说明至少有一半的水井接下来不会再处理;若x>=n2,说明剩下的水井中所有水井的t值至少减了一半。这样可以证明不超过2log(n)次操作后,所有水井的t值都会小于等于0。证得上述方法的时间复杂度为O(nlog2(n)),实际运行也飞快。

然而kekxy还有一种严格O(nlog(m))的方法:我们将所有水井按t值从小到大排序,如果t值相同就按位置从右到左排序,这样排在前面的水井被喝的次数就一定小于等于后面的水井。对于每一个水井,我们二分它被喝的次数就行了。

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;struct data{    int t,id;} b[maxn];int bit[maxn];int d[maxn];int temp[maxn];int num=0;int n,m,X;int Time=0,ans=0;bool Comp1(data x,data y){    return x.t<y.t;}bool Comp2(data x,data y){    return x.id<y.id;}void Add(int x,int v){    while (x<=n)    {        bit[x]+=v;        x+=(x&(-x));    }}int Sum(int x){    int s=0;    while (x)    {        s+=bit[x];        x-=(x&(-x));    }    return s;}int main(){    freopen("2238.in","r",stdin);    freopen("2238.out","w",stdout);    scanf("%d%d%d",&n,&m,&X);    for (int i=1; i<=n; i++) scanf("%d",&d[i]);    for (int i=1; i<=n; i++)    {        int a;        scanf("%d",&a);        b[i].t=(X-d[i])/a+1;        b[i].id=i;    }    sort(b+1,b+n+1,Comp1);    for (int i=1; i<=n; i++)    {        if (b[i].t<=ans)        {            Add(b[i].id,1);            continue;        }        int x=(b[i].t-ans)/(n-i+1);        if (Time+x>=m)        {            ans+=((m-Time)*(n-i+1));            break;        }        Time+=x;        ans+=(x*(n-i+1));        int j=i;        while ( j<n && b[j+1].t-ans<=n-i+1 ) j++;        sort(b+i,b+j+1,Comp2);        for (int k=i; k<=j; k++)        {            int w=b[k].id;            if ( b[k].t-ans<w-Sum(w) )                temp[++num]=w,Add(w,1);        }        Time++;        ans+=(n-i+1-num);        while (num) Add(temp[num],-1),num--;        sort(b+i,b+j+1,Comp1);        Add(b[i].id,1);    }    printf("%d\n",ans);    return 0;}

总结:这次比赛真的脑抽了,写T3的时候本来写了一句正确的代码,交上去也已经AC了的,结果忽然头脑发热没考虑清楚,以为那句是错的,就把那句改错了……然后第一个点返回0分,一直在不停地调,导致T2草草交了个20分的大暴力就算了,其实T2的50分算法也很容易想的。以后做题的时候要思考谨慎再码代码;另外要多做DP,提高智商。

原创粉丝点击