HDU-5656-CA Loves GCD

HDU-5656-CA Loves GCD

传送门

这道题是gcd和dp压缩。
这次是gcd和dp的结合。

最近写题有点力不从心了。。效率低下: )
今天写一道组合的题目卡了,就换了到题emmm。

题目大意:给出n个数,问至少选择其中的一个数,求他们的gcd之和等于多少,答案模1e8+7;

我们用num[i]表示i出现的次数,也就是包含i有多少种相同的情况
最主要的是num[]上面的压缩。
dp[i]表示包含i的gcd之和是多少。
然后ans每次在dp[]基础上面进行维护就可以了。
我们预处理一下g[][];
g[i][j]代表i和j的gcd是多少。

下面主要说一下压缩思想:
我们先把所给的数字排序一下。
然后循环是按照数组下标进行的,也就是说我们选数的时候从小到大,从少到多选取的。可以保证每种情况考虑到。
dp[i]初始化为a[i];
代表只选取a[i]一个数的情况,那么一个数的gcd就是自己本身。
num[]初始化为0

举个例子:
三个数:
3
1 2 3
数组存储从1开始
第一次循环操作a[]中的每一个数
第二次循环选取对应的小于等于a[]的数字
第一重循环每次num[a[i]]应该++;(因为需要更新到ans中,所以代表a[i]出现的次数++)

对于数字1:
ans += 1(选取一个数字1时); num[1]++;
对于数字2:
ans += 2(选取一个数字2时); sum[gcd(1, 2)] += num[j];(这里的j指的是第二重循环操作时的对应的数字)
因为1和2的最大公约数时1嘛,我们这里直接把num[1]更新为2了(代表出现了2次,包含两种情况,分别是1和1,2)
这样可以在下次我们操作数字3的时候可以一次得到前面压缩过的答案。
如下一次对于数字3:
ans += gcd(1, 3) * num[1];
这个地方为什么要乘num[1]?
因为操作1,3的时候:我们直接把1, 2, 3和1, 3的两种情况当成一种情况压缩了,只需要算一次。(原因是我们上面更新num[gcd(1, 2)] += num[j]时压缩的把1和1,2两种情况结合了)
所以这里的gcd(1, 2, 3) = gcd(1, 3),全部按照gcd(1, 3)这种情况处理了。
于是为了我们后面继续压缩num[]需要随时更新。
所以我们得到式子num[gcd(a[i], j)] += num[j];
代表原本gcd(a[i], j)这个数出现了几次(包含了几种情况),j这个数出现了几次(包含了几种情况),全部累加到num[gcd(a[i], j)]中。
代码部分是把ans的更新放在后面了,取而代之的是dp[](其实dp[]要不要都无所谓)加上dp[]结构化一些。

最后记得每次取模就行。

代码部分:

#include <bits/stdc++.h>
#define mst(a, n) memset(a, n, sizeof(a))
using namespace std;
typedef long long ll;
const int N = 1e3 + 10;
const ll mod = 100000007;

int t;
int n;
int a[N];
ll dp[N];
ll num[N];
ll g[N][N];

int main()
{
	cin >> t;
	for (int i = 1; i <= 1e3; i++)
	{
		for (int j = 1; j <= 1e3; j++)
		{
			g[i][j] = __gcd(i, j);
		}
	}
	while (t--)
	{
		ll ans = 0;
		mst(num, 0);
		cin >> n;
		for (int i = 1; i <= n; i++)
		{
			scanf ("%d", &a[i]);
		}
		sort(a + 1, a + n + 1);
		for (int i = 1; i <= n; i++)
		{
			dp[i] = 1ll * a[i];
			for (int j = 1; j <= a[i]; j++)
			{
				if (num[j])
				{
					dp[i] = (dp[i] + num[j] * g[a[i]][j]) % mod;
					num[g[a[i]][j]] = (num[g[a[i]][j]] + num[j]) % mod;
				}
			}
			num[a[i]] = (num[a[i]] + 1) % mod;
			ans = (ans + dp[i]) % mod;
		}
		cout << ans << endl;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

娃娃酱斯密酱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值