JZOJ2904. 【集训队互测 2012】Calc

15 篇文章 0 订阅

题意:

一个序列 a 1 , . . . , a n a_1,...,a_n a1,...,an 是合法的,当且仅当:
长度为给定的 n n n
a 1 , . . . , a n a_1,...,a_n a1,...,an 都是[1,A]中的整数。
a 1 , . . . , a n a_1,...,a_n a1,...,an 互不相等。
一个序列的值定义为它里面所有数的乘积,即 a 1 a 2 . . . a n a_1a_2...a_n a1a2...an
求所有不同合法序列的值的和。
两个序列不同当且仅当他们任意一位不一样。
输出答案对一个数 m o d mod mod取余的结果。

数据范围:

0 : A ≤ 10 , n ≤ 10. 0:A\leq10,n\leq10. 0:A10,n10.
1..3 : A ≤ 1000 , n ≤ 20. 1..3:A\leq1000,n\leq20. 1..3:A1000,n20.
4..9 : A ≤ 1 0 9 , n ≤ 20. 4..9:A\leq10^9,n\leq20. 4..9:A109,n20.
10..19 : A ≤ 1 0 9 , n ≤ 500. 10..19:A\leq10^9,n\leq500. 10..19:A109,n500.
m o d ≤ 1 0 9 , m o d mod\leq10^9,mod mod109,mod 为素数, m o d > A > n + 1 mod>A>n+1 mod>A>n+1

Analysis:

首先解决互不相同,不妨将最后序列排序,从小到大统计方案再乘上 n ! n! n!即可。
于是我们可以设 f i , j f_{i,j} fi,j,表示当前考虑到 [ 1.. i ] [1..i] [1..i],选了 j j j个数的方案。
可以 O ( n A ) O(nA) O(nA)转移,每次考虑当前 i i i选不选。能通过前 4 4 4档部分分。
考虑优化转移,套路地去想能不能从 f A , i f_{A,i} fA,i推到 f 2 ∗ A , i f_{2*A,i} f2A,i
我们考虑如此一个式子: ∏ ( a i + A ) = ∑ S ⊂ a 0 . . a n A n − ∣ S ∣ ∗ ∏ a i ∈ S a i \prod(a_i+A)=\sum_{S\subset {a_0..a_n}}A^{n-|S|}*\prod_{a_i\in{S}}a_i (ai+A)=Sa0..anAnSaiSai
通过这个式子我们也许就有了从 A A A推到 2 A 2A 2A的方法了。
我们考虑设在 [ 1 , A ] [1,A] [1,A]里选 i i i个数的价值和为 a i a_i ai
[ A + 1..2 A ] [A+1..2A] [A+1..2A] i i i个数的价值和为 b i b_i bi
那么有: f 2 A , i = ∑ j = 0 i a j ∗ b i − j f_{2A,i}=\sum_{j=0}^ia_j*b_{i-j} f2A,i=j=0iajbij
怎么算 b i b_i bi?用刚刚那个式子
b i = ∑ j = 0 i A i − j ∗ a i ∗ ( i − j A − i ) b_i=\sum_{j=0}^iA^{i-j}*a_i*(^{A-i}_{i-j}) bi=j=0iAijai(ijAi)
表示枚举那个子集,那么后面的乘积部分可以用 a i a_i ai表示出来,前面的系数是 a i a_i ai乘上有多少个这样的子集,也就是那个组合数。
组合数我们一般都可以用下降幂的形式表示出来。
m i = ∏ j = 0 i − 1 ( A − j ) , n m i = m i − 1 m_i=\prod_{j=0}^{i-1}(A-j),nm_i=m_i^{-1} mi=j=0i1(Aj),nmi=mi1。那么它就是: ( i − j ) ! − 1 ∗ m i ∗ n m j (i-j)!^{-1}*m_i*nm_{j} (ij)!1minmj
现在 b i b_i bi就可以算出来了。
我们考虑当 A A A为奇数时,递归 A − 1 A-1 A1,然后 O ( n ) O(n) O(n)递推到 A A A
为偶数直接递归 A / 2 A/2 A/2 O ( n 2 ) O(n^2) O(n2)变换到 A A A
复杂度 O ( n 2 log ⁡ A ) O(n^2\log{A}) O(n2logA)

Code:

# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;
const int N = 500 + 5;
typedef long long ll;
int f[65][N],inv[N],fac_[N],c[N];
int mi[N],nmi[N],b[N],a[N];
int n,A,mo;
inline int pow(int x,int p)
{
	int ret = 1;
	for (; p ; p >>= 1,x = (ll)x * x % mo)
	if (p & 1) ret = (ll)ret * x % mo;
	return ret;
}
inline void calc(int B,int d)
{
	if (B == 1) { f[d][0] = f[d][1] = 1; return; }
	if (B & 1)
	{
		calc(B - 1,d + 1); int A = B; f[d][0] = 1;
		for (int i = 1 ; i <= n ; ++i) f[d][i] = (f[d + 1][i] + (ll)f[d + 1][i - 1] * A % mo) % mo;
	}else
	{
		calc(B >> 1,d + 1); int A = B >> 1;
		for (int i = 0 ; i <= n ; ++i) a[i] = f[d + 1][i];
		mi[0] = nmi[0] = c[0] = 1;
		for (int i = 0 ; i < n ; ++i) mi[i + 1] = (ll)mi[i] * (A - i) % mo;
		for (int i = 1 ; i <= n ; ++i) c[i] = (ll)c[i - 1] * A % mo,nmi[i] = pow(mi[i],mo - 2);
		for (int i = 0 ; i <= n ; ++i)
		{
			b[i] = 0;
			for (int j = 0 ; j <= i ; ++j) b[i] = (b[i] + (ll)c[i - j] * a[j] % mo * nmi[j] % mo * fac_[i - j] % mo) % mo;
			b[i] = (ll)b[i] * mi[i] % mo;
		}
		for (int i = 0 ; i <= n ; ++i)
			for (int j = 0 ; j <= i ; ++j) f[d][i] = (f[d][i] + (ll)a[j] * b[i - j] % mo) % mo;
	}
}
int main()
{
	scanf("%d%d%d",&A,&n,&mo),inv[1] = fac_[0] = fac_[1] = 1;
	for (int i = 2 ; i <= n ; ++i)
		inv[i] = (ll)(mo - mo / i) * inv[mo % i] % mo,fac_[i] = (ll)fac_[i - 1] * inv[i] % mo;
	calc(A,0); printf("%d\n",(ll)f[0][n] * pow(fac_[n],mo - 2) % mo);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值