英文题干
Define a non-empty positive integer multi-set as "Good" iff the product of all elements in the set is the square of some positive integer 𝑥, and the "Good" set has a weight of 𝑥.
Let the set 𝑆𝑆 be defined as the multi-set of all non-empty subsets of the multi-set 𝑆.
For example, when =
,
=
Now, given a multi-set 𝑆 of size 𝑁, Chino wants to know the sum of the weights of all "Good" sets in 𝑆𝑆. Output the answer modulo
Input:
The first line contains a positive integer — the size of 𝑆.
The next line contains positive integers
— each element in the set 𝑆.
Output:
Only one line with a non-negative integer, representing the answer modulo .
中文题干
定义一个非空的正整数多重集合为“好”的条件是集合中所有元素的乘积是某个正整数 𝑥 的平方,而“好”集合的权重为 𝑥。
定义集合 𝑆𝑆 为多重集合 𝑆 的所有非空子集的多重集。
例如:当=
时,
=
现在,给定一个大小为 的多重集合
,其中的元素范围
,Chino想要知道 𝑆𝑆 中所有“好”集合的权重之和。输出答案模
思路
Inspired by 命题组,一道较为经典的质因子状压dp。
-
由于满足条件的子集合元素乘积为平方数,故子集中的每个质因数必然成对出现。可以将每个元素
中的质因子分为两类:小于
的(元素内可以出现多次)和大于
的(元素内只能出现一次)
-
首先注意到
以内质数只有
这 11 个。故对 1000 以内的数,质因子中不可能出现两个大于32的质数。可以用状态压缩(01串)进行处理(0表示某质因子在元素内出现偶数次,1表示出现奇数次)并存储状态的得分(即题中的权重)。这样就转化为01背包问题:其中背包体积为状态01串,背包价值为得分。
记dp[i][st]为前 i 个小质数选了若干个,小质数状态压缩串为st的权值和。那么
=
-
其次,对大于
的质因子,对每个大质因数分组处理。即每个包含这种质因子的元素必须同时出现偶数个。我们可以在原有 st 的基础上再加一个最高位表示这个大质因数,在分组dp后将 st 的最高位为0的保留即可。(其他的必然有奇数个质因子,没必要进行后续的其他组dp了)
AC代码
时间复杂度:O()
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int infmin = 0xc0c0c0c0;
const int infmax = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 1010;
int n;
ll prime[12] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 0}; // 枚举小质因数,预留最后一位存大因数x
map<int, vector<pair<int, int>>> mp; // 用于存储大整数供转移
ll dp[maxn][1 << 12];
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
// 构造质数01串
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
int st = 0; // 当前输入元素的小质数01串
int value = 1; // 当前元素内小质数配对后的得分
for (int j = 0; j < 11; j++)
{
while (x % prime[j] == 0)
{
x /= prime[j];
st ^= (1 << j);
if ((st & (1 << j)) == 0) // 小质因数prime[i]达成配对
value *= prime[j];
}
}
mp[x].push_back({st, value}); // 最终的x必然只剩余大质因子或1
}
// 进行背包dp
dp[0][0] = 1;
int pt = 0;
for (auto &[x, vec] : mp) // 分组背包
{
prime[11] = x;
for (auto &[st, value] : vec) // 对于每组进行01
{
pt++;
if (x != 1) // 如果x是大质因数,最高位 置1
{
st += (1 << 11);
}
for (int p = 0; p < (1 << 12); p++) // 遍历大质数x所有可能的转移来源
{
int tmpSt = st & p; // tmpSt为1的位置就是st和p都出现奇数次的质因数(可以元素间配对)
int tmpValue = value;
for (int j = 0; j <= 11; j++)
{
if (tmpSt & (1 << j)) // 寻找tmpSt二进制中为1的位
{
tmpValue *= prime[j]; // 原本得分*转移得分
}
}
// st^p 就是转移后的状态
dp[pt][st ^ p] = (dp[pt - 1][st ^ p] + dp[pt - 1][p] * tmpValue) % mod;
}
}
// 只保留最终转移结果最高位为0的(大质数x能完全配对),其余全部重置为0
for (int i = (1 << 11); i < (1 << 12); i++)
{
dp[pt][i] = 0;
}
}
cout << (dp[pt][0] - 1 + mod) % mod;
return 0;
}