链接:https://www.nowcoder.com/acm/contest/83/D
来源:牛客网
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
空间限制: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 个人一定不会是「"重排后前方" 跟 "原排列前方" 一样的人」。
举例来说,有五个人编号为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的结果打了出来。
![](https://i-blog.csdnimg.cn/blog_migrate/5a34ae839e1612bff27702e13e457d57.png)
然后就开始找规律。用公式把第三列表示了出来,但是再表示后面的就会比较烦,继续找规律。。
发现第三列可以被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
空间限制: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 个人一定不会是「"重排后前方" 跟 "原排列前方" 一样的人」。
举例来说,有五个人编号为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的结果打了出来。