比如{3,2,4,3,6} 可以分成{3,2,4,3,6} m=1;
{3,6}{2,4,3} m=2
{3,3}{2,4}{6} m=3 所以m 的最大值爲3。
通過調研,找到網上的解決方法主要是如下兩種:
1. http://my.oschina.net/dapengking/blog/92183
“算法原理的思想是將大問題轉換成小問題。就{3,2,4,3,6}的操作步驟:
第一步:想將數組遞減排序得{6,4,3,3,2},求出數組中所有數的和m=18,第一個最大的數b=6, m/b=3餘數爲0,當除數爲1,餘數爲0時終止。當餘數不爲0時,轉到第三步。當餘數爲0時將數組劃分爲{6},{4,3,3,2}兩個。把{4,3,3,2}看成一個新的數組。
第二步:先用{4,3,3,2}中的最大數與b=6比較,即4<b,所以再將4與最右邊的數即2相加與b比較,結果相等,則將這兩個數從該數組中除去生成新的數組,轉到第一步,現在的結果是{6},{4,2},{3,3},把{3,3}看成一個新的數組繼續重複第二步。
第三步,將數組中最大的數與最小的數取出構成一個新數組Z,剩餘的構成一個數組,然後,判斷m/Z中數字之和看是否餘數爲0,若爲0,把b替換爲Z中數字之和轉第二步,若不爲0,繼續從剩餘的數字中取出最小值加入到Z中,再判斷m/Z中數字之和看是否餘數爲0,直到爲0,轉第二步爲止。最後得到的結果是{6},{4,2},{3,3} 這時可以計算出m爲3,也可以在程序中作記載。在第二步工程過,若出現兩個數相加大於上一次的b,則將程序轉到第三步。”
具體代碼爲:
#include <iostream>
#include <queue>
using namespace std;
int partition(int *index,int begin,int end){
int i = begin;
int j = end;
int temp = 0;
while(i < j){
while((i < j) && (index[i] >= index[j])){
--j;
}
if(i < j){
temp = index[i];
index[i] = index[j];
index[j] = temp;
}
while((i < j) && (index[i] > index[j])){
++i;
}
if(i < j){
temp = index[i];
index[i] = index[j];
index[j] = temp;
}
}
return i;
}
//快速排序,按遞減順序
void quickSort(int *index,int begin,int end){
if(begin >= end){
return;
}
int p = partition(index,begin,end);
quickSort(index,begin,p-1);
quickSort(index,p+1,end);
}
bool findPartition(int *index,int length){
quickSort(index,0,length-1);//快速排序,按遞減順序
int sum = 0;
for(int i = 0 ; i < length ; ++i){
cout << index[i] << " ";
sum += index[i];
}
cout << endl;
int mMax = length;
for(int i = mMax ; i > 1 ; --i){
if(0 == sum % i){
int aSum = sum / i;
int pBegin = 0;//初始化數組索引,指向頭
int pEnd = length - 1;//初始化數組索引,指向尾
queue<int> q;
int j = 0;
for(j = i ; j > 0 ; --j){//找到mMax個組中,每個組的元素組成情況
if(aSum == index[pBegin]){
q.push(index[pBegin]);
q.push(-1);
++pBegin;
}else if(aSum > index[pBegin]){
q.push(index[pBegin]);
int temp = index[pBegin];
++pBegin;
while(temp < aSum){
temp += index[pEnd];
q.push(index[pEnd]);
--pEnd;
}
if(temp == aSum){
q.push(-1);
}else{
break;
}
}else{
//不確定是否應該清空queue
break;
}
}
if(j == 0){
while(!q.empty()){
if(-1 != q.front()){
cout << q.front() << " ";
q.pop();
}else{
cout << endl;
q.pop();
}
}
cout << "mMax:" << i << endl;
return true;
}
}
}
return false;
}
int main(){
int index[] = {3,2,4,3,6};
//int index[] = {3,3,3,3,3};
findPartition(index,sizeof(index)/sizeof(int));
return 1;
}
2. http://blog.csdn.net/v_july_v/article/details/6870251
“初始值m從n開始,依次遞減測試;數組的和爲sum,若sum%m的值不爲0,則直接跳過
對於符合sum%m = 0的每個m,掃描數組中每個元素,若該元素的狀態爲未選,將其分配到相應組
(1) 若當前組元素的和大於 sum/m,表明當前元素不適合該組,將其狀態(aux[i])置爲0
(2) 若當前組元素的和等於 sum/m, 將組號加1,繼續進行下一組的判斷
(3) 若當前組元素的和小於 sum/m,將當前加入的元素置爲已選狀態(aux[i]的值設爲當前組號),繼續判斷下一個元素加入加入當前組的情況”
具體代碼爲:
#include <cstdio>
#include <cstdlib>
#define NUM 10
int maxShares(int a[], int n);
//aux[i]的值表示數組a中第i個元素分在哪個組,值爲0表示未分配
//當前處理的組的現有和 + goal的值 = groupsum
int testShares(int a[], int n, int m, int sum, int groupsum, int aux[], int goal, int groupId);
int main()
{
int a[] = {2, 6, 4, 1, 3, 9, 7, 5, 8, 10};
//打印數組值
printf("數組的值:");
for (int i = 0; i < NUM; i++)
printf(" %d ", a[i]);
printf("\n可以分配的最大組數爲:%d\n", maxShares(a, NUM));
system("pause");
return 0;
}
int testShares(int a[], int n, int m, int sum, int groupsum, int aux[], int goal, int groupId)
{
if (goal < 0)
return 0;
if (goal == 0)
{
groupId++;
goal = groupsum;
if (groupId == m+1)
return 1;
}
for (int i = 0; i < n; i++)
{
if (aux[i] != 0)
continue;
aux[i] = groupId;
if (testShares(a, n, m, sum, groupsum, aux, goal-a[i], groupId))
return 1;
aux[i] = 0; //a[i]分配失敗,將其置爲未分配狀態
}
return 0;
}
int maxShares(int a[], int n)
{
int sum = 0;
int *aux = (int *)malloc(sizeof(int) * n);
for (int i = 0; i < n; i++)
sum += a[i];
for (int m = n; m >= 2; m--)
{
if (sum%m != 0)
continue;
for (int i = 0; i < n; i++)
aux[i] = 0;
if (testShares(a, n, m, sum, sum/m, aux, sum/m, 1))
{
//打印分組情況
printf("\n分組情況:");
for (int i = 0; i < NUM; i++)
printf(" %d ", aux[i]);
free(aux);
aux = NULL;
return m;
}
}
free(aux);
aux = NULL;
return 1;
}
通過分析這兩種算法的代碼,可以看到第一種方法有點像貪心的思想,每次都將最小的整數加入當前組,直到找到解,或者在找不到解時便將組數減1後重新尋找。而第二種方法則窮舉遍歷,無解時則回溯。
實際上,第一種方法是錯誤的。因爲貪心找不到解並不代表沒有解。比如輸入“6,4,1,2,3,2”時,運行第一種方法的代碼,得到的m=2,而實際上很明顯m=3([6],[4,2],[1,3,2])。我們看一下第一種方法執行的過程:
首先排序得到“6,4,3,2,2,1”。取m=3時,首先6直接成一組。然後取4,再從右邊最小的數開始取1,再取2的時候已經超過6,所以認爲不成立,m--。但是,4並非一定要從最小的整數開始組合,而可以直接取2組成6。所以這種方法是錯的。
第二種方法中的組數其實並不用從NUM開始取,可以從sum/max開始取,可以部分減小計算量。基於此,我也試着寫了一下整數數組最大等分組數的代碼:
#include <vector>
#include <iostream>
#include <string>
using namespace std;
bool verify(vector<int> &vc, int used[], int group_num, int group_size, int rest_size, int remain, int group_id){ // group_num表示組數,group_size表示每組的和,rest_size表示每組的剩餘空間,remain表示整個數組的剩餘空間,group_id表示當前組的編號
if(group_id == group_num+1 && remain == 0){ // 當前組編號=組數+1,並且整個數組剩餘空間爲0,迭代完畢,找到解(迭代的終止條件)
return true;
}
for(vector<int>::size_type i=0; i<vc.size(); i++){
if(used[i] == 0){ // 遍歷未使用的數字
if(vc[i] ==rest_size){ // 當前數字等於當前組剩餘空間
used[i] = group_id;
group_id++; // 當前組分配完畢,group_id加1
if(verify(vc, used, group_num, group_size, group_size, remain-vc[i], group_id)){ // 繼續往後分配,進入下一組,因此當前組剩餘空間置爲group_size,整個數組剩餘空間remain-vc[i]
return true; // 後面分配成功,則當前步也正確
}
used[i] = 0; // 後面分配不成功,則當前步也不正確,重置爲0
}
else if(vc[i] <rest_size){ // 當前數字小於當前組剩餘空間
used[i] = group_id;
if(verify(vc, used, group_num, group_size, rest_size-vc[i], remain-vc[i], group_id)){ // // 繼續往後分配,仍在當前組,當前組剩餘空間置爲rest_size-vc[i],整個數組剩餘空間remain-vc[i]
return true; // 後面分配成功,則當前步也正確
}
used[i] = 0; // 後面分配不成功,則當前步也不正確,重置爲0
}
}
}
if(group_id <= group_num){ // 當所有未使用的數字都大於當前組的剩餘空間時,沒有解,終止當前迭代
return false;
}
}
int FindMax(vector<int> &vc, int sum, int max){
int group_num;
int *used = new int[vc.size()]; // 記錄數組中的每個數的狀態,0表示還沒有使用,其他數字表示分到組的編號
memset(used, 0, vc.size()*4); // 初始化
for(group_num=sum/max; group_num>0; group_num--){ // 組數不用從vc.size()開始計算,實際上從sum/max即可
if(sum%group_num == 0){
if(verify(vc, used, group_num, sum/group_num, sum/group_num, sum, 1)){ // 迭代
for(vector<int>::size_type i=0; i<vc.size(); i++){ // 成功,則輸出結果
cout<<used[i]<<'\t';
}
cout<<endl;
delete []used;
return group_num;
}
}
}
}
int main()
{
int val;
int sum = 0, max = -1;
vector<int> vc;
while(cin>>val){
vc.push_back(val);
}
for(vector<int>::size_type i=0; i<vc.size(); i++){
sum += vc[i]; // 數組和
if(vc[i]>max){
max = vc[i]; // 數組中最大的數
}
}
for(vector<int>::iterator it=vc.begin(); it!=vc.end(); it++){
cout<<*it<<'\t';
}
cout<<endl;
cout<<FindMax(vc, sum ,max); // 找到最大的組數
return 0;
}