2019 ICPC 网络赛(上海)D

题目链接:D. Counting Sequences I

前置技能:多重集的排列数

S = ( n 1 ∗ a 1 , n 2 ∗ a 2 . . . . . n k ∗ a k ) S= (n_1*a_1,n_2*a_2.....n_k*a_k) S=(n1a1,n2a2.....nkak)是由 n 1 个 a 1 , n 2 个 a 2 . . . . . . n k 个 a k n_1个a_1,n_2个a_2......n_k个a_k n1a1,n2a2......nkak组成的多重集。S的全排列个数位
n ! n 1 ! n 2 ! . . . n k ! \frac{n!}{n_1!n_2!...n_k!} n1!n2!...nk!n!
n = n 1 + n 2 + . . . + n k n=n_1+n_2+...+n_k n=n1+n2+...+nk

思路

假设我们构造的序列的乘积为f,因为 2 13 > 3000 2^{13}>3000 213>3000,所以可以肯定这个对于构造的序列来说非1的数不会太多,最多也就是10多个,此外f是小于等于n*2的,具体证明不是很清楚,但是打表出来的结果确实是这样,于是猜了一下过了。所以说只要我们能够找出非1的数的大小及数量,那么就可以用多重集的排列数计算了。

由于它的个数不会太多,并且也不大(小于2*n),所以可以用搜索取逐一搜索出来。

搜索是一门艺术

AC_CODE

时间比预期的还优秀,200ms+就A了。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod = 1e9 + 7;
ll n;
ll a[3000], cnt, f[1000];
ll ans;

ll fspow(ll x, ll n) {
	ll ans = 1;
	while (n) {
		if (n & 1)ans = ans * x%mod;
		n >>= 1;
		x = x * x%mod;
	}
	return ans;
}

void init() {
	f[0] = 1;
	for (int i = 1; i < 1000; i++)f[i] = f[i - 1] * i%mod;
}

void solve(int k) {
	ll res = 1;
	for (int i = n - k + 1; i <= n; i++)res = res * i%mod;
	int c = 0;
	a[0] = a[1]; a[cnt + 1] = a[cnt] + 1;
	for (int i = 1; i <= cnt + 1; i++) {
		if (a[i] == a[i - 1])c++;
		else {
			res = res * fspow(f[c], mod - 2) % mod;
			c = 1;
		}
	}
	ans = (ans + res) % mod;
}

void dfs(int st, int k, int f, int sum) {
	if (f > 2 * n)return;
	if (sum + n - k > 2 * n)return;
	if (f == sum + n - k) {
		solve(k);
		return;
	}
	for (int i = st; i <= n && f*i <= 2 * n; i++) {
		a[++cnt] = i;
		dfs(i, k + 1, f*i, sum + i);
		cnt--;
	}
}

int main() {
	init();
	int t; scanf("%d", &t);
	while (t--) {
		cnt = 0;
		scanf("%lld", &n);
		ans = 0;
		dfs(2, 0, 1, 0);
		printf("%lld\n", ans);
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值