问题描述
BestCoder 1st Anniversary 1003(HDU5312)
Soda习得了一个数列, 数列的第n(n≥1)项是3n(n-1)+1. 现在他想知道对于一个给定的整数m, 是否可以表示成若干项上述数列的和. 如果可以, 那么需要的最小项数是多少? 输出最小花费
这道题很容易被当成贪心,然后就跪了。
后来看了题解也理解了很久,是三角形数的题,构造很巧妙。
这个题看上去是一个贪心, 但是这个贪心显然是错的. 事实上这道题目很简单, 先判断1个是否可以, 然后判断2个是否可以. 之后找到最小的k (k > 2)k(k>2), 使得(m - k) mod 6 = 0(m−k)mod6=0即可.
这里用到三角形数的一个性质:任意一个自然数最多只需要3个三角形数即可表示。
设f(x) = 3x(x-1)+1
M = f(x1) + f(x2) + f(x3) + …
假设最少可由k个f(x)表示,那么有: M = 6(k个三角形数的和) + k ,可以令k<6,那么k = M %6, 由于三角形数的性质,那么k>=3 的时,任意k个自然数可以被k个三角形数表示,假设成立,答案即为k.
所以要讨论一下k=0, k=1,k=2时的情况:
k=0 明显不成立,所以可以令 k=6 (6>=3), 显然成立且为最小值
先打一个f(x)<1e9的表,一共有大概19000个数,存在一个map里面,这样每次查询就可以在log级别查询
k=1时,直接查询map里是否有相应的键值,有的话结果为1,没有的话结果为7(7>=3)
同理k=2时 能找到一组满足条件的值,那么结果为2,否则结果为 8 (8 >= 3)
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<map>
using namespace std;
int form[20000];
int FN = 0;
int M;
map<int,int> Map;
void init()
{
for(int i=1;;i++)
{
int temp = 3*i*(i-1) + 1;
if(temp > 1e9) break;
form[++FN] = temp;
Map.insert(make_pair(temp,1));
}
}
int main()
{
init();
int T;
scanf("%d",&T);
while(T--)
{
int M;
scanf("%d",&M);
int res = (M - 1)%6 + 1;
if(res<3)
{
if(res == 1)
{
map<int,int>::iterator it;
it = Map.find(M);
if(it==Map.end()) printf("7\n");
else printf("1\n");
}
else
{
bool flag = false;
for(int i=1;form[i]<=M/2;i++)
if(Map.find(M-form[i])!=Map.end())
{
flag = true;
break;
}
if(flag) printf("2\n");
else printf("8\n");
}
}
else
{
printf("%d\n",res);
}
}
return 0;
}