牛客练习赛15-D(杨辉三角,组合数,逆元)

35 篇文章 0 订阅
6 篇文章 0 订阅



链接:https://www.nowcoder.com/acm/contest/83/D
来源:牛客网

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

有n(n≤500000) 个人排成一列,把他们解散后重排,使得"重排后前方" 跟"原排列前方" 一样的人不超过k(k<n) 个,问有几种方法数,答案请mod (10 9+7) 输出。
举例来说,有五个人编号为1∼5 间的整数,最初的排列由前至后依序为1, 2, 3, 4, 5,重排列后顺序由前至后变为1, 3, 4, 2, 5,其中只要编号为4 的人,"原排列前方" 跟"重排后前方" 都是编号为3 的人,故"重排后前方" 跟"原排列前方" 一样的人只有1 人。
原排列的第 1 个人和重排后的第 1 个人一定不会是「"重排后前方" 跟 "原排列前方" 一样的人」。

输入描述:

输入只有一行,有两个正整数 n,k,满足 1≤k<n≤500000。

输出描述:

请输出一行包含一个小于 109+7 的非负整数,表示总共的方法数 mod (109+7)。
 
 
 
 
 
 
思路:先是想打表找一下规律。从1到11的结果打了出来。
 

然后就开始找规律。用公式把第三列表示了出来,但是再表示后面的就会比较烦,继续找规律。。

发现第三列可以被3整除,第4列可以被11整除,第5列可以被53整除。。就想着先把这个3、11、53除掉之后找找规律。

这样发现是一个杨辉三角!!(但是一开始没有用公式算),但是这个每一列的可以整除的数又怎么出来呢,想了很久发现可以用这个公式a[x][x]=a[x][x-1]+a[x-1][x-2]计算,这样就能把所有的计算出来了,这里因为n和k很大,不能用公式初始化组合数,又有模数p很大,所以不能用lucas定理,我们使用逆元来计算。

最后跑出来是有几百ms,但是也有人几十ms过的,还没看过。

打表代码:

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
int cnt=0;
bool vis[100];
int num[100];
int sum[100];
void dfs(int x,int cnt)
{
	if(x>cnt)
	{
		int p=0;
		for(int i=2;i<=cnt;i++)
		{
			if(num[i]==num[i-1]+1)p++;
		}
		sum[p]++;
		return;
	}
	for(int i=1;i<=cnt;i++)
	{
		if(!vis[i])
		{
			num[x]=i;
			vis[i]=1;
			dfs(x+1,cnt);
			vis[i]=0;
		}
	}
}
int main()
{
	
	for(int i=1;i<=11;i++)
	{
		memset(vis,0,sizeof(vis));
		memset(sum,0,sizeof(sum));
		memset(num,0,sizeof(num));
		dfs(1,i);
		for(int j=i-1;j>=0;j--)cout<<sum[j]<<" ";
		cout<<endl;
	}
	
}

第一代TLE代码:

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
map<int,map<int,int> > mp;
map<int,int>mmp;
ll dfs(int n,int k)
{
    if(mp[n][k])
    {
        return mp[n][k];
    }
    if(n<1||k<1)return 0;
    ll ans=0;
    for(int i=k-1;i<=n-1;i++)
    {
        ans=(ans+dfs(i,k-1))%mod;
    }
    return mp[n][k]=ans;
}
ll dfs2(int n,int k)
{
    if(n==k)
    {
        if(mmp[k])
        {
            return mmp[k];
        }
        return mmp[k]=(dfs2(n,n-1)+dfs2(n-1,n-2))%mod;
    }
    return (dfs(n,k)*dfs2(k,k))%mod;
}
int main()
{
    mp.clear();
    mmp.clear();
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        mp[i][i]=mp[i][1]=1;
    }
    mmp[1]=1;
    mmp[2]=1;
    mmp[3]=3;
    mmp[4]=11;
    ll ans=0;
    for(int i=n-k;i<=n;i++)
    {
        ans=(ans+(dfs(n,i)*dfs2(i,i))%mod)%mod;
    }
    printf("%lld\n",ans%mod);
}

最终AC代码(不要使用map,用数组,会卡map):

/*
杨辉三角公式:
第n行第m个(n,m都从0开始):
C(n,m)
*/
  
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
ll N[500005];
ll mmp[500005];
void init()
{
    N[0]=1;
    N[1]=1;
    for(int i=2;i<500005;i++)
    {
        N[i]=(N[i-1]*i)%mod;
    }
}
ll quick_mod(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=(ans*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return ans;
}
ll dfs2(int n,int k)
{
    if(n==k)
    {
        if(mmp[k])
        {
            return mmp[k];
        }
        return mmp[k]=(dfs2(n,n-1)+dfs2(n-1,n-2))%mod;
    }
    ll mods=mod;
    ll n1=N[n-1],k1=N[k-1],n_k=N[n-k];
    k1=quick_mod(k1,mods-2);
    n_k=quick_mod(n_k,mods-2);
    return (((n1*k1)%mod*n_k)%mod*dfs2(k,k))%mod;
}
int main()
{
    init();
 //   mmp.clear();
    memset(mmp,0,sizeof(mmp));
    int n,k;
    scanf("%d%d",&n,&k);
    mmp[1]=1;
    mmp[2]=1;
    mmp[3]=3;
    mmp[4]=11;
    ll ans=0;
    ll mods=mod;
    for(int i=n-k;i<=n;i++)
    {
    //  ans=(ans+(dfs(n-1,i-1)*dfs2(i,i))%mod)%mod;
        ll n1=N[n-1],k1=N[i-1],n_k=N[n-i];
        k1=quick_mod(k1,mods-2);
        n_k=quick_mod(n_k,mods-2);
        ans=(ans+(((n1*k1)%mod*n_k)%mod*dfs2(i,i))%mod)%mod;
    }
    printf("%lld\n",ans%mod);
}



链接: https://www.nowcoder.com/acm/contest/83/D
来源:牛客网

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

有n(n≤500000) 个人排成一列,把他们解散后重排,使得"重排后前方" 跟"原排列前方" 一样的人不超过k(k<n) 个,问有几种方法数,答案请mod (10 9+7) 输出。
举例来说,有五个人编号为1∼5 间的整数,最初的排列由前至后依序为1, 2, 3, 4, 5,重排列后顺序由前至后变为1, 3, 4, 2, 5,其中只要编号为4 的人,"原排列前方" 跟"重排后前方" 都是编号为3 的人,故"重排后前方" 跟"原排列前方" 一样的人只有1 人。
原排列的第 1 个人和重排后的第 1 个人一定不会是「"重排后前方" 跟 "原排列前方" 一样的人」。

输入描述:

输入只有一行,有两个正整数 n,k,满足 1≤k<n≤500000。

输出描述:

请输出一行包含一个小于 109+7 的非负整数,表示总共的方法数 mod (109+7)。
 
  
 
  
 
  
思路:先是想打表找一下规律。从1到11的结果打了出来。
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值