题意:
给你一个 集合 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 ( n l o g n ) 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;
}