2019 上海網絡賽 D. Counting Sequences I (dfs + 剪枝)

在這裏插入圖片描述


(你以爲是個結論題或者規律題,結果他是個暴力題)

通過小範圍數據打表會發現非1的數字個數非常有限,且最大數字不會超過 n。

證明:假設存在一個數大於 n,讓它儘量小,設爲 n + 1,因爲至少存在兩個大於 1的數字,讓令一個大於 1 的數字也儘量小,設爲 2,可以計算出對應項:2 * (n + 1) - (n + 1 + 2) + 2 = n + 1,也就是說當存在一個數大於 n 時,最小的一項也會大於 n。

那麼直接暴力打表就完事了。按遞增的順序枚舉非 1 的數字,可以計算出對應項,當項數超過3000時退出遞歸。按順序枚舉還有一個提前退出的剪枝:abc(d+1)a * b * c * (d + 1) 對應的項數 >> abcda * b * c *d,當 abcda * b * c * d 對應項超出 3000 時,後面的就不需要枚舉了,也不需要繼續遞歸下去,可以直接回到上一層遞歸。

實際這樣跑非常非常快,3ms就打完了3000個數字。


代碼:

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
typedef long long ll;
const int maxn = 3e3 + 100;
ll dp[maxn];
ll fact[maxn],ifact[maxn];
int g[maxn],top = 0;
int t,n;
ll fpow(ll a,ll b) {
	ll r = 1;
	while(b) {
		if(b & 1) r = r * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return r;
}
bool dfs(int cur,int lst) {
	if(cur > 2) {
		ll v = -1,num = 0,res = 1,sum = 0;
		ll ans = 1;
		for(int i = 1; i <= top; i++) {
			int it = g[i];
			res = res * it;
			sum = sum + it;
			if(it != v) {
				ans = ans * ifact[num] % mod;
				num = 1;
				v = it;
			}
			else {
				num++;
			}
		}	
		ans = ans * ifact[num] % mod;
		int p = res - sum + cur - 1;
		if(p > 3000) return false;
			dp[p] = (dp[p] + ifact[res - sum] * fact[p] % mod * ans % mod) % mod;
		if(p == 3000) return false;
	}
	for(int i = lst; i <= 3000; i++) {
		g[++top] = i;
		bool f = dfs(cur + 1,i);
		top--;
		if(!f) break;
	}
	return true;
}
int main() {
	fact[0] = 1;
	for(int i = 1; i <= 3000; i++) {
		fact[i] = fact[i - 1] * i % mod;	
	}
	ifact[3000] = fpow(fact[3000],mod - 2);
	for(int i = 2999; i >= 0; i--)
		ifact[i] = ifact[i + 1] * (i + 1) % mod;
	dfs(1,2);
	scanf("%d",&t);
	while(t--) {
		scanf("%d",&n);
		printf("%lld\n",dp[n]);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章