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;
}