【NOIP2013模拟10.23】君と彼女の恋

感想

这道题十分坑爹,上面一大堆没用的废话,还没分背景!让我差点漏了关键的模数:“神的电话”。

题目大意

给出两个正整数n,m。
求出所有满足条件的序列的个数
设A为序列,则
1:对于任意一对i, j,A[i] mod m !=A[j] mod m
2: |A|i=1A[i]=n
简单的来说就是整个数列的和为n,所有数mod m各不相同

对于 20% 的数据 ,n≤20,m≤5。
对于 40%的数据 ,n≤300,m≤10。
对于 70 %的数据, n≤10^18,m≤20。
对于 100% 的数据, n≤10^18,m≤100。


分析

40分肯定很容易拿啦,DP,设F[i,j,s]表示序列长度为i,和为j,数列mod m结果的情况(S为二进制)的方案数,答案就是 F[i,n,s]i

一定要记得mod 那个神的电话号码

再看70%以上的数据,n就变成10^18 了,很明显,上面dp的不足就是j它是会达到N的,严重超时,有没有办法把 j 转化得更小呢?

我们发现,可以把 j 的表示换一换,代表所有数分别mod m再加起来的和,
这下子,有效的j就不止一个了,所有(n-j) mod m=0的j都是合法的。而j 最大也就是1..m的和,即最大5050,做完之后只用再算出 (n-j)/m 个m的分配方案,即考虑给每个数加几个m的方案,乘起来,就能还原原来的样子了(因为一个数 mod m就等于删去若干个m嘛)。

可是这样还是不行,还得继续优化。我们看到40分dp的 s, 明显他已经不需要了,因为我们这个时候只用枚举取0..m-1 这些数,用01背包的思路,弄好取的顺序,就不怕取重了。

这时,dp的时间复杂度降为O( m4 ),枚举状态O( m3 ),转移O(m)

dp考虑完了,再来考虑分配m的问题。
我们现在有 (n-1)/m个m,要分配给i个数,求方案数。
这就转化为另一个问题了,记cnt=(n-1)/m 我们可以理解为:有 cnt个棒子,有i-1个挡板,把棒子分成i堆,求组合数。

一定要记得mod 那个神的电话号码

这下简单了,这个问题可以简单理解为:有cnt+i-1个数(把棒子和挡板当作同一类的东西),从中选i-1个,求组合数。很显然,就是求 Ci1cnt+i1 。这个O(i)就能求出来了。所有问题完全解决(至于c在模意义下怎么求,自己百度去,总之要用到逆元与费马小定理【这个我想了很久,因为忘了···】)

综合起来,就是先做一次DP,再用每个满足(n-j) mod m=0的F[i,j](这是组合的个数),乘以i的全排列(i的阶乘),再乘以 Ci1cnt+i1 (cnt=(n-1)/m),得出ans[i,j],最后加起来就OK了。

一定要记得mod 那个神的电话号码(很重要说三遍)

呵呵哒,写完了。其实这道题打起来十分快,但是想要想比较久,主要还是dp后面的部分比较烦,做比赛时,一开始没有想好就开始乱搞了,错了挺多次,搞的为了满分打了2个多钟头···

下面贴出代码(别看dp,是水的,O( m5 ),其他都是正常的)
const mo=905229641; //这是电话号码,大家打一下试试,看看会不会空号,但记得加日本区号,^_^
var
    m,mx,i,j,k,l,yu:longint;
    n,dur,ans,xl,xd:int64;
    ny,pre:Array[0..101]of int64;
    f:array[0..101,0..5050,0..101]of longint;

    function ksm(x,y:int64):int64;
    begin
        if y=0 then exit(1);
        if y=1 then exit(x);
        ksm:=ksm(x,y div 2);
        ksm:=ksm*ksm mod mo;
        exit(ksm*ksm(x,y mod 2)mod mo);
    end;
    function c(x,y:int64):int64;
    begin
        c:=1;
        xl:=y;
        while xl>y-x do begin
            c:=c*xl mod mo;
            xl:=xl-1;
        end;
        for l:=2 to x do c:=c*ny[l] mod mo;
        exit(c);
    end;
begin
    assign(input,'alpha.in');reset(input);
    assign(output,'alpha.out');rewrite(output);
    readln(n,m);
    f[0,0,m]:=1;
    mx:=(m+1)*m div 2;
    for i:=0 to m-1 do
        for j:=0 to mx do
            for k:=m-i downto 1 do
                if f[i,j,k]>0 then
                    for l:=k-1 downto 0 do
                        f[i+1,j+l,l]:=(f[i+1,j+l,l]+f[i,j,k])mod mo;
    for i:=1 to m+1 do ny[i]:=ksm(i,mo-2);
    yu:=n mod m;
    pre[1]:=1;
    for i:=2 to m do pre[i]:=pre[i-1]*i mod mo;
    for i:=1 to m do begin
        j:=yu;
        while (j<=n)and(j<=mx) do begin
            dur:=0;
            for k:=m-i downto 0 do
                dur:=(dur+f[i,j,k])mod mo;
            dur:=dur*pre[i]mod mo;
            xd:=(n-j)div m mod mo;
            dur:=dur*c(i-1,xd+i-1)mod mo;
            ans:=(ans+dur)mod mo;
            j:=j+m;
        end;
    end;
    writeln(ans);
end.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
noip2013普及组初赛是全国信息学奥林匹克联赛的一场选拔赛。该比赛旨在选拔初学者,对编程和算法有一定基础的学生,通过比赛形式来考察他们的知识水平和解题能力。 比赛题目通常会涉及各个领域的算法和数据结构,如图论、动态规划、数论等。题目难度逐步增加,从简单的输出结果,到复杂的程序设计与代码实现,考察选手的逻辑思维和编程能力。 参赛选手需要通过自己的思考和编程实现来解决题目,同时时间也是一个重要因素。比赛中,选手需要在规定的时间内独立完成所有题目,对于复杂的题目需要迅速想出解题思路并进行编码。因此,在比赛中,选手的临场发挥和解题速度也是需要考虑的因素。 noip2013普及组初赛的结果将作为选拔阶段的一个重要依据,选出表现出色的选手进入到更高阶段的比赛,对于他们来说,这是一次展示自己实力的机会。 此外,noip2013普及组初赛,也给了参赛选手一个交流的平台。选手们可以通过比赛结交同好,相互切磋,共同进步。同时,比赛结束后,还有详细的解题分析和讲解,有助于参赛选手对自己在比赛中的不足进行反思与改进。 总之,noip2013普及组初赛是一个考察学生编程和算法能力的选拔赛,通过比赛的形式来选拔出优秀的选手。这对于参赛选手来说,是一次展示自己才华的机会,也是一个展示自己实力和提高自己能力的平台。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值