1.将这组数据分成两个集团,使得两个集团中数据之和相差最小,输出最终两个集团中的数据之和
如例子中的这个分组应该为:33 40和35
即输出为:40 68。
这可以看成是一个典型的背包问题
这题背包的容量相当于总价值的一半,而物品的体积和价值单位都是1,每个物品的价值=物品的体积=数的大小
背包问题就是说现在有一个容量为s的背包,现在给你n个物品,其体积和价值分别为wi,vi,那么求在容量s内能获得的最大价值. 我们用d[i]数组来保存当背包容量为i时,背包的容纳最大价值。
这可以采用动态规划来作。为了设计一个动态规划算法,需要推导一个递推关系,用较小子实例的解的形式来表示背包问题的实例的解。让我们来考虑一个由前i个物品(1≤i≤n)定义的实例,物品的重量分别为w1, …,wi、价值分别为v1,…,vi,背包的承重量为j(1≤j≤W)。设V[i,j]为该实例的最优解的物品总价值,也就是说,是能够放进承重量为j的背包中的前i个物品中最有价值子集的总价值。可以把前i个物品中能够放进承重量为j的背包中的子集分成两个类别:包括第i 个物品的子集和不包括第i个物品的子集。然后有下面的结论:
1.根据定义,在不包括第i个物品的子集中,最优子集的价值是V[i-1,j]。
2.在包括第i个物品的子集中(因此,j-wi≥0),最优子集是由该物品的前i-1个物品中能够放进承重量为j-wi的背包的最优子集组成。这种最优子集的总价值等于vi+V[i-1,j-wi]。
因此,在前i个物品中最优解的总价值等于这两个价值中的较大值。当然,如果第i 个物品不能放进背包,从前i 个物品中选出的最优子集的总价值等于从前i-1个物品中选出的最优子集的总价值。
初始条件:当j≥0时,V[0,j]=0,我们的目标是求V[n,W]。
这个问题的答案1:
#define len 64*400
int main()
{
int a[len],d[len],i,j,n,sum;
while(scanf("%d",&n)==1)
{
memset(d,0,sizeof(d)); //初始化条件,全为0
for(i=0,sum=0;i <n;i++)
{
scanf("%d",&a[i]);
sum+=a[i];
}
for(i=0;i <n;i++)
{
for(j=sum/2;j >=a[i];j--)
{
if(d[j-a[i]]+a[i] >d[j])
d[j]=d[j-a[i]]+a[i];
}
}
printf("%d %d/n",d[sum/2],sum-d[sum/2]);
}
return 0;
}
2.int Fbag(int n,int weight,int *w)
{
int a,b;
if(n==0)
{
if(weight>=w[0]) return w[0];
else return 0;
}
if(weight>=w[n])
{
a=Fbag(n-1,weight-w[n],w)+w[n];
b=Fbag(n-1,weight,w);
if(a>b) return a;
else return b;
}
return Fbag(n-1,weight,w);
}
int main()
{
int n,sum_weight(0),bag_weight,fin_weight;
cin>>n;
int *arr=new int[n];
for(int i=0;i<n;++i)
{
cin>>arr[i];
sum_weight+=arr[i];
}
bag_weight=sum_weight/2;//最接近所有重量的一半为最优解
fin_weight=Fbag(n-1,bag_weight,arr);//最终一个背包的重量
cout<<"group A:"<<sum_weight-fin_weight<<endl;
cout<<"group B:"<<fin_weight<<endl;
return 0;
}
2. 输入包含两个整数m和n,并且m<n。输出一个由m个随机数字组成的有序列表,这些随机数的范围是0...n-1,并且每个整数最多出现一次。就概率上而言,我们希望得到不需要替换的选择,其中每个选择出现的可能性都相同。
解决方法一:
int bigrand()
{
return RAND_MAX*rand() + rand();
}
/*
**函数的伪码为:
**select = m
**remaining =n
**for i = [0,n)
** if(bigrand() % remaining) <select
** print i
** select--
** remaining--
**只要m<=n,程序就选出m个整数:不能选择更多的整数,因为select变为0时不能选择更多的整数;并且选择的整数
**也不可能少于m,因为当select/remaining变为1时总会选中一个整数。
**这种解决方案当n非常大时,该代码可能非常慢
*/
void genknuth(int m, int n)
{
for(int i = 0; i<n; i++)
{
if(bigrand()%(n-i)<m)
{
cout <<i<<"/n";
m--;
}
}
}
int main()
{
srand((unsigned)time(NULL));
int m,n;
cout<<"Please enter m and n:";
cin>>m>>n;
genknuth(m,n);
return 0;
}
这个方法有一个很大的漏洞,当m=0时,程序就死掉
解决方法二:就是在一个初始为空的集合中插入随机整数,直到足够的整数。
伪代码如下所示:
initialize set S to empty
size = 0
while size < m do
t = bigrand()%n
if t is not in S
insert t into S
size++
print the elements of S in sorted order
该算法在选择元素时能够保证所有的元素都具有相同的选中概率,它的输出是随机的
void genknuth(int m, int n)
{
set<int> S;
while( S.size() < m)
S.insert(bigrand()%n);
set<int>::iterator i;
for(i = S.begin(); i != S.end(); ++i)
cout<<*i<<"/n";
}
C++标准模板库规范能够保证在O(log m)时间内完成每个插入操作,集合内部迭代需要O(m),所以整个程序需要的时间为O(m log(m)(和n相比,m较小时)。然而,该数据结构的空间消耗较大。
解法三、
for i = [0,n)
swap(i, randint(i,n-1))
这个是弄乱数据x[0...n-1]。在这个问题中,只需要搅乱数组前面m个元素
int bigrand()
{
return RAND_MAX*rand() + rand();
}
int randint(int l, int u)
{
return l + bigrand() % (u-l+1);
}
void genknuth(int m, int n)
{
int i,j;
int *x = new int[n];
for(i = 0; i < n; i++)
x[i] = i;
for(i = 0; i < m; i++)
{
j = randint(i,n-1);
swap(x[i],x[j]);
}
sort(x, x+m);
for(i=0; i<m; i++)
cout<<x[i]<<"/n";
}
int main()
{
srand((unsigned)time(NULL));
int m,n;
while(1)
{
cout<<"Please enter m and n:";
cin>>m>>n;
genknuth(m,n);
}
return 0;
}
该算法使用了n个字的内存并需要O(n+mlogm)时间
解法四:(递归)
void randselect(int m,int n)
{
assert(m>=0 && m <= n);
if(m>0)
if((bigrand()%n) < m)
{
randselect(m-1,n-1);
cout<<n-1<<",";
}
else
randselect(m, n-1);
}
3.一道ACM题 引发的思考
(1)正整数 n 除 3 的余数,等价于,正整数 n 的各位数字之和除 3 的余数;
(2)正整数 n 除 9 的余数可以通过这样的方法来计算:
计算 n 的各位数之和,设为 m,如果 m 已经是一位数,那么余数就是 m;
直到 m 是一个一位数。
fans 想睡觉了,不去管了。现在请你计算一下正整数n除一位数 m 的余数。
文件中有一些数对,一为大整数(可能大到100位)n,另一为一位数 m( > 0)。
Sample Input:
23 7
123 9
Sample Output:
2
6
2 10%2=0,偶数为0;奇数为1
3 10%3=1,各位数数字累加再取3的余数
4 100%4=0,故取末两位数对4的余数
5 10%5=0,故取个位数对5的余数
6 分别计算对2、3的余数,再孙子定理
7 10^6%7=1,故从后向前每6位分段累加再取7的余数
8 1000%8=0,故取末三位数对4的余数
9 10%9=1,各位数数字累加再取9的余数
其中比较麻烦的是7,其中对7也可这么计算:
因1000%7=-1,可从后向前每三位分段,再奇偶编号分别累加,代数和后再取7的余数
我介绍一个快速算法。
先看21可以被7整除,有什么特性呢?
就是 2 - 1*2 = 0 可以整除
63也是同样道理。
我们再举一个不整除的例子。
24 / 7 - > 2 - (4*2) = -6 - > 1 但实际余数 为 3
36 / 7 - > 3 - (6*2) = -9 - > 5 但实际余数 为 1
这样我们就可以建立一个数组 名叫 modByA = {0, 3, 6, 2, 5, 1, 4}
查表就可以了。
还有一个例子 1481 它对7的余数为 4
但用这个方法,我们看看结果
1481 - > 148 - (1 * 2) = 146 - > 14 - (6*2) = 2
modByA[2]对应的是6并不正确
怎么回事?
原因很简单 你用了两次步骤A, 所以要查表两次
于是乎,再找modByA[6]的对应 就是 4
我们再看一个更长的例子
12345345 mod 7 = 5
1234534 - ( 5*2) = 1234524
123452 - (4 *2) = 123444
12344 - (4 *2) =12336
1233 - (6*2) = 1221
122 - 1*2 = 120
12 -0*2 = 12
1 - 2*2 = -3 - > 4
modByA[4] 对应的是5 OK
不是应该查7次吗?
对啊? 7次一个循环,查一次就好了。
查表次数的规律
1 2 3 4 5 6 1 2 3 4 5 6.....
所以,最多查6次。
#include <deque>
#include <cassert>
void MaxIncreaseQueue(const std::deque<int> & source,std::deque<int> & result)
{
typedef std::deque<int> IntArray;
//怎样从source中得到最长递增子序列放到result中?
}
经过分析我们发现,首先,我们要得到这个序列的最小值,所以我们建立了我们的第一个类GetMinValue:
class GetMinValue
{
int minvalue_;
public:
explicit GetMinValue(const IntArray & in){
assert(in.size() != 0);
IntArray::const_iterator i = in.begin();
for(minvalue_ = *i,++i;i != in.end();++i){
if(minvalue_ > *i)
minvalue_ = *i;
}
}
~GetMinValue(){}
operator int() const{
return minvalue_;
}
};
可以看到,GetMinValue以一个IntArray 为参数构造,并由operator int() const来返回其最小值。这样,我们可以这样来得到source的最小值:
class GetMinValue
{
int minvalue_;
public:
explicit GetMinValue(const IntArray & in){
assert(in.size() != 0);
IntArray::const_iterator i = in.begin();
for(minvalue_ = *i,++i;i != in.end();++i){
if(minvalue_ > *i)
minvalue_ = *i;
}
}
~GetMinValue(){}
operator int() const{
return minvalue_;
}
};
可以看到,GetMinValue以一个IntArray 为参数构造,并由operator int() const来返回其最小值。这样,我们可以这样来得到source的最小值:
int min = GetMinValue(source);
然后我们开始考虑算法的问题。我想很多人都知道计算机里的堆的概念,一个堆就是一个类似于树的结构,只是它的节点满足一定的排列规则,于是比一般的树更有规律,也更复杂。我们这里构造这样一个堆(同时也是一棵树,所以我用了Tree这个名字),其父节点的值总比子节点小(或相等),其右节点的值总比左节点小,如图:
2 ——父节点值比子节点小
/ /
6 3 ——右节点值比左节点小
于是,当我们遍历整个序列的时候,就可以根据以上规则建立这个堆,然后根据深度最大的节点,回溯出整个子序列。第一步我们建立节点的定义: 首先看到Node的数据成员,有value_——节点的值,length_——节点的深度(也就是子序列的长度),parent_——父节点的指针(用于回溯),rightchild_——最右的子节点的指针,leftbrother_——左兄弟的指针。为什么要用rightchild_和leftbrother_而不用几个children_呢?显然大家都能猜到,因为这个树并不一定是二叉树,所以子节点数是不确定的。
然后大家也许会注意到Node的构造函数,必须要提供父节点的指针,这只是一个设计上的问题。我想把所有关于Node指针的操作全放在Node里面,这样就不用把parent_,rightchild_等定义为public,或提供一个修改的接口。
随后是Node的析构函数,2个delete可能会吓坏许多有良好C++编程风格的高手。我只能说,当我把this赋值给parent->rightchild_(Node构造函数里)的时候,就注定了“这样的结局”(^_^)
最后是成员的访问函数,我想应该不会有多少异议。
当构造好了节点,我们开始正式构造我们的树(堆)了。树的根root_的value_总是序列里面最小的值(这就是我们定义GetMinValue的原因),这是在树的构造函数里保证的,为的是让所有可能的序列都能在这个根下生长。而当我们每向树里加入一个值为val的节点时,都要调用AddValue(int val)函数,这个函数进一步调用AddValue(Node * parent,int val)函数的AddValue(&root,val)版本。
AddValue(Node * parent,int val)函数是递规调用的,它的逻辑是这样:
如果parent不是一个节点或者val比parent的value_值小,那么返回false,告诉调用者不能插入节点;否则对parent的每一个子节点,如果其value_值比val小,那么应该对其每一个都调用AddValue;如果没有一个子节点能调用AddValue成功的话,就把val当成parent的子节点插进来。
这个规则看似复杂,其实如果有一个流程图来表示,也许会明了得多,可惜我不会在这里画图。
现在我想我们应该来看看代码了: 大家也许会发现成员变量里的max_length_node_,这是一个追踪深度最大的节点的变量,初始化为&root_,方便最后的回溯。
函数AppendNode是真正插入节点的函数,通过一个简单的Node(parent,val)就完成了所有的动作,这就是我前面对Node的构造函数满意的地方。然后是检查深度最大的节点的变化情况,这一点应该是显而易见的。
Tree的构造函数主要用来初始化root_,要使得root_.value_的值是最小的,理由我已经说过了。析构函数更是简单得没有明显的代码。
最后是2个接口InputIntArray和OutputIntArray分别用来输入源序列和输出结果序列。注意out.push_front(p->Value())这句,因为我是从最深的子节点向上回溯的,也就是说按照相反的顺序得到最大子序列,所以用了push_front函数,这也是我使用deque而不用vector的原因。
class Node
{
int value_,length_;
Node *parent_,*rightchild_,*leftbrother_;
public:
Node(Node * parent,int value):value_(value),length_(1),parent_(parent),rightchild_(0),leftbrother_(0){
if(parent_ != 0){
length_ = parent->length_ + 1;
leftbrother_ = parent->rightchild_;
parent->rightchild_ = this;
}
}
~Node(){
if(rightchild_)
delete rightchild_;
if(leftbrother_)
delete leftbrother_;
}
//Functions for member access
int Value() const{
return value_;
}
int Length() const{
return length_;
}
Node * Parent() const{
return parent_;
}
Node * RightChild() const{
return rightchild_;
}
Node * LeftBrother() const{
return leftbrother_;
}
};
class Tree
{
Node root_,*max_length_node_;
void AddValue(int val){
AddValue(&root_,val);
}
bool AddValue(Node * parent,int val){
if(!parent || val < parent->Value())
return false;
int count = 0;
for(Node * p = parent->RightChild();p != 0;p = p->LeftBrother(),++count){
if(!AddValue(p,val))
break;
}
if(!count)
AppendNode(parent,val);
return true;
}
void AppendNode(Node * parent,int val){
Node * p = new Node(parent,val);
if(p->Length() > max_length_node_->Length())
max_length_node_ = p;
}
public:
explicit Tree(int value):root_(0,value),max_length_node_(&root_){}
~Tree(){}
void InputIntArray(const IntArray & in){
for(IntArray::const_iterator i = in.begin();i != in.end();++i)
AddValue(*i);
}
void OutputIntArray(IntArray & out){
out.erase(out.begin(),out.end());
Node * p = max_length_node_;
while(p != &root_){
out.push_front(p->Value());
p = p->Parent();
}
}
};
所有的辅助工具都介绍了,现在你也许要不耐烦的问:“说了这么多,到底怎么得到最大子序列的呢?”那么现在让我们回到最初的MaxIncreaseQueue函数: 一切就OK了!
如果你对上面的4行代码还有疑问的话,请参阅我前面的一大堆“口水”。
void MaxIncreaseQueue(const std::deque<int> & source,std::deque<int> & result)
{
Tree t = Tree(GetMinValue(source));
t.InputIntArray(source);
t.OutputIntArray(result);
}
int main()
{
typedef deque<int> IntArray;
const int size = 30;
int array[size] = {5,4,9,4,6,1,7,4,3,5,7,5,3,2,3,4,5,8,1,6,5,4,0,0,1,0,0,0,0,0};
IntArray source(array,array + size),result;
MaxIncreaseQueue(source,result);
cout<<"Max Queue:";
for(IntArray::const_iterator i = result.begin();i != result.end();++i)
cout<<*i<<' ';
cout<<'/n';
return 0;
}
void genfloyd(int m, int n)