https://ac.nowcoder.com/acm/contest/5673/A
题意
有n个篮球运动员,m个球迷。
一个球迷可能是多个球员的粉丝
选择最少的球员进全明星赛,使得所有球迷都愿意观看(至少一个球迷想看的球员入选)。
想看的球员标准如下
有q个粉丝关系的修改,修改完回答询问。(1 le n, m,q le 2*10^5)
题解
用map处理出每个粉丝关系存在的时间段,把这个粉丝关系作为一条边插入到这个时间段中,用线段树维护这个时间段,在进入这个时间段时,把只属于这个时间段的边加入关系。
由于只要一个粉丝和另一个粉丝喜欢的球员有相同的,那么两个粉丝喜欢的球员会进行合并,所以我们要求的就是n个球员的联通分量个数,其中不包含鼓励球员的点
我们使用可撤销并查集维护这个数量,具体做法是
首先肯定不能路径压缩,我们选择直接按并查集大小合并,小的并查集向大的合并。
我们一开始把答案设为m,即粉丝的数量
设x为球员,y为球迷
x,y加边的时候,如果本来不连通,且y原来有边,那么ans--
x,y删边的时候,如果删完他们不连通,且y删完还有变,那么ans++
把球迷的sz初始设为1,球员的sz初始设为0,那么直接用联通块的sz判断是否还有球员向球迷的边即可。
但要注意,每个球迷都要有喜欢的球员,要维护并判断一下
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct READ {
inline char read() {
#ifdef _WIN32
return getchar();
#endif
static const int IN_LEN = 1 << 18 | 1;
static char buf[IN_LEN], *s, *t;
return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
}
template <typename _Tp> inline READ & operator >> (_Tp&x) {
static char c11, boo;
for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
if(c11 == -1) return *this;
boo |= c11 == '-';
}
for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
boo && (x = -x);
return *this;
}
} in;
const int N = 5e5 + 50;
#define pii pair<int, int>
map<pii, int> mp;
pii edge[N<<1];
int vis[N<<1];
int link[N];
int ans, num;
int f[N], sz[N];
stack<pii> s;
int find(int x) { return x == f[x] ? x : find(f[x]); }
int merge(int x, int y) {
x = find(x); y = find(y);
if (x == y) return 0;
if (sz[x] < sz[y]) swap(x, y);
sz[x] += sz[y];
if (sz[x] >= 2 && sz[y]) ans--;
f[y] = x;
s.push(pii(x, y)); return 1;
}
void del(int len) {
while (!s.empty() && (len--)) {
int x = s.top().first, y = s.top().second;
s.pop();
if (sz[x] < sz[y]) swap(x, y);
if (sz[x] >= 2 && sz[y]) ans++;
sz[x] -= sz[y];
f[y] = y;
}
}
#define ls (o<<1)
#define rs (o<<1|1)
#define mid ((l+r)>>1)
vector<int> t[N<<2];
void update(int o, int l, int r, int ql, int qr, int id) {
if (ql > qr) return;
if (ql <= l && r <= qr) { t[o].push_back(id); return; }
if (ql <= mid) update(ls, l, mid, ql, qr, id);
if (qr > mid) update(rs, mid + 1, r, ql, qr, id);
}
void query(int o, int l, int r) {
int len = 0;
for (int id : t[o]) {
link[edge[id].second]++;
if (link[edge[id].second] == 1) num--;
len += merge(edge[id].first, edge[id].second);
}
if (l == r) {
if (num) puts("-1");
else printf("%d
", ans);
for (int id : t[o]) {
link[edge[id].second]--;
if (link[edge[id].second] == 0) num++;
}
del(len);
return;
}
query(ls, l, mid); query(rs, mid + 1, r);
for (int id : t[o]) {
link[edge[id].second]--;
if (link[edge[id].second] == 0) num++;
}
del(len);
}
int main() {
int n, m, q; in >> n >> m >> q;
int cnt = 0;
num = ans = m;
for (int i = 1; i <= n + m; i++) f[i] = i;
for (int i = n + 1; i <= n + m; i++) sz[i] = 1;
for (int i = 1; i <= n; i++) {
int k; in >> k;
for (int j = 1; j <= k; j++) {
int x; in >> x; x += n;
mp[pii(i, x)] = ++cnt;
edge[cnt] = pii(i, x);
vis[cnt] = 1;
}
}
for (int i = 1; i <= q; i++) {
int u, v; in >> v >> u; v += n;
if (!mp.count(pii(u, v))) {
mp[pii(u, v)] = ++cnt;
edge[cnt] = pii(u, v);
}
int id = mp[pii(u, v)];
if (!vis[id]) vis[id] = i;
else {
update(1, 1, q, vis[id], i - 1, id);
vis[id] = 0;
}
}
for (int i = 1; i <= cnt; i++) {
if (vis[i]) update(1, 1, q, vis[i], q, i);
}
query(1, 1, q);
return 0;
}