题意
给定一个 位 01 串 。
给定 个集合 ,任意三个集合满足 。
一次操作被定义为选择一个集合 ,对集合中的每个元素 执行 。保证能通过若干次操作使得 变为全 1 串。
定义 为使 的前 个位置都变成 1 的最小操作次数,我们需要求出 。
数据范围:。
题解
首先注意到题目竟然没有给 的数据范围,这让我们感到疑惑这题出错了,于是我们考虑任意三个集合交集为空这个莫名其妙的条件。稍微思考一下即可发现,它的意思是对 中任意一个位置,它最多只受两个集合的控制。所以显然 。
知道了这件事情以后,我们就需要考虑从每一位的角度看待问题了:
先设 表示集合 的状态(选,不选,是 01 变量)
考虑 :
- 若 不受集合约束,不管(因为保证了答案存在)
- 若 只受一个集合 约束,则 。
- 若 受两个集合 约束,则 ,即 时 , 时 。
这个给定等于和不等于的条件很容易让我们想到 关押罪犯 那套模型啊。。就是并查集拆点,我们将每个集合 的选项分为两个点 分别表示两种选择,则我们的操作是:强制选点,连接 或是连接 。
然后接下来我就不太会做了所以我考场没做出来。。我们要维护的答案是一个有些连通块取 min 有些按照强制选点来取然后还要求和的玩意,但事实上我们有一个巧妙的做法(来自 Mohammad_Yasser),我们建一个权为 的点,假设我们 强制选上,只需要将 连向这个新点。这样我们就没有了强制选点的操作,接下来就好做了。
时间复杂度 (我没按秩合并,按秩合并是 的)
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 600005, INF = 1000000000;
int N, K, a[MAXN]; vector<int> V[MAXN]; char S[MAXN];
int fa[MAXN], val[MAXN], ans;
int get(int x) {
if (fa[x] != x) fa[x] = get(fa[x]);
return fa[x];
}
void merge(int x, int y) {
x = get(x), y = get(y);
if (x != y) fa[x] = y, val[y] += val[x];
}
inline int va(int x) {
return min(val[get(x)], val[get(x + K)]);
}
int main() {
scanf("%d%d%s", &N, &K, S + 1); int i, j;
for (i = 1; i <= K; ++i) {
int c, x; scanf("%d", &c);
while (c--) scanf("%d", &x), V[x].push_back(i);
}
for (i = 1; i <= K * 2; ++i) fa[i] = i, val[i] = i > K;
fa[K * 2 + 1] = K * 2 + 1, val[K * 2 + 1] = INF;
for (i = 1; i <= N; ++i) {
if (V[i].size() == 1) {
int x = V[i][0] + (S[i] == '1') * K;
ans -= va(V[i][0]), merge(x, K * 2 + 1), ans += va(V[i][0]);
} else if (V[i].size() == 2) {
int x = V[i][0], y = V[i][1];
if (S[i] == '0') {
if (get(x) != get(y + K))
ans -= va(x) + va(y), merge(x, y + K), merge(x + K, y), ans += va(x);
}
else {
if (get(x) != get(y))
ans -= va(x) + va(y), merge(x, y), merge(x + K, y + K), ans += va(x);
}
}
printf("%d\n", ans);
}
return 0;
}
来源:CSDN
作者:wyy603
链接:https://blog.csdn.net/wyy603/article/details/104156509