【JZOJ 省选模拟】Exercise

题目

Description
Farmer John(又)想到了一个新的奶牛晨练方案! 如同之前,Farmer John 的 N 头奶牛( 1 ≤ N ≤ 7500 )站成一排。对于 1 ≤ i ≤ N 的每一个 i ,从左往右第 i 头奶牛的编号为 i 。他告诉她们重复以下步骤,直到奶牛们与她们开始时的顺序相同。给定长为 N 的一个排列 A ,奶牛们改变她们的顺序,使得在改变之前从左往右第 i 头奶牛在改变之后为从左往右第 A i 头。 例如,如果A=(1,2,3,4,5),那么奶牛们总共进行一步就回到了同样的顺序。如果A=(2,3,1,5,4),那么奶牛们总共进行六步之后回到起始的顺序。每步之后奶牛们从左往右的顺序如下:

0 步:(1,2,3,4,5)(1,2,3,4,5)
1 步:(3,1,2,5,4)(3,1,2,5,4)
2 步:(2,3,1,4,5)(2,3,1,4,5)
3 步:(1,2,3,5,4)(1,2,3,5,4)
4 步:(3,1,2,4,5)(3,1,2,4,5)
5 步:(2,3,1,5,4)(2,3,1,5,4)
6 步:(1,2,3,4,5)

计算所有可能的 N ! 种长为 N 的排列 A 回到起始顺序需要的步数的乘积。
由于这个数字可能非常大,输出答案模 M 的余数( 10^8 ≤ M ≤ 10^9 + 7 , M 是质数)。其中 b>1 为一个编译时未知的常数。(不幸的是,我们没有找到对于 Java 的这样的优化)。(译注:中文选手可以参考 几种取模优化方法(译自 min-25 的博客))
#include <bits/stdc++.h>using namespace std;
typedef unsigned long long ull;
typedef __uint128_t L;
struct FastMod {
ull b, m;
FastMod(ull b) : b(b), m(ull((L(1) << 64) / b)) {}
ull reduce(ull a) {
ull q = (ull)((L(m) * a) >> 64);
ull r = a - q * b; // can be proven that 0 <= r < 2b
return r >= b ? r - b : r;
}
};
FastMod F(2);
int main() {
int M = 1000000007; F = FastMod(M);
ull x = 10ULL
M+3;
cout << x << " " << F.reduce(x) << “\n”; // 10000000073 3
}

Input
文件名:exercise.in
输入的第一行包含 N 和 M。

Output
文件名:exercise.out
输出一个整数。

Sample Input
输入样例:
5 1000000007

Sample Output
输出样例:
369329541

Data Constraint

测试点 2 满足 N=8。
测试点 3-5 满足 N≤50。
测试点 6-8 满足 N≤500。
测试点 9-12 满足 N≤3000。
测试点 13-16 没有额外限制

Hint
在这里插入图片描述

思路

求所有lcm的乘积,转换为枚举一个质数p,求p出现的指数

那么
图片来自cold chair

考虑min-max容斥
图片来自cold chair
这时,min(T)显然要>0才有意义

枚举k,求min>=k的方案数,那么把pk的倍数作为环长丢进dp

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int gcd(int x,int y) 
{
	return !y?x:gcd(y,x%y);
}
const int N=7577;
int n,mod;
ll power(ll x,ll t)
{
	ll b=1;
	while(t)
	{
		if(t&1) b=b*x%mod;
		x=x*x%mod; t>>=1;
	}
	return b;
}

int bp[N],p[N],p0;
ll c[N][N],fac[N],f[N];

void dp2(int p) 
{
	for(int i=1; i<=n/p; i++) f[i]=0;
	f[0]=-1;
	for(int i=1; i<=n/p; i++)
	{
		for(int j=1; j<=i; j++) 
		{
			f[i]=(f[i]-f[i-j]*c[i*p-1][j*p-1]%(mod-1)*fac[j*p-1])%(mod-1);
		}
	}
}

int main() 
{
	freopen("exercise.in","r",stdin);
	freopen("exercise.out","w",stdout);
	scanf("%d %d",&n,&mod);
	for(int i=0; i<=n; i++) 
	{
		c[i][0] = 1;
		for(int j=1; j<=i; j++)  c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % (mod-1);
	}
	for(int i=2; i<=n; i++) for(int j=2; j<=n/i; j++) bp[i * j] = 1;
	for(int i=2; i<=n; i++) if(!bp[i]) p[++p0] = i;
	fac[0]=1; 
	for(int i=1; i<=n; i++) fac[i] = fac[i - 1] * i % (mod-1);
	ll yjy=1;
	for(int i=1; i<=p0; i++)
	{
		int mx = log2(n) / log2(p[i]);
		ll sum = 0,sp = 1;
		for(int j=1; j<=mx; j++)
		{
			sp *= p[i];
			dp2(sp);
			for(int k=1; k<=n/sp; k++) 
				sum = (sum + f[k] * fac[n - k * sp] % (mod-1) * c[n][k * sp]) % (mod-1);
		}
		if(sum < 0) sum += (mod-1);
		yjy = yjy * power(p[i],sum) % mod;
	}
	printf("%lld\n",yjy);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值