【NOIP2017SummerTraining0705】

T1正解KMP Next数组利用,n^2大暴力水过
T2 dp??乱搞?? 只水了50
T3 组合数? 有点难,但不是很难。 现场0分,部分分都没拿。

看题解或代码的看目录,有索引。

T1

问题 A: 重复字符串
时间限制: 1 Sec 内存限制: 256 MB
题目描述
给定两个字符串a和b,我们可以定义一些操作:a*b为将字符串a和字符串b连接起来,比如a= “aoe”,b= “jkw”,那么a*b= “aoejkw”。进一步,我们可以有指数操作,a^0= “”, a^1=a, a^2=a*a, a^n=a*(a^(n-1))=a*a*…*a (n个a)

现在给你一个字符串,你可以将它看成是a^n的形式,比如字符串”abababab”,可以认为是”abab”^2, 也可以是”abababab”^1,还可以是”ab”^4。

现在问题是,给定的字符串,我们想让它变成a^n中的n达到最大,那么这个n最大是多少?例如:”abababab”最大的n是4。

输入
第一行,一个整数m,表示有m个字符串。

接下来m行每行输入一个只含小写字母的字符串。

输出
输出m行,对于每行输出相应字符串的最大n。

样例输入
3
abcde
aaaaaa
abababab

样例输出
1
6
4

提示

30%的数据:字符串的长度≤1000;

100%的数据:字符串的长度≤1000000, m≤10,字符串内只含小写字母。

Solution

  1. 没错,数据水,只要for循环分成几份,O(N)判断,优化技巧:1.因数在上,2.找到了就break,万能的break!
  2. 100分做法:KMP,Next数组
    假设字符串为S,长度为N,子串T重复K次后得到串S,那么T的长度一定为L = N/K(要整除),则T = S[1…L],将S拆分成K份,每份长度为L,则有
    S[1…L] = S[L+1…2L] = S[2L+1…3L] = … = S[(K-1)L+1…KL]
    由于要保证K最大,势必L要取最小,所以根据Next函数的定义,有Next[KL] = (K-1)L;
    即Next[N] = N - L,所以L = N - Next[N];
    但是得出的长度L还要保证能被N整除,所以如果不能整除说明L = N,即K = 1;而如果能整除,那么K = N / (N - Next[N]);
    因而我们只要对字符串S做一趟KMP,对其求Next数组,剩下的就是上述结论
    时间复杂度O(n)

Code

我的

#include<bits/stdc++.h>
using namespace std;
const int MAXL=1000;
char str[MAXL],a[MAXL];
int n,ans;
bool flag;

bool pd(int len)
{
    for (int i=1;i<=n;i++)
    {
        if (i<=len) a[i%len]=str[i];
        else if (str[i]!=a[i%len]) return false;
    }
    return true;    
}

int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        flag=true;
        scanf("%s",str+1);
        n=strlen(str+1);
        for (int i=n;i>=1&&flag;i--)
            if (n%i==0 && pd(n/i)) ans=i,flag=false;
        printf("%d\n",ans);     
    }
    return 0;
}

标程

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char a[1000005];
int next[1000005],len;
void getnext(){
    int i=0,j=-1;
    next[0]=-1;
    while(i<len){
        if(j==-1 || a[i]==a[j]){
            i++,j++;
            next[i]=j;
        }else j=next[j];
    }
}
int main(){
    freopen("powerstr.in","r",stdin);
    freopen("powerstr.out","w",stdout);
    int n;
    scanf("%d",&n);
    while(n--){
        scanf("%s",a);
        //if(a[0]=='.') break;
        len=strlen(a);
        getnext();
        int pos=next[len];
        if(len%(len-pos)!=0) printf("1\n");
        else printf("%d\n", len/(len-pos));
    }
    return 0;
}

T2

问题 B: Fibonacci进制
时间限制: 1 Sec 内存限制: 256 MB
题目描述
定义一种Fibonacci进制,可以将十进制数用Fibonacci数表示。Fibonacci进制中,每个位上的数值只有0或1,权值是Fibonacci数。令f0=f1=1,fi=fi-1+fi-2, N=an*fn+an-1*fn-1+…+a1*f1,写成N=anan-1..a2a1f。Fibonacci表示中,不能出现相邻的两个1。例如:自然数(十进制)表示为Fibonacci进制为1=1F,2=10F,3=100F,4=3+1=101F,5=1000F,6=5+1=1001F,7=5+2=1010F。

现在,Bsny将所有自然数按照Fibonacci进制,依次输出在屏幕上,110100101100010011010……现在,Bsny想知道这个长串的前N个数字中,包含多少个1。

输入
第一行一个整数N,表示统计范围是自然数的Fibonacci
表示的前N个数字

输出
一个数,前N个数字中1的个数。

样例输入
21
样例输出
10
提示

【样例解释】

前21个数字为110100101100010011010,共有10个1。

【数据规模】

30%的数据N≤1000;

50%的数据N≤106;

100%的数据N≤1015。

Solution

100分做法:DP
N很大,先尝试几个小数据。可以发现,每个Fibonacci表示的长度,和Fibonacci数大小有关(1,2,3,5,8,13,21……),这些值最高位上是1,后面全是0,即第一个Fibonacci表示长度为i的数是fib[i]。因此按照长度对Fibonacci表示进行分类,长度为i的数有fib[i-1]个,看做是第i组。那么第i组的总长度len[i] = fib[i-1]*i。
接下来,看1出现的次数。长度为i的数的表示中,第i-1位肯定是0。
Sum[i]表示前i组的1的个数。可以得到如下式子:Sum[i]=sum[i-1]+fib[i-1]+sum[i-2]。第i组首位1的个数为fib[i-1],i-1位为0,那么最后的i-2位的情况,恰好为1~i-2组的所有情况。
整体算法也就明了了:
1) 求出N位所在的Fibonacci表示的数的长度t
2) 求1~t中Fibonacci表示中1出现的个数。
3) 继续求解剩余字符的1。
例如求解得到最后对应Fibonacci表示为x=100100
1) 对于长度为1~5的Fibonacci表示中1的个数为sum[5],i<=100000中1的个数即为sum[5]+1。
2) 对于100000

Code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll MAXN=100;
ll fib[MAXN],slen[MAXN],cnt[MAXN],sum[MAXN],a[MAXN];
ll n,m,ans;
void prepare()
{
    fib[1]=fib[2]=1;
    slen[1]=1; slen[2]=3;
    cnt[1]=cnt[2]=1;
    sum[1]=1; sum[2]=2;
    for (ll i=3;fib[i-1]<n;i++)
    {
        fib[i]=fib[i-1]+fib[i-2];
        slen[i]=slen[i-1]+fib[i]*i;
        cnt[i]=fib[i]+sum[i-2];
        sum[i]=sum[i-1]+cnt[i];
        m=i-1;  
    }
}
void change(ll x,ll &len)//change x to fib and return the length;
{
    memset(a,0,sizeof(a));
    len=0;
    for (ll i=m;i>=1;i--)
        if (x>=fib[i+1])
        {
            a[i]=1;
            x-=fib[i+1];
            if (len==0) len=i;
        }   
}
ll calc(ll len)
{
    ll num=0,t=0;
    for (ll i=1;i<=len-1;i++)
    {
        num+=a[i];
        if (a[i]==1) t=i;
    }
    if (num==0)
    {
        ans+=sum[len-1]+1;
        return fib[len+1];
    }
    else
    {
        ll x=calc(t);
        ans+=sum[len-1]+1+x;
        return fib[len+1]+x;
    }
}
void solve()
{
    ll t,x,y,len;
    //sgma_i=1-n_fib[i]=fib[n+2]-1;
    for (t=0;slen[t]<=n;t++);
    x=n-(slen[t-1]);
    y=x%t; x=(x/t)+fib[t+1]-1;
    change(x,len);
    ans=0;
    x=calc(len);
    change(x+1,len);
    for (ll i=len;i>=len-y+1;i--) ans+=a[i];
    printf("%lld",ans);
}
int main()
{
    scanf("%lld",&n);
    if (n==0) {cout<<0<<endl; return 0;}
    prepare();
    solve();    
    return 0;
}

T3

问题 C: 发奖金
时间限制: 1 Sec 内存限制: 256 MB
题目描述
Bsny最近公司运作不佳,本年度利润才m元,但员工的奖金还是要发的,公司有n个员工,怎么发奖金这个完全由老板Bsny自己决定。Bsny想要么把这m元全发了,激励一下员工,但具体怎么分配方案有很多。比如m=1, n=2, 那么可以员工1发1元,员工2发0元;也可以员工1发0元,员工2发1元,有两种方案。

但其实,Bsny还是有点吝啬的,他想这m元不一定全部作为奖金,可以部分留给自己,这样的话,发奖金的方案数就更多了。还是以m=1, n=2为例子:

方案1:员工1发1元,员工2发0元

方案2:员工1发0元,员工2发1元

方案3:员工1发0元,员工2发0元

意味着老板Bsny发的奖金范围为[0, m]。

好奇的Bsny想知道,给定n和m,他有多少种发奖金的方案?这个答案很大,所以再给定一个p,最终的答案取模p的余数.

输入
第一行三个整数n, m, p。

输出
仅一行,一个整数表示最终的答案取模p的余数。

样例输入
2 1 5
样例输出
3
提示

对于p:设p=p1^c1 * p2^c2 * p3^c3 * … *pt ^ ct,pi为质数。

20%的数据:1 ≤ n, m≤ 15;

40%的数据:1≤n, m≤1000,p=10007;

60%的数据:保证t=1,ci=1,pi^ci≤10^5;

80%的数据:t≤2,ci=1,pi≤10^5;

100%的数据:1≤ n, m≤10^9,1≤pi^ci≤10^5,所有P不超过2^31-1。

Solution

7.5考的今天才订正完……
求组合数模一个合数,上题解
100分做法:组合+质因数分解+逆元+中国剩余定理
题目相当于求n个数的和不超过m的方案数。
首先如果是恰好等于m,那么就等价于求方程x1 + x2 + … + xn = m的解的个数,利用插板法可得到公式:C(n + m - 1, m)
现在要求不大于m的,相当于对i = 0 … m对C(n + i - 1, i)求和,根据pascal递推式可以得到答案为C(n + m, m)
现在就需要求C(n + m, m) mod P

这里我们主要要解决如何快速计算n! mod P
以及当分母有m! mod P的情况

  1. 当n,m都比较小的时候,同时P为比较大的素数时,可以直接利用逆元求解,当n,m比较大的时候,见下面两种情况(以下参考魏铭2011年国家集训队作业)

  2. 当P为素数的情况:
    我们发现n! mod P的计算过程是以P为周期的,举例如下:
    n = 10, P = 3
    n! = 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10
    = 1 * 2 *
    4 * 5 *
    7 * 8 *
    10 *
    3 * 6 * 9
    = (1 * 2)3 *
    33 * (1 * 2 * 3)
    最后一步中的1 * 2 *3可递归处理。
    因为P的倍数与P不互质,所以P的倍数不能直接乘入答案,应当用一个计数器变量cnt来保存答案中因子P的个数。
    我们提前预处理出fac[i] = 1 * 2 * 3 * … * (i – 1) * i mod P,函数calcfac(n)返回n! mod P的值,power(a, b, c)返回ab mod c的值,可用快速幂在O(logb)的时间内完成。
    typedef long long LL;
    LL calcfac(LL n)
    {
    if (n

Code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXN=100005;
ll fac[MAXN],inv[MAXN],A[MAXN],M[MAXN];
ll p[MAXN],c[MAXN];
ll n,m,cntp,MOD;


ll power(ll a,ll b,ll p)
{
    ll ret=1;
    while (b)
    {
        if (b&1)
        {
            ret*=a;
            if (p!=-1) ret%=p;
        }
        a*=a;
        if (p!=-1) a%=p;
        b>>=1;
    }
    return ret; 
}
ll extend_gcd(ll a,ll b,ll &x,ll &y)
{
    if (b==0) {x=1; y=0; return a;}
    ll d=extend_gcd(b,a%b,y,x);
    y-=a/b*x;
    return d;   
}
ll getinv(ll a,ll n)
{
    ll d,x,y;
    d=extend_gcd(a,n,x,y);
    if (d==1) return (x%n+n)%n;
    else return -1; 
}
ll calcfac(ll n,ll &cnt,const ll &P,const ll &p)
{
    if (n<p) return fac[n];
    ll seg=n/P,rem=n%P;
    ll ret=power(fac[P-1],seg,P);
    ret=ret*fac[rem]%P;
    cnt+=n/p;
    return ret*calcfac(n/p,cnt,P,p)%P;  
}
ll calcinv(ll n,ll &cnt,const ll &P,const ll &p)
{
    if (n<p) return inv[n];
    ll seg=n/P,rem=n%P;
    ll ret=power(inv[P-1],seg,P);
    ret=ret*inv[rem]%P;
    cnt-=n/p;
    return ret*calcinv(n/p,cnt,P,p)%P;  
}
void GetC(ll n,ll m)
{
    for (int i=1;i<=cntp;i++)
    {
        ll P=power(p[i],c[i],-1);
        fac[0]=1;
        for(int j=1;j<P;j++)
        {
            fac[j]=fac[j-1];
            if (j%p[i]!=0) 
                fac[j]=fac[j]*j%P;
        }
        inv[0]=1;
        for (int j=1;j<P;j++)
        {
            inv[j]=inv[j-1];
            if (j%p[i]!=0)
                inv[j]=inv[j]*getinv(j,P)%P;
        }
        ll cnt=0;
        ll ret=calcfac(n,cnt,P,p[i]);
        ret=ret*calcinv(m,cnt,P,p[i])%P;
        ret=ret*calcinv(n-m,cnt,P,p[i])%P;
        ret=ret*power(p[i],cnt,P)%P;
        A[i]=ret; M[i]=P;       
    }   
}
ll CRT(ll a[],ll m[],ll n)
{
    ll M=1,ans=0,x,y,Mi,d;
    for (int i=1;i<=n;i++)
        M*=m[i];
    for (int i=1;i<=n;i++)
    {
        Mi=M/m[i];
        d=extend_gcd(Mi,m[i],x,y);
        ans=(ans+Mi*x*a[i])%M;      
    }
    if (ans<0) ans+=M;
    return ans; 
}
int main()
{
    cin>>n>>m>>MOD;
    for (int i=2;i<=100000;i++)
    {
        if (MOD%i==0) p[++cntp]=i;
        while (MOD%i==0) c[cntp]++,MOD/=i;      
    }
    GetC(n+m,n);
    cout<<CRT(A,M,cntp)<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值