1. 程式人生 > 實用技巧 >codeforces 1437 F - Emotional Fishermen (計數dp)

codeforces 1437 F - Emotional Fishermen (計數dp)

題目連結:https://codeforces.com/contest/1437/problem/F

一個很重要的性質:happy 的漁夫手中的魚的重量一定是遞增的,且每個 happy 漁夫的魚都比前一個 happy 的漁夫重至少兩倍
於是我們就可以列舉 happy 的漁夫,剩下的漁夫肯定都是 sad 的

首先將 \(a\) 排序,\(dp[i]\) 表示第 \(i\) 個漁夫是 happy 的方案數,
轉移的時候,列舉前一個 happy 的漁夫 \(j\)
注意,前一個漁夫的 \(a[j] <= \frac{a[i]}{2}\), 所以設 \(px[i]\) 為滿足 \(a[x] <= \frac{a[i]}{2}\)

的最大的位置 \(x\)
所以 \(j\) 只需要列舉到 \(px[i]\) 即可

同時,\(px[i]\) 以內的所有不是 happy 的漁夫肯定都是 sad 的,現在來考慮安排這些漁夫位置的方案數
當前已經安排好了 \(i, j, px[j]\) 這些漁夫,所以還剩餘 \(n - 2 - px[j]\) 個空位,還有 \(px[i] - px[j] - 1\) 個漁夫需要安排(-1 是因為 \(j\)\(px[i]\) 中),剩下的漁夫要安排在漁夫 \(i\) 的後面才能保證他們是 sad,而後來的 happy 的漁夫肯定比當前 \(i\) 漁夫的 \(a\) 要大,所以這些 sad 的漁夫安排在後面的任意位置都可以,而漁夫 \(i\)

則必須被安排在當前序列中的第一個空位,這樣才能保證 \(i\) 漁夫是 happy 的,否則如果前面有空位,那後來填進去的漁夫一定會使得漁夫
\(i\) 不 happy

\(dp\) 轉移方程為:
$$dp[i] = \sum_{0}^{px[i]}A(n - 2 - px[j], px[i] - px[j] - 1)$$

最後要特別注意,因為最大的漁夫必須是 happy 的,所以第 \(n - 1\) 小的漁夫一定要滿足 \(a[n - 1] <= \frac{a[n]}{2}\), 也就是 \(px[n] = n - 1\)
否則的話方案數一定為 \(0\)

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;

const int maxn = 5010;
const int M = 998244353;

int n, m;
int a[maxn], dp[maxn], fac[maxn], ifac[maxn], px[maxn];

int sta[maxn], p[maxn], tail = 0;

int qsm(int i, int po){
	int res = 1;
	while(po){
		if(po & 1) res = 1ll * res * i % M;
		po >>= 1;
		i = 1ll * i * i % M;
	} 
	return res;
}

int A(int n, int m){
	if(m > n) return 0;
	return 1ll * fac[n] * ifac[n - m] % M;
}

ll read(){ ll s = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); } while(ch >= '0' && ch <= '9'){ s = s * 10 + ch - '0'; ch = getchar(); } return s * f; }

int main(){
	fac[0] = 1; 
	for(int i = 1 ; i <= 5000 ; ++i) fac[i] = 1ll * fac[i - 1] * i % M;
	ifac[5000] = qsm(fac[5000], M - 2);
	for(int i = 4999 ; i >= 0 ; --i) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % M;
	
	n = read();
	for(int i = 1 ; i <= n ; ++i) a[i] = read();
	
	sort(a + 1, a + 1 + n);
	
	int top = 0;
	for(int i = 1 ; i <= n ; ++i){
		while(a[top + 1] * 2 <= a[i]) ++top;
		px[i] = top; 
	}
	
	dp[0] = 1, px[0] = -1;
	for(int i = 1 ; i <= n ; ++i){
		for(int j = 0 ; j <= px[i] ; ++j){
			dp[i] = (dp[i] + 1ll * dp[j] * A(n - 2 - px[j], px[i] - px[j] - 1) % M) % M;
		}
	}
	
	if(px[n] == n - 1) printf("%d\n", dp[n]); // 最大的數必須開心 
	else printf("0\n");
	
	return 0;
}