一个经典的问题:给定n个数,求其中的任意一个子集满足集合中的每个元素值加和正好是n的倍数。刚开始怎么也没有思路,因为n很大,直接搜索显然是不行的。后来在组合数学书上找到了这个例题(晕,之前白看了,居然把这个经典的题目都给忘了),是抽屉原理的典型应用。
假定n个数为a1,a2,...,an,前n项和分别是S1、S2、...、Sn,那么如果有一个Si模n是0,就是答案,否则,n个数模n的余数只能在1到n - 1之间,把余数作为抽屉,显然n个数放到n - 1个抽屉里面,肯定有两个数余数相等,这样取它们的差就得到了结果,算法复杂度是O(n)的。
抽屉原理的应用都十分巧妙。还有一个例子,一个屋子里面有n个人,他们的最大年龄不超过k岁,问是否肯定存在2组人(两组没有重复的人),使得这两组人的年龄和相等。n个人的组合一共有2 ^ n种方法,注意到最大情况n的人的年龄和是n * k,这样如果2 ^ n > n * k,根据抽屉原理,一定存在两种组合他们的年龄和相等,而且没有重复的人(如果重复,把那个人删去就行了)。所以O(1)的时间就判断出了结果。
不过在最开始做题目(PKU 2356)的时候,虽然代码很短,但是错了好多次。首先是数组开小了,然后发现输出的时候题目要求的是输出数但是我给输出下标了。最后的问题出在一个很关键的地方,在取模为零的时候,我本应该直接就记录产生了解,但是我以为取模为零的时候下次肯定会产生重复的标记,就没有特殊处理。其实如果第一个数取模就是0的话,就有可能后面不产生重复的标记,这样我的程序就错了,还有就是最后一个是取模为零的时候也会出问题。想了很久才想到这个问题,改正之后终于过了。以后写程序要仔细,不能想当然啊。
附PKU 2356代码:
#include <cstdio>
const int N = 10010;
int main()
{
int a[N], n, mod[N] = {0}, tmp = 0, len = 0, pos;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
if (len) continue;
tmp = (tmp + a[i]) % n;
if (tmp == 0)
{
len = i;
pos = 1;
}
if (mod[tmp])
{
len = i - mod[tmp];
pos = mod[tmp] + 1;
}
else
mod[tmp] = i;
}
printf("%d\n", len);
for (int i = 0; i < len; i++)
printf("%d\n", a[pos+i]);
return 0;
}
posted on 2009-06-12 09:09
sdfond 阅读(530)
评论(2) 编辑 收藏 引用 所属分类:
Algorithm - Combinatorics