数的划分
数的划分
数的划分是一个十分经典的递推题,有多种版本。。。
由于本人是 蒟蒻 ,所以整了好长时间还没整懂...
本来早就写好了,因为比较Lazy,就一直没发...
-------------------废话到此结束------------------------------------
其基本骨架即将一个数字 n 划分成 k 份
(一)每分皆不能为空。
1. k可以是奇数、偶数。
比较经典的思路是:用 f[i][j]表示将一个数字 i 划分成 j 份时的划分方案总数。
则根据划分策略里是否存在 1 ,可以得到递推方程:
f[i][j]=0;(i<=0 || j<=0)//此处因为没有任何一个划分出的子集可以是 0 ,所以f(0,x)=0,又因为不会有数字可以划分成 0 份,所以f(x,0)=0;
f( i , j )=f( i-1 , j-1 )+f( i-j , j ) ;
边界:f( 1 , 1)=0;
代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 const int MAXN=2e2+1; 4 int f[MAXN][MAXN]; 5 inline int read() 6 { 7 int x=0,f=1;char c=getchar(); 8 while(c<'0'||c>'9'){if(c=='-')c=getchar();} 9 while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();} 10 return f*x; 11 } 12 int find(int n,int k) 13 { 14 if(n<k||n<=0||k<=0) 15 return 0; 16 if(k==1) 17 return 1; 18 int f1=0,f2=0; 19 f1=(f[n-1][k-1]!=-1?f[n-1][k-1]:(f[n-1][k-1]=find(n-1,k-1))); 20 f2=(f[n-k][k]!=-1?f[n-k][k]:(f[n-k][k]=find(n-k,k))); 21 return f1+f2; 22 } 23 int main() 24 { 25 int n=read(),k=read(); 26 //memset(f,-1,sizeof(f)); 27 f[0][0]=1; 28 for(int i=1;i<=n;i++) 29 for(int j=1;j<=k;j++) 30 { 31 if(i<j) 32 f[i][j]=0; 33 if(i-j<j) 34 f[i][j]=f[i-1][j-1]; 35 if(i>=j) 36 f[i][j]=f[i-1][j-1]+f[i-j][j]; 37 } 38 //printf("%d\n",find(n,k)); 39 printf("%d",f[n][k]); 40 return 0; 41 }
2.k只能是奇数或偶数
此时的状态转移方程并没有太大的改变,对于奇数:
f( i , 1 ) = i & 1;
f( i , j )=f (i - 1,j - 1)+f( i - j , j );
偶数:
f(i , 1)=! ( i & 1 );
f(i , j )=f ( i-2 , j-1 ) + f ( i-2*j , j );
个人觉得记忆化搜索比较顺眼。。。
(二)允许有集合为空
1.奇数+偶数
首先,很明显,
g( i , j )=∑ f ( i , k ) | 1<=k<=j;
但是如果直接那么计算,很显然会超时 .并不能满足我们的要求 .
考虑递推方程:
这次不能依据是否有1来划分了,应该以“是否放满”划分 :
g[ i , j ]=g[ i , j - 1 ]+g[ i - j , j ];
//解释: 将数字 i 分成 j 份,"可以留空",那么就可以将划分方案分为两类,(1)划分后的各个数字中有 0 ,即"有留空";(2)划分后的数字中 没有0,即"没有留空".
显然,这两种情况互不交叉,且两种情况已经包括了所有情况,因此 答案即为(1)+(2)
"划分后的数字中 有0",,可以先人为制造一个 0 ,即留出一份为空,而将剩下的划分成 j-1 份,所以是 g [ i , j-1 ].
而 "划分后的各个数字中没有 0 ",这种划分可以表示为 g[ i-j ,j ],即先将 j 份每一份都给一个 "1" ,将剩下的 (i-j) 个数字分成 j 份.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
int find(int n,int k) { if(n<0||k<=0)return 0; if(g[n][k]!=-1)return g[n][k]; else if(k==1||n==0)return g[n][k]=1; else return g[n][k]=find(n,k-1)+find(n-k,k); }
2.只能为奇数(偶数)
思路和上面一样的,注意初始化边界即可.
PS : 犹记得那个阳光明媚的周六...班主任告诉我去参加物理奥赛初赛,我一脸懵逼:我就从来没学过物理奥赛!但由于其魅(淫)力(威),还是硬着头皮去了...果然成为了mengbier!
做完寥寥几个会做的题,便无聊地推其起了这个题的方程....嗯,物理奥赛还是有收获的!