整数的划分问题是一个很经典的问题,它的变形也非常的多,总结了一下,大概有以下几种变形:
1) 将 N 划分为若干个正整数的和的划分数
2) 将 N 划分为若干个不同的正整数的和的划分数
3) 将 N 划分为不超过K 个正整数的和的划分数
4) 将 N 划分为不超过K 个不同正整数的和的划分数
5) 将 N 划分为最大数不超过K 的正整数的和的划分数
6) 将 N 划分为若干个连续正整数的和的划分数
ohoh,怎么这么多@@
变形(1): 将 N 划分为若干个正整数的和的划分数。比如 6,可以这样划分:
6
5 + 1
4 + 2, 4 + 1 + 1
3 + 3, 3 + 2 + 1, 3 + 1 + 1 + 1
2 + 2 + 2, 2 + 2 + 1 + 1, 2 + 1 + 1 + 1 + 1
1 + 1 + 1 + 1 + 1 + 1
所以答案应该是 11.
分析:
根据 n和 m的关系,考虑以下几种情况:
(1 )当 n=1时,不论 m的值为多少( m>0),只有一种划分即 {1};
(2) 当 m=1时,不论 n的值为多少,只有一种划分即 n个 1, {1,1,1,...,1};
(3) 当 n=m时,根据划分中是否包含 n,可以分为两种情况:
( a)划分中包含 n的情况,只有一个即 {n};
( b)划分中不包含 n的情况,这时划分中最大的数字也一定比 n小,即 n的所有 (n-1)划分。
因此 f(n,n) =1 + f(n,n-1);
( 4)当 n<m时,由于划分中不可能出现负数,因此就相当于 f(n,n);
( 5)但 n>m时,根据划分中是否包含最大值 m,可以分为两种情况:
( a)划分中包含 m的情况,即 {m, {x1,x2,...xi}}, 其中 {x1,x2,... xi} 的和为 n-m,可能再次出现 m,因此是( n-m)的 m划分,
因此这种划分个数为 f(n-m, m);
( b)划分中不包含 m的情况,则划分中所有值都比 m小,即 n的 (m-1)划分,个数为 f(n,m-1);
因此 f(n, m) = f(n-m, m)+f(n,m-1);
因此,设dp[n][m] 表示将 n 划分为 最大数不超过 m 的划分个数,有
dp[n][m] = 1 ( m = 1)
dp[n][m] = dp[n][m-1] + 1 ( m = n)
dp[n][m] = dp[n][n] ( m > n)
dp[n][m] = dp[n][m-1] + dp[n-m][m] ( m < n)
Code:
- #include <cstdio>
- #include <cstring>
- #define MAXN 500
- using namespace std;
- int dp[MAXN][MAXN];
- int main()
- {
- int i,j;
- memset(dp,0,sizeof(dp));
- for(i = 1;i < MAXN; ++i)
- dp[i][1] = 1;
- for(i = 1;i < MAXN; ++i)
- for(j = 1;j < MAXN; ++j){
- if(i > j)
- dp[i][j] = dp[i][j-1] + dp[i-j][j];
- else if(i == j)
- dp[i][j] = dp[i][j-1] + 1;
- else dp[i][j] = dp[i][i];
- }
- }
变形(2):将 N 划分为若干个不同正整数的和的划分数 TOJ.3511 Staircases
依然是DP,转移方程为 a[k][n]=a[k+1][n]+a[k+1][n-k];其中a[k][n]表示将n划分为不小于k种不同的数的划分法。
初始化为 a[n][n]=1 ; a[k][n]=0 (k>n).
Code:
- #include <cstdio>
- #include <cstring>
- #define MAXN 500
- using namespace std;
- int dp[MAXN][MAXN];
- int main()
- {
- int i,j;
- memset(dp,0,sizeof(dp));
- for(i = 1;i < MAXN; ++i)
- dp[i][i] = 1;
- for(i = 2;i < MAXN; ++i)
- for(j = 1;j < i; ++j)
- dp[j][i] = 0;
- for(i = 2;i < MAXN; ++i)
- for(j = i-1;j >= 1; --j)
- dp[j][i] = dp[j+1][i] + dp[j+1][i-j];
- }
变形(3):将 N 划分为不超过K 个正整数的和的划分数
解法:dp[i][j] 表示将 i 划分为 j 份的划分数,则转换为求 sigma( dp[N][ i ] ) (i <= K);
dp[i][j] 的转移方程为 dp[i-1][j-1] + dp[i-j][j];
详细见http://wurong81.spaces.live.com/blog/cns!5EB4A630986C6ECC!254.entry
Code:
- #include <cstdio>
- #include <cstring>
- #define MAXN 500
- using namespace std;
- int dp[MAXN][MAXN];
- int main()
- {
- int i,j;
- // 求将某个数划分为 m 份的划分数
- for(i = 1;i < MAXN; ++i)
- for(j = 1;j <= (i<m?i:m); ++j)
- dp[i][j] = dp[i-1][j-1] + dp[i-j][j];
- }
变形(4):将 N 划分为不超过K 个不同正整数的和的划分数
解法:即为变形2的方程意义。a[k][n] 表示将 N 划分为不超过K份不同的数的和的划分数
变形(5):
变形(6): 这个最简单了。N 可以写为连续K(K >= 2)个正整数的和,则K <= sqrt(2*N);
令 i 从 K 到 2 循环,如果 i 为奇数 且 N %i == 0,则可分解为 i 个数,第一个数为 N/i - i/2;
如果 i 为偶数 且 N %i == i/2,则可分解为i 个数,第一个数为 (N-1)/i - i/2 + 1;
给出一个N,求1..N中与N互质的数的和
if gcd(n,i)=1 then gcd(n,n-i)=1 (1<=i<=n)
反证法:
如果存在K!=1使gcd(n,n-i)=k,那么(n-i)%k==0
而n%k=0
那么必须保证i%k=0
k是n的因子,如果i%k=0那么 gcd(n,i)=k,矛盾出现;
于是问题变的非常简单: ANS=N*phi(N)/2
i,n-i总是成对出现,并且和是n
于是可能就有人问了,如果存在n-i=i那不是重复计算?
答案是不会
因为:
n=2*i->i=n/2
1.如果n是奇数,那么n!=2*i,自然也不存在 n-i=i和重复计算之说
2.如果n是偶数,n=2*i成立,gcd(n,n/2)必然为n的一个因子,这个因子为1当且仅当n==2
于是对于n>2的偶数,绝对不存在gcd(n,n/2)=1所以更别说什么重复计算了
对于n==2
ans=2*1/2=1,正好也满足
所以得到最终公式:
ans=N*phi(N)/2
求小于等于N 的所有与N互质的数的乘积modN。这是今天训练赛的一道题。RP极高的我们愣是找出了结论。
结论:对于 1,2,4,P^n, 2*P^n,答案为 N-1,其余情况都是1。也就是说,1,2,4,以及只有一个质因子(奇数)或者它的1/2只有一个
质因子(偶数),答案是N-1.其余情况答案均是 1。
附程序:
- #include <cstdio>
- #include <cstring>
- #include <algorithm>
- #include <vector>
- #include <queue>
- #define min(a,b) ((a)<(b)?(a):(b))
- #define INF 0x1f1f1f1f
- #define MAXN 40000
- using namespace std;
- int p[MAXN],cont[50];
- bool flag[MAXN];
- int k,num;
- void get_prime(){
- memset(flag,false,sizeof(flag));
- k = 0;
- for(int i = 2;i < MAXN; ++i){
- if(!flag[i])
- p[k++] = i;
- for(int j = 0;j < k && i*p[j] < MAXN; ++j){
- flag[i*p[j]] = true;
- if(i % p[j] == 0) break;
- }
- }
- }
- bool cal(int n){
- num = 0;
- for(int i = 0;p[i] <= n && i < k; ++i){
- if(n%p[i] == 0){
- while(n%p[i] == 0)
- n /= p[i];
- num++;
- if(num > 1) return false;
- }
- if(n == 1) break;
- }
- if(n > 1) num++;
- if(num > 1) return false;
- return true;
- }
- int main()
- {
- int n;
- get_prime();
- while(scanf("%d",&n) != EOF){
- if(n != 4 && n % 4 == 0){
- printf("1/n");
- continue;
- }
- if(n%2 == 0){
- if(cal(n/2)){
- printf("%d/n",n-1);
- continue;
- }else{
- printf("1/n");
- continue;
- }
- }else{
- if(cal(n)){
- printf("%d/n",n-1);
- continue;
- }else{
- printf("1/n");
- continue;
- }
- }
- }
- }