【Codeforces Round 365 (Div 2)E】【乘除法DP map映射 约数分解】Mishka and Divisors n个数中选最小数量使得乘积为K的倍数

E. Mishka and Divisors
time limit per test
1 second
memory limit per test
256 megabytes
input
standard input
output
standard output

After playing with her beautiful array, Mishka decided to learn some math. After learning how to multiply, divide and what is divisibility, she is now interested in solving the following problem.

You are given integer k and array a1, a2, ..., an of n integers. You are to find non-empty subsequence of array elements such that the product of its elements is divisible by k and it contains minimum possible number of elements.

Formally, you are to find a sequence of indices 1 ≤ i1 < i2 < ... < im ≤ n such that  is divisible by k while m is minimum possible among all such variants.

If there are more than one such subsequences, you should choose one among them, such that sum of its elements is minimum possible.

Mishka quickly solved this problem. Will you do so?

Input

The first line of the input contains two integers n and k (1 ≤ n ≤ 1 0001 ≤ k ≤ 1012).

The second line of the input contains n integers a1, a2, ..., an (1 ≤ ai ≤ 1012) — array elements.

Output

Print single positive integer m in the first line — the number of elements in desired sequence.

In the second line print m distinct integers — the sequence of indices of given array elements, which should be taken into the desired sequence.

If there are more than one such subsequence (e.g. subsequence of minimum possible number of elements and with minimum possible sum of elements), you can print any of them.

If there are no such subsequences, print  - 1 in the only line.

Example
input
5 60
2 4 6 5 2
output
3
4 3 1 
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 1010, M = 0, Z = 1e9 + 7, ms63 = 0x3f3f3f3f;
int n, m; LL K;
LL a[N];
LL b[N];
vector<LL>v;
#include<unordered_map>
unordered_map<LL, int>mop;
pair<int, LL>f[N][N * 10];
#define num first
#define sum second
LL gcd(LL x, LL y)
{
	return y == 0 ? x : gcd(y, x % y);
}

//处理K的所有约数,并使得每个约数对应映射到特定编号
void init()
{
	v.clear();
	int top = sqrt(K + 0.5);
	for (int i = 1; i <= top; ++i)if (K % i == 0)
	{
		v.push_back(i);
		if (K / i != i)v.push_back(K / i);
	}
	sort(v.begin(), v.end());
	m = v.size() - 1;
	mop.clear(); for (int i = 0; i <= m; ++i)mop[v[i]] = i;
	for (int i = 1; i <= n; ++i)
	{
		scanf("%lld", &a[i]);
		b[i] = gcd(a[i], K);
	}
}

void dp()
{
	if (K == 1)
	{
		puts("1");
		printf("%lld\n", min_element(a + 1, a + n + 1) - a);
		return;
	}
	for (int j = 1; j <= m; ++j)f[0][j] = { n + 1, 0 };
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 0; j <= m; ++j)
		{
			f[i][j] = f[i - 1][j];
			int pre = mop[v[j] / gcd(v[j], b[i])];
			gmin(f[i][j], MP(f[i - 1][pre].num + 1, f[i - 1][pre].sum + a[i]));
		}
	}
	if (f[n][m].num > n)puts("-1");
	else
	{
		printf("%d\n", f[n][m].num);
		for (int i = n; i; --i)
		{
			if (f[i][mop[K]] != f[i - 1][mop[K]])
			{
				printf("%d ", i);
				K /= gcd(K, b[i]);
			}
		}
		puts("");
	}
}
int main()
{
	while (~scanf("%d%lld", &n, &K))
	{
		init();
		dp();
	}
	return 0;
}
/*
【trick&&吐槽】
1,对gcd的预处理是个很好的优化
2,在输出答案的时候,不能连续使得K/=b[i],而应当使得K/=gcd(K,b[i])才能找到其合法前驱
3,如果输入的K为1,这已经是个终止状态了,但是我们依然需要至少一个数

【题意】
有n个数,我们希望选出最少数量的数,使得这些数的乘积为K的倍数

【类型】
乘除法DP map映射

【分析】
这道题是一个DP
数字个数不多,我们可以考虑逐渐枚举每个数x,逐一加进来。
然后我们当前的若干个数的乘积,有哪些可行的状态呢?
显然,之前所有数乘积的意义,是达到了K的一个约数的位置。
于是我们可以枚举当前所达到的约数j。
然后这个数对答案的贡献最大为gcd(j, x);
那么其前驱状态节点就为j/gcd(j,x)

我们用f[i][j]表示当前考虑了前i个数,当gcd(所选数的乘积,K)==j时的最优方案
那么有f[i][j]由f[i-1][j/gcd(j,x)]转移而来。

j/gcd(j,x),使得我们考虑了当前加入数的最大贡献,使得其前驱节点尽可能小。
为什么这样子呢?难道不能是前驱节点大一些反而答案更优么?
因为啊,我们的DP保证了解的单调性,即f[i][j的约数]的状态,一定是比f[i][j]要优的。

这个可以用数学归纳法证明。
其实很显然,初始f[0][0]最优;在之后,凡是f[i][j]可以获得的转移途径,f[i][j的约数]都可以通过更小的前驱转移而来。
转移当步的成本增加是相同,转移前驱的状态会更优,当前状态也就会更优。
于是,这样直到最后,就可以生成最优答案。

不过,我们不能直接用j==约数表示状态,这个是爆炸的。
所以我们要用j==约数编号表示状态,同时用map做约数->约数编号的哈希。

最后只要模拟求解的过程,在DP中找到路径方案输出即可。

【时间复杂度&&优化】
O(n * K的约数个数)

*/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值