bzoj3612 平衡 (dp)
设f[i][j]为把i拆成j个不重复的、大于0小于等于N的数的方案数
我们考虑一个方案是怎么来的:(初始状态是f[0][0]=1)
如果这个方案里有1,那它是先把原来的状态的每个数加1、然后再增加一个1
如果这个方案没有1,那它是把原来的状态直接每个数加1得来的
就对应了方程$f[i][j]=f[i-j][j]+f[i-j][j-1]$
但这还不够,还没有限定这个数不能大于N
由于我们每次加的都是1,所以只需要把加出来了N+1的情况减掉就可以
那这个f[i][j]的包含N+1的状态,一定能由f[i-N-1][j-1],就是相当于直接把这个N+1去掉转移过来,把它减下去就可以了
然后枚举左右两边去的个数、再枚举力矩和,统计答案。注意是可以把正中间那个位置去掉的
1 #include<bits/stdc++.h> 2 #define pa pair<int,int> 3 #define CLR(a,x) memset(a,x,sizeof(a)) 4 using namespace std; 5 typedef long long ll; 6 const int maxn=10010,maxk=12; 7 8 inline ll rd(){ 9 ll x=0;char c=getchar();int neg=1; 10 while(c<'0'||c>'9'){if(c=='-') neg=-1;c=getchar();} 11 while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); 12 return x*neg; 13 } 14 15 int N,K,P; 16 int f[maxn*maxk][maxk]; 17 18 inline void getf(){ 19 CLR(f,0); 20 f[0][0]=1; 21 for(int i=1;i<=N*(K-1);i++){ 22 for(int j=1;j<K&&i-j>=0;j++){ 23 f[i][j]=f[i-j][j]+f[i-j][j-1]-(i-N-1>=0?f[i-N-1][j-1]:0); 24 f[i][j]%=P; 25 // printf("%d %d %d\n",i,j,f[i][j]); 26 } 27 } 28 } 29 30 int main(){ 31 //freopen("","r",stdin); 32 int i,j,k; 33 for(int t=rd();t;t--){ 34 N=rd(),K=rd(),P=rd(); 35 int ans=0; 36 if(K==1){printf("1\n");continue;} 37 getf(); 38 for(i=1;i<K;i++){ 39 for(j=i;j<=N*i;j++){ 40 ans=(ans+1ll*f[j][i]*f[j][K-i]+1ll*f[j][i]*f[j][K-i-1])%P; 41 // printf("!%d %d %d\n",i,j,ans); 42 } 43 } 44 printf("%d\n",ans); 45 } 46 return 0; 47 }