/*
题意就是找等差数列,最少等差数列个数
深度挖掘信息,这个等差数列必须满的。我所谓的满,是指这个数列不能在0到59之间在找一个了
而且必须有两个,那么第一个数值必须小于30了,否则等差小于30,然后就可以在之前在找一个了
通过这样,可以把所有的等差数列描述出来
为了搜索的更快,按照等差数列的大小从大到小排列
在搜索的过程中,及时找出下限,如果下限仍然大于最优解,那么就舍去
此题如果不加强剪枝,还真不好过。按照节点个数递减的顺序搜索,也是不错的选择。
*/
// include file
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <ctime>
#include <iostream>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <bitset>
#include <algorithm>
#include <string>
#include <vector>
#include <queue>
#include <set>
#include <list>
#include <functional>
using namespace std;
// typedef
typedef __int64 LL;
//
#define read freopen("in.txt","r",stdin)
#define write freopen("out.txt","w",stdout)
#define Z(a,b) ((a)<<(b))
#define Y(a,b) ((a)>>(b))
const double eps = 1e-6;
const double INFf = 1e100;
const int INFi = 1000000000;
const LL INFll = (LL)1<<62;
const double Pi = acos(-1.0);
template<class T> inline T sqr(T a){return a*a;}
template<class T> inline T TMAX(T x,T y)
{
if(x>y) return x;
return y;
}
template<class T> inline T TMIN(T x,T y)
{
if(x<y) return x;
return y;
}
template<class T> inline T MMAX(T x,T y,T z)
{
return TMAX(TMAX(x,y),z);
}
template<class T> inline T MMIN(T x,T y,T z)
{
return TMIN(TMIN(x,y),z);
}
template<class T> inline void SWAP(T &x,T &y)
{
T t = x;
x = y;
y = t;
}
// code begin
int N;
int da[60];
int ans;
int remain;
struct node
{
int first;
int interval;
int times;
friend bool operator<(node a,node b)
{
return a.times>b.times;
}
};
node seg[1000];
int segdx;
int check(int s,int inc)
{
if(s+inc>=60) return 0;
int cnt = 0;
for(int i=s;i<60;i+=inc)
{
if(!da[i]) return 0;
cnt++;
}
return cnt;
}
void update(int s,int inc,int dx)
{
for(int i=s;i<60;i+=inc)
{
da[i]+=dx;
}
}
void dfs(int i,int curt) //可行数列的第几个了,当前的最优解
{
if(curt>ans) return ;
if(!remain)
{
if(curt<ans)
ans=curt;
return;
}
//printf("%d\n",i);
// 粗略估计一下,剩下最少能用多少个数列
int up,j=i;
for(;j<segdx;j++) //找到一个比remain小的times的数列开始
{
if(seg[j].times<=remain)
{
break;
}
}
for(;j<segdx;j++)
{
up = curt+1+(remain-seg[j].times)/seg[j].times; //求下限 ,这是一个相等重要的剪枝,要深刻理解其内涵
if( up>=ans) return;
if( check(seg[j].first,seg[j].interval) )
{
//printf("%d %d here\n",seg[j].first,seg[j].interval);
remain -= seg[j].times;
update(seg[j].first,seg[j].interval,-1);
if(da[seg[j].first]) dfs( j,curt+1); //此处只所以还是从j开始,是因为j还能满足条件,因为有重复的时间了
else dfs(j+1,curt+1);
update(seg[j].first,seg[j].interval,1);
remain += seg[j].times;
}
}
}
int main()
{
read;
write;
int tmp;
while(scanf("%d",&N)==1)
{
memset(da,0,sizeof(da));
remain = N;
for(int i=0;i<N;i++)
{
scanf("%d",&tmp);
da[tmp]++;
}
segdx = 0;
for(int i=0;i<30;i++)
{
for(int j=i+1;j<60-i;j++)
{
if((tmp=check(i,j)))
{
seg[segdx].first = i;
seg[segdx].interval = j;
seg[segdx].times = tmp;
segdx++;
}
}
}
sort(seg,seg+segdx);
ans = 17;
remain = N;
dfs(0,0);
printf("%d\n",ans);
}
return 0;
}