


题意:
给你一个 集合 S 这个 集合里的元素是 1 ~ n,每次操作可以 选择一个数 k,并删除 集合 s 中 最小的 k 的倍数,举个例子,假设一个集合为 {2, 3},里面的元素 均为 1 的倍数,删除的时候只能 先删 2 后删除 3,不能跳过 2 再删除 3。
给定 集合 T 为 集合 S 的子集,问能不能通过 上面的 删除操作 从集合 S 转化成集合 T,集合的形式以 二进制的形式 给出。
对于题目给定的样例,0010,表示 S 集合为 {1, 2, 3, 4},T 集合为 {3},正确的做法应当是:先选择 k = 1,先后删掉 1、2,之后选择 k = 2,删掉 4,总代价为 4。
不符合题意的错误做法:选择 k = 1,直接删除 1、2、4,总代价为 3。因为 不能跳过 3 直接删除 4(不能跳着删除 数字,每次只能 删掉最小的 k 的倍数)
思路:
为了让花费最小,应该选择一个 可以删除 x 的 k,且 k 最小。
具体实现如下:
从 i = 1 开始枚举,不断往后删除 i 的倍数 j,如果 不能删除(即 子集 T 包含了 j,s[j] == '1'),就 直接 break。
用一个 数组 cost 记录一下 每个数字的花费,如果这个数字 在子集 T 中存在,枚举 i 的倍数 j,如果 倍数 j 需要删除(s[j] == '0'),则 记录一下代价 cost[j],
注意:由于 cost 要取 min,所以 如果 cost[j] 为 0 表示:j 这个数没有删过,就直接 cost[j] = i 即可,如果 不为 0,表示:j 被之前的某个数删除过,由于我们需要找到 最小 k,且 i 从小到大枚举,因此 只需要在 cost 第一次为 0 的时候记录一下即可。
最后 将所有 cost 相加 即为答案。
时间复杂度:
标准的 调和级数 O(nlogn)O(nlogn)O(nlogn)
代码:
#include <bits/stdc++.h>
using namespace std;
//#define map unordered_map
#define int long long
const int N = 1e6 + 10;
int n;
string s;
int cost[N];
void solve()
{
cin >> n;
cin >> s; s = " " + s;
fill(cost, cost + n + 1, 0);
for (int i = 1; i <= n; ++i)
{
if(s[i] == '0')
{
for (int j = i; j <= n; j += i)
{
if(s[j] == '1') break;
if (!cost[j]) {
cost[j] = i;
}
}
}
}
int ans = 0;
for(int i = 1; i <= n; ++i) if(s[i] == '0') ans += cost[i];
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int _ = 1; cin >> _;
while (_--)
{
solve();
}
return 0;
}

1343

被折叠的 条评论
为什么被折叠?



