题目大意:
给一个N<=1000.一个M <=1e12
然后给N个数字,分别为a1,a2...an
问,用若干个数字,ai,aj,ak....,他们的乘积,是M的倍数。
1、满足是M的倍数
2、尽可能少用数字
3、满足1,2条件的情况下,数字的和尽可能的小
4、无解输出-1
一个1e12的数字的约数,不会超过1W个。 因为一个数字的约数的公式是
ANS=(1+a)(1+b)(1+c)...(1+p)
其中a,b,c..p是这个数字的分解质因数后,每个质数的幂。
粗略了估算一下,然后最坏的情况也不过6个数字都是6次方而已。总之,是一个很小的数字啦。
然后给这些数字排序,当做状态。
f[i][j] 表示用前i个数字,拼出j的倍数,所用的最少的数字数量。
g[i][j]则表示用前i个数字,拼出j的倍数,在用最少数字的时候,最小和为多少。
=======分割线====
上面是不是语序混乱了……
这道题其实类似背包……
对于一个ai,这个数字包含的因子中,和题目给的M不公共的部分,我们是不关心的。我们只关心公共部分。
也就是,我们希望组合出公共因子乘积为X的各种状态。
比如对于样例60而言,他的约数为
1 2 3 5 6 12 15 20 30 60 (有没有漏的,有漏的假装不知道就行……举个例子不是很重要)
对于读入的数据2 4 6 5 2 假如再加一个数字21
对于21,实际上有用的只有其中3,另外一个因子7可以视而不见了~。
然后就是对于上述状态【1 2 3 5 6 12 15 20 30 60 】,我们用题目给的n个数字,像做【01背包】一样,去塞,看看能否拼出这些状态,并计算得出这个状态所需最小代价。
然后这道题就是一个01背包问题了。这道题map会T,要用hash。
#include <iostream>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstring>
#include <vector>
#include <map>
#include <string>
using namespace std;
typedef long long LL;
const int maxn = 1000 + 100;
int n;
const LL inf = 1LL<<60;
LL a[maxn], m;
vector<LL> divisor;
int d_t=0;
//map<LL, int>mp;
vector<LL>f,g;
int haxi[1000000], mod = 999983;
inline void ins(LL k, int p)
{
haxi[k%mod] = p;
}
inline int get(LL k)
{
return haxi[k%mod];
}
void check(LL k) //计算K的所有约数,保存在divisor数组中,数量是d_t个
{
for (LL i = 1; i * i <= k; ++ i)
if (k % i == 0)
{
divisor.push_back(i);
divisor.push_back(k / i);
}
sort(divisor.begin(), divisor.end());
d_t = unique(divisor.begin(), divisor.end()) - divisor.begin();
f = g = divisor; //仅仅为了数组【容量】初始化,这样我感觉能快一点…… 纯粹当数组用,里面元素的值不重要……
for (int i = 0; i != d_t; ++i)
{
//mp[divisor[i]] = i;
ins(divisor[i], i);
}
}
inline LL gcd(LL a,LL b)
{
LL r;
while(b>0)
{
r=a%b;
a=b;
b=r;
}
return a;
}
struct node
{
int pre;//前驱状态
LL w; //前驱转移而来的那个数组下标,保存起来,为了输出好用
};
node pre[1000+10][10000];//记录DP状态转移的前驱,第一维表示的第几个阶段,第二维才是状态。 第I阶段的J状态的前驱,-1表示从上一阶段的j状态转移而来。其他值表示
int output[maxn], tail=0;
void doit()
{
memset(pre, -1, sizeof(pre));
if (m==1) //对1特判
{
printf("1\n");
LL tmp =inf, ans=-1;
for (int i = 1; i<=n;++i)
if (a[i]<=tmp)
{
ans=i;
tmp=a[i];
}
printf("%I64d\n", ans);
return;
}
for (int i = 0; i!=d_t;++i) f[i] = g[i] = inf;
f[0] = 0;
g[0] = 0;
for (int i = 1; i <= n; ++ i) //这里就是一个01背包DP了
{
for (int j = d_t - 1; j >= 1; -- j)
{
LL tmp = gcd(divisor[j], a[i]);
// int idx = mp[divisor[j] / tmp];
int idx = get(divisor[j] / tmp); //这些gcd的过程,表示我们只关心有用的因子。
if (f[j] > f[idx] + 1)
{
f[j] = f[idx] + 1;
g[j] = g[idx] + a[i];
pre[i][j] = {idx, i};
}
if (g[j] > g[idx] + a[i] && f[j] == f[idx] + 1)
{
g[j] = g[idx] + a[i];
pre[i][j] = {idx, i};
}
}
}
if (f[d_t - 1] == inf) printf("-1\n"); //判断是否有解
else
{
int jie=n; //从最后阶段往回,找前驱,输出解
for (int i = d_t - 1; i; i = pre[jie--][i].pre)
{
while (pre[jie][i].w == -1) --jie;
output[++tail] = pre[jie][i].w;
}
printf("%d\n", tail);
for (int i = 1; i <= tail; ++ i) printf("%d ", output[i]);
}
}
int main()
{
scanf("%d%I64d", &n, &m);
for (int i = 1; i<=n;++i) scanf("%I64d", &a[i]);
check(m);
doit();
return 0;
}