考试T1总结(又CE?!)

考试T1CE...

最近不适合考试

T1

扶苏是个喜欢一边听古风歌一边写数学题的人,所以这道题其实是五三原题。
歌曲中的主人公看着墙边的海棠花,想起当年他其实和自己沿着墙边种了一排海
棠,但是如今都已枯萎,只剩下那一株,寄托着对他深深的思念。
沿着墙一共有 n 个位置可以种下海棠花,主人公记得自己当年和他一共种下了 m
朵,由于花的特性,海棠不能紧挨着种植,也就是两朵海棠花之间最少间隔一个不种花
的空位置。但是她记不清当时海棠花具体是怎么摆放的了,所以她想知道一共有多少方
案使得 m 朵海棠花都被种下且两两之间不是相邻的。我们将这 m 朵海棠花按照
1,2,3…m 的顺序编号,两个种花的方案不同当且仅当它们被种下的位置不同或者从左向
右数花的编号序列不同。
为了避免输出过大,答案对一个参数 p 取模

这道题一看就是DP嘛...

是个数学题

就是有n个位置,m个不同物品,问m个物品都不相邻的摆法有几种,其中m小于ceil(n/2),就是n/2上取整
思路分析:

思路一:

骗分输出最小的良心防爆零样例

思路二:

打神搜,其实T2,T3也可以这样操作,时间不够了...只拿了十分

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef unsigned int ui;
ui type,n,m,p;
inline ui arr(const ui &k){
    ui ans=1;
    for(ui i=2;i<=k;i++)
        ans=(ans*(i%p))%p;
    return ans;
}
int ans;
inline void search(const int &step,const int &pos){
    if(step==m){
        ans++;
        return;
    }
    for(int i=2;i<=n;i++){
        if(pos+i<=n&&step+1<=m)   //没有上下界剪枝
        search(step+1,pos+i);
    }
}
int main(){
    freopen("ilove.in","r",stdin);
    freoprn("ilove.out","w",stdout);    //大家注意这一行的freopen!!!!!!(目力呐喊!!!)
    scanf("%d%d%d%d",&type,&n,&m,&p);
    for(int i=1;i<=n-(m-1)*2;i++) //遍历每个可能的点找可能的路线,所以非常慢,只能处理范围在20以内的点
        search(1,i);
    cout<<ans*arr(m);
    return 0;
}

思路三(正经思路):

一开始的思路是考虑n个位置选m个位置再减去相邻情况,然而这并不好代码实现,而且最大数据范围是n<=2000000,m<=1000000,然而组合数最广为人知的求法要么是前缀和(杨辉三角),要么是公式计算,

前缀和算法会因为数据范围而时间爆炸,公式计算因为取模运算的存在而正确性爆炸...

双倍爆炸,双倍快乐,算法两开花!!!

前缀和的爆炸原因不解释,有目共睹...

下面讲下关于组合数取模的错误的原因:

现给出组合数计算公式(并不想百度百科...):


本式值即为在n个互不相同的东西中取m个的方案总数

并且可以直观的看出该值的直接公式运算需要除法的参与,

已知四则运算中的加减乘满足“一边算一边模还保证正确性”定律(瞎起的名字)

除可不可以?

那么现在考虑如下式子:

15/21 (mod 20)

15/7   (mod 3)

14/6   (mod 3)

如果把分子分母分别进行模运算再除,得到的值显然不正确,

那么现在就要考虑乘法逆元了

整除也不行(考虑15/5 (mod 7))

所以要么把乘法逆元板子搬过来,要么就把运算全都变成乘运算

现在考虑推一个保证正确的式子再将其尽可能的化简

现有m个不同物品摆在n个位子,那么就有n-m个空位

现在保证正确的方案就是把这几个空位固定,在它们之间插空摆物品,以保证每两个物品之间至少有一个空位

如图:

每两点的中间(包括最边上的两点之外那两条线)就是合法方案的可选位置

那么就是n-m+1个位置中选择m个位置摆物品,在考虑物品的排列顺序就好了

就是这样:

又知:

辣么它们的乘机化简下,就是这样滴:

那么剩下的只有乘法运算辣!

最后说一点,由于模数最大好像是109,所以要开long long,并且由于本题经过分析后数据范围就相当于很小了,所以不用专门写函数加上inline加速,根本没必要...

代码(及其简短):

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
long long n,m,p;
long long type;  //当时type为什么要开long long
int main(){
    scanf("%d%d%d%d",&type,&n,&m,&p);   //type变量用来判别测试点类型(骗分的)
    long long ans=1;
    for(int i=n-2*m+2;i<=n-m+1;i++)
        ans=ans*i%p;
    printf("%d\n",ans);
    return 0;
}
posted @ 2019-06-25 15:10  _Alex_Mercer  阅读(229)  评论(0编辑  收藏  举报