1. 程式人生 > >[CF301E]Yaroslav and Arrangements

[CF301E]Yaroslav and Arrangements

題目大意

一個長度為n的序列a如果a[1]是最小的且每一個a[i]都與a[i%n+1]相差1稱a是紅衣男孩。
一個長度在n以內的序列b如果每一個b[i]都在[1,m]內且b是非遞減的,而且重排後是紅衣男孩的序列不超過p個且至少有1個(重排後算的是本質不同的序列個數)則稱b是藍衣男孩。
求藍衣男孩的個數。

DP

你想想假如有一堆數了。我們先認為最小數是1,到時候乘上m-最大元素+1即可。
首先只考慮1有一堆1
每兩個1中間一定要夾有2,而且如果1之間的2不止1個,這些2之間還有有3。
依次類推,我們可以想到dp。當然注意原問題最後一個必須是2,我們可以在後面加一個1。
設f[i,j,k,l]表示最大元素為i,b有j個數,一共有k個間隙(即k對i中間需要一定有i+1),方案數為l。
列舉i+1的個數t,容易寫出轉移到f[i+1,j+t,t-k,l*C

k1t1]
統計答案也很簡單了。
注意只有1個元素是不合法的。
詳見程式碼。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=100+10,mo=1000000007;
int f[2][maxn][maxn][maxn],c[maxn][maxn];
int i,j,k,l,t,n,m,p,ans,num,now;
int main(){
    //freopen("arrangement.in","r",stdin);freopen("arrangement.out","w",stdout);
c[0][0]=1; fo(i,1,100){ c[i][0]=1; fo(j,1,100){ c[i][j]=c[i-1][j]+c[i-1][j-1]; if (c[i][j]>100) c[i][j]=101; } } scanf("%d%d%d",&n,&m,&p); n++; f[0][0][1][1]=1; fo(i,0,n){ num=0; fo(j,2,n) fo(l,1,p)
(num+=f[now][j][0][l])%=mo; if (m-i+1>0) ans=(ans+(ll)num*(m-i+1)%mo)%mo; fo(j,0,n) fo(k,0,n) fo(l,0,p) f[1-now][j][k][l]=0; fo(j,0,n) fo(k,1,n) fo(l,1,p) if (f[now][j][k][l]) fo(t,k,n-j) if (l*c[t-1][k-1]<=p) (f[1-now][j+t][t-k][l*c[t-1][k-1]]+=f[now][j][k][l])%=mo; now=1-now; } printf("%d\n",ans); }