Codeforces Round #365 (Div. 2)Mishka and Divisors 题解。 DP

题目大意:


给一个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;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值