2017暑假第二阶段第六场 总结

来源:互联网 发布:ubuntu键盘左alt没反应 编辑:程序博客网 时间:2024/06/06 00:23

T1 2357 数

问题描述

一个数字被称之为 2357 数,当且仅当其所有大于 1 的因子均能被 2/3/5/7 中的某一个整除。对于数字 N,你需要求出不小于 N 的最小 2357 数。

输入格式

一个数字 N。

输出格式

一个数字表示最小的 2357 数

样例输入

209

样例输出

210

数据范围

对于 30%的数据,N≤5000。

对于 60%的数据,N≤10^9。

对于 100%的数据,N≤10^13。


显然,满足条件的数可以表示为 num=2w3x5y7z的形式,其中w,x,y,z都是非负数。四个循环枚举指数即可,当然也可以写成搜索的形式。

优先队列或单调队列的方法也可以考虑,但实现起来没有循环容易,因为在下面的循环中会在刚得到大于等于N的数的时候就停止,对答案的正确性没有影响。

//代码中的快速幂可以用累乘优化,但是当时懒得写了#include<stdio.h>#define ll long longll N,Ans=(1ll<<60);inline ll _R(){    char s=getchar();ll v=0,sign=0;    while((s!='-')&&(s>57||s<48))s=getchar();    if(s=='-')sign=1,s=getchar();    for(;s>47&&s<58;s=getchar())v=v*10+s-48;    if(sign)v=-v;    return v;}ll ksm(ll a,ll b){    ll ans=1;    while(b)    {        if(b&1)ans=ans*a;        b>>=1;a*=a;    }    return ans;}int main(){    ll i,j,k,l,w,x,y,z;    N=_R();    for(i=0;;i++)    {        w=ksm(2,i);        if(w>=N)        {            if(Ans>w)Ans=w;            break;        }        for(j=0;;j++)        {            x=w*ksm(3,j);            if(x>=N)            {                if(Ans>x)Ans=x;                break;            }            for(k=0;;k++)            {                y=x*ksm(5,k);                if(y>=N)                {                    if(Ans>y)                    Ans=y;                    break;                }                for(l=0;;l++)                {                    z=y*ksm(7,l);                    if(z>=N)                    {                        if(Ans>z)Ans=z;                        break;                    }                }               }        }    }    printf("%lld",Ans);}

T2 监狱

问题描述

一个监狱,构造很奇特,有N个牢房,但是N个牢房却是一字排起的。也就是说,第i个牢房紧挨着第i+1个(除了末尾那个)。上级要求将某些罪犯释放,给了一份名单,要求每天释放一个人。现在牢房中一共有N个人,他们互相之间可以说话,如果有一个人离开了,那么能和说上话的人就会angry,如果想让他们安静下来,看守必须给angry的人吃肉。

输入格式

第一行两个数N和M,M表示要释放名单上的人数;
第二行M个数,表示释放哪些人

输出格式

仅一行,表示最少要给多少人次送肉吃

样例输入

20 3
3 6 14

样例输出

35

数据范围

对于 30%的数据,1≤N≤100;1≤M≤5。

对于 70%的数据,1≤N≤1000; 1≤M≤100;

对于100%的数据, 1≤N≤4000; 1≤M≤100;


正向考虑要用到费用提前计算的思想,但是不论是从思维上还是代码实现上都比较困难。更简单的方法是反向考虑,依次把需要释放的人“关进监狱”。那么添加一个人,就相当于把这个人两边的区间进行合并,且合并区间需要等于两区间长度和的费用。这就转换成了一道简单的区间DP。状态转移方程:

设f[i][j]表示合并区间[i,j]所需要的最小费用:
f[i][j]=min(f[i][k]+f[k+1][j]+len[i][j])

len[i][j]在代码中体现为sum[i+k]-sum[i-1]-2。
时间复杂度O(M3)。

#include<stdio.h>#include<algorithm>#define Min(x,y) ((x<y)?(x):(y))using namespace std;const int inf=1e9;int N,M,a[123],f[123][123],sum[123];int main(){    int i,j,k;    scanf("%d%d",&N,&M);    for(i=1;i<=M;i++)scanf("%d",&a[i]);    for(i=1;i<=M+1;i++)    for(j=1;j<=M+1;j++)f[i][j]=inf;    sort(a+1,a+M+1);    for(i=1;i<=M+1;i++)f[i][i]=0;    for(i=1;i<=M;i++)sum[i]=a[i]-1;    sum[1+M]=N;    sum[0]=-1;    //sum[i]表示第i个人以前的区间总长度,由于这样的意义,注意sum[0]的初值    for(k=1;k<=M;k++)    for(i=1;i+k<=M+1;i++)    for(j=i;j<i+k;j++)    f[i][i+k]=Min(f[i][i+k],((f[i][j]+f[j+1][i+k]+sum[i+k]-sum[i-1]-2)));    printf("%d",f[1][M+1]);}

T3 lucknum

问题描述

每个人都会有幸运数字,有种幸运数字是这样定义的:

如果X是幸运数字,则X在m进制下的表示为x1x2…xk,一定有x1<=x2<=…<=xk,其中k可以表示X在m进制下的位数(不能有前导0,除非该数本身就是0)。

这样的数字可能有无穷多个的,但是如果是在m进制下位数不超过n的幸运数字,就应该是有限个了,你能算出来吗?

这个答案可能很大,你只需要输出答案对一个质数p取模的值即可。

输入格式

共一行,三个正整数 n、m 和 p,保证 p 是质数。

输出格式

共一行,表示答案对p取模的值。

样例输入

4 10 10000079

样例输出

715

提示

前 20%的数据满足 n <= 18, m <= 10。

前 50%的数据满足 n <= 100, m <= 100。

前 80%的数据满足 n <= 1000, m <= 1000。

100%的数据满足 n <= 10^7, m <= 10^7, n + m <= p, p <= 10000079


首先容易做出80分算法,思路当然是递推:

设f[i][j]表示讨论到第i位,且最后一位是j的方案总数,那么容易得出递推关系:

1.f[1][i]=10i<M
2.f[i][j]=jk=0f[i1][k],i>1,0j<M

注意到2式可以用前缀和优化,时间复杂度降到了O(MN),能得80分。

80分代码:

#include<stdio.h>#define ll long longll sum[1005][1005];int N,M;ll P;int main(){    int i,j;    scanf("%d%d%lld",&N,&M,&P);    for(i=0;i<M;i++)sum[0][i]=1;    for(i=1;i<=N;i++)    {        sum[i][0]=1;        for(j=1;j<M;j++)sum[i][j]=(sum[i][j-1]+sum[i-1][j])%P;    }    printf("%lld",sum[N][M-1]);}

AC算法:

从这里可以看出,sum[i][j]=(sum[i][j-1]+sum[i-1][j])%P。再结合我们所赋的初值,不难想到组合数的递推公式。或者如果用上面的程序打表找规律,也可以发现其实打的表构成了一个斜放的杨辉三角形,然后可以得出答案是Cnm+n+1。时间复杂度级别为O(n)。

同时,p < m +n 以及p为质数的条件也给了我们一些暗示。

AC代码:

#include<stdio.h>#define ll long longll N,M,P;ll ksm(ll a,ll b){    ll ans=1;    while(b)    {        if(b&1)ans=ans*a%P;        b>>=1;a=a*a%P;    }    return ans;}ll C(ll m,ll n){    if(n>m)return 0;    if(n==0)return 1;    if(n>m-n)n=m-n;    ll i,A=1,B=1;    for(i=0;i<n;i++)    {        A=A*(m-i)%P;        B=B*(i+1)%P;    }    return A*ksm(B,P-2)%P;//P为质数,费马小定理求逆元}int main(){    scanf("%lld%lld%lld",&N,&M,&P);    printf("%lld",C(N+M-1,N));}

数学解释(来自PWJ大佬):

对于一个有n位的m进制数,设各位上的数为x1,x2,xn,则满足0x1x2x3xn<m,不妨设x0=0,xn+1=m1,那么上式化为x0x1x2x3xnxn+1

观察式子(xn+1xn)+(xnxn1)++x1x0m1,不等号左边括号内的每一项都是非负的。将不等号左右两边同时加上(n+1)得:

(xn+1xn+1)+(xnxn1+1)++x1x0+1m+n,在这个式子中,括号内每一项为正整数。原问题就转换为了求满足该式子的非负数列x0,x1,x2,xn,xn+1的个数。

可以发现上面的问题能转化为隔板法的数学模型:把(m+n)个物品分成(n+1)堆,且每堆不为空。那么一共有(m+n1)个隔板,可控选择的隔板位置有n个。那么答案就是Cnm+n1


总结

T1是道暴力水题;T2只要能想到逆向考虑就变成一道区间DP水题;T3如果用纯数学推导其实并不好想,但是80分算法应该是可以立即弄出来,只要更进一步看出组合数递推公式或想到打表找规律就能轻松AC。

总的来说本次考试比较简单,做完了三道题还对拍了一个多小时,考下来还AK了。

再次意识到随时保存代码的重要性,六号大佬差点就没有AK。只不过二十分钟就打完了三道题的代码并且AK,膜膜膜。

原创粉丝点击