【CF1103D】 Professional layer 题解 (状压 dp)
作者:互联网
状压 dp。
Solution
发现有些题解对一些细节部分没有说明,导致某些实现部分没得到证明。
约定:记题面中的因数上限 \(k\) 为 \(limit\)。
1
记所有数的最大公因数为 \(res\)。
稍加思考可以发现,如果我想使用一个数去消除掉 \(res\) 的一个质因数 \(x\),必须要除掉这个数内所有的 \(x\),才能使得之后所有数的最大公因数不含 \(x\)。
故,若想使用一个数 \(x = p_1^{k_1} \times p_2^{k_2} \times \cdots \times p_t^{k_t}\times r\) 消去 \(res\) 中 \(p_1,\ p_2,\ \cdots,\ p_t\) 这 \(t\) 个质因数(其中 \(x\) 的 \(r\) 部分指的是 \(x\) 中除去这些素数之外的余下部分),必须要使 \(x\) 除去 \(p_1^{k_1} \times p_2^{k_2} \times \cdots \times p_t^{k_t}\),而这步操作可行当且仅当 \(p_1^{k_1} \times p_2^{k_2} \times \cdots \times p_t^{k_t} \leq limit\)。所以我们明白:要想使用 \(x\) 消去 \(res\) 中的一个质因数 \(p\),与 \(p\) 在 \(res\) 中的次数无关,而是与 \(p\) 在 \(x\) 中的次数有关。
显然,我们的目标就是将 \(res\) 中的每个质因数消掉。
其实这里已然可以想到使用状压 dp 来处理了,但是直接上手复杂度显然过不了,所以需要根据限制考虑优化。
2
其中,根据数据范围可知 \(res\) 的不同质因数个数上限为 \(11\)。同时,每一个数 \(x\) 与 \(res\) 有关联的只有 \(x\) 内 同时在 \(res\) 中出现的质因数 的幂。而除了这些质因数外的因数,对 \(x\) 没有贡献,故我们可以考虑将每个 \(x\) 加工,把这些没有贡献的数全部从 \(x\) 中除去。得到的这些新的数,通过暴力打表,得知其不同种类数的上限约为 \(12000\)。
约定:下文提及的“数”都是将每个 \(a_i\) 重新处理(即剔除掉无贡献部分)后得到的新序列中的数。
上述两者的上限都较小,对于:
-
”不同质因数 \(m\) 上限为 \(11\)”:
不难想到使用状压 dp,定义 \(f_{i,st}\),表示使用了 \(i\) 个数,把 \(st\) 中为 \(1\) 的每一位对应的 \(res\) 中的一个质因数 除去了。其中,\(i\) 的上限为 \(m\),因为最劣情况就是使用一个数消去一个质因数,即最多使用 \(m\) 个数。 -
“新数种类数 \(S\) 上限为 \(12000\)”:
我们可以将 \(a\) 序列中的数重新分门别类,即将处理过后相同的数放到一起处理(因为它们之间的差别只剩下代价的不同)。具体地,开一个
map
,一个数值对应一个vector
,而某个数值 \(val\) 对应的vector
内存放 那些处理之后的值为 \(val\) 的数,操作一次的代价。这里在实现上还有一个小优化:在将每个值对应的
vector
内部从小到大排序之后,我们最多显然是使用前 \(m\) 小的那些代价,所以第 \(m\) 个代价之后的代价于我们来说都没有意义了,故可以直接resize(m)
掉,这样之后转移状态也不用再考虑了。按照处理后的值分类之后,就可以分别考虑这些值对答案的贡献,即转移 dp 状态。首先预处理出对于每个状态 \(st\)(即选取出 \(res\) 的某些质因数),若想使用 \(val\) 这个值除去 \(res\) 中的这些质因数,需要除去多少。
然后就是转移状态了,这里按照从小到大的顺序遍历每一个代价,考虑这个数对应的每个代价对答案的贡献。(一旦发现遍历到某个代价之后没法更新任何状态,立即
break
掉。)这里转移状态的方式比较特别:枚举状态 \(st\),对于每个 \(st\),找到 \(2^m-1\) 这个状态与 \(st\) 的补集 \(C\),然后遍历这个补集中的每一个非空子集 \(k\),那么在需要除去的数(即上面预处理的部分)小于 \(limit\) 的时候,即有:\(f_{i + 1,j | k} = \min(f_{i + 1,j | k},\ f_{i,j} + cst)\)(其中 \(cst\) 指的是当前遍历到的代价。)
于是最后的答案也就水到渠成了:rep(i, 1, m) ans = min(ans, f[i][(1 << m) - 1] * i);
。
记新数的种类数为 \(S\),那么最终复杂度就是 \(\mathcal{\text{O}}(Sm^23^m)\),跑不满,能过。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i, a, b) for(register int i = a; i <= b; ++i)
#define per(i, a, b) for(register int i = a; i >= b; --i)
const int maxn = 1e6 + 5;
int n, lmt, m, res, ans = 1e15;
vector <int> fac;
struct node{
int a, c;
}p[maxn];
map <int, vector<int> > mp;
int f[12][1 << 12];
inline int gcd(int x, int y){
return !y ? x : gcd(y, x % y);
}
inline void dp(){
memset(f, 2, sizeof f), f[0][0] = 0;
for(auto val : mp){ int x = val.first;/*值为x的数*/ vector <int> mul;
sort(val.second.begin(), val.second.end());
if(val.second.size() > m) val.second.resize(m);
rep(i, 0, (1 << m) - 1){ int tot = 1, tmp = x;
rep(j, 0, m - 1) if(i & (1 << j))
while(!(tmp % fac[j])) tot *= fac[j], tmp /= fac[j];
mul.push_back(tot);//tot:要想消掉这一种搭配中所有素数,x 需要除去多少
//这里是按顺序存放的,之后调用的时候直接用状态调用即可
} for(auto cst : val.second){ bool flg = 0;//从小到大每一种代价
per(i, m - 1, 0) rep(j, 0, (1 << m) - 1)
if(f[i][j] <= 1e13){
int C = (1 << m) - 1 - j;//该状态的补集
for(int k = C; k; k = (k - 1) & C) if(mul[k] <= lmt)
if(f[i + 1][j | k] > f[i][j] + cst) f[i + 1][j | k] = f[i][j] + cst, flg = 1;
} if(!flg) break;
}
}
}
signed main(){
scanf("%lld%lld", &n, &lmt);
rep(i, 1, n) scanf("%lld", &p[i].a),
res = gcd(res, p[i].a);
rep(i, 1, n) scanf("%lld", &p[i].c);
if(!(res - 1)) return printf("0"), 0;
rep(i, 2, 1e6) if(i <= res and !(res % i)){
fac.push_back(i);
while(!(res % i)) res /= i;
} else if(i > res) break;
if(res - 1) fac.push_back(res);
m = fac.size();
rep(i, 1, n){ int nw = 1;
rep(j, 0, m - 1)
while(!(p[i].a % fac[j])) nw *= fac[j], p[i].a /= fac[j];
mp[nw].push_back(p[i].c);//把每个输入的数更新,即剔除掉没用的部分,其最终形式为 nw
} dp();
rep(i, 1, m) ans = min(ans, f[i][(1 << m) - 1] * i);
if(ans > 1e13) printf("-1"); else printf("%lld", ans);
return 0;
}
感谢阅读。
标签:layer,val,CF1103D,题解,rep,times,int,res,质因数 来源: https://www.cnblogs.com/gsn531/p/16579003.html