2020牛客暑期多校训练营(第八场)A.All-Star Game (时间分治线段树)

时间限制:C/C++ 5秒,其他语言10秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

In Apollo's country, basketball is the most popular sport. There are n basketball players and m basketball fans. Bastketball players are numbered from 1 to n. Basketball fans are numbered from 1 to m.
A basketball fan can be a fan of multiple players.
Basketball fan i is like to watch the game of player j if one of the following conditions is met:

  • Basketball fan i is a fan of basketball player j.
  • There is a fan i' and a player j', both fan i and fan i' are like to watch the game of player j', and fan i' is like to watch the game of player j.

The All-Star Game is an annual exhibition basketball game. You need to select some players into the All-Star Game. A fan will watch the All-Star Game if he is like to watch the game of one selected player (i.e. at least one of the players that the fan is like to watch was selected). You need to decide the minimum number of players need to be selected into All-Star Game so that all basketball fans will watch the game.
Initially, some basketball players will have some fans. There are q changes to the relationship between basketball players and basketball fans.
After each of q changes, print one integer --- the minimum number of players which you have to select to make all basketball fans will watch the game. Print "-1" if it is impossible.

输入描述:

The first line contains three integers n, m and q (1≤n,m,q≤2×1051 \le n, m, q \le 2 \times 10^51≤n,m,q≤2×105) - the number of basketball players, the number of basketball fans and the number of changes respectively.

Then n lines follow. The i-th line starts with one integer kik_iki​ (0≤ki≤m0 \le k_i \le m0≤ki​≤m) - the initial number of fans of basketball player i, then contains kik_iki​ different integers - present the fans of player i.

∑i=1nki≤5×105\sum_{i=1}^n k_i \le 5 \times 10^5∑i=1n​ki​≤5×105.

Then q lines follow. The i-th line contains two integers x and y (1≤x≤m1 \le x \le m1≤x≤m, 1≤y≤n1 \le y \le n1≤y≤n). If fan x is a fan of basketball player y, then fan x will be not a fan of player y, otherwise, fan x will be a fan of player y.

输出描述:

After each change print one integer --- the minimum number of players need to be selected into All-Star Game so that all basketball fans will watch the game. Pritn "-1" if it is impossible.

示例1

输入

复制

4 4 6

2 1 2

0

2 2 3

1 4

4 2

2 3

2 1

2 2

4 2

4 1

4 4 6
2 1 2
0
2 2 3
1 4
4 2
2 3
2 1
2 2
4 2
4 1

输出

2 3 -1 3 4 3

2
3
-1
3
4
3

题意不好解释就当你们已经知道了啊^_^

解法:

首先这是一个动态维护连通块的问题,并且题目没有强制在线,那么就可以用时间分治线段树(以下简称SJFZ)来解决。

SJFZ简单地理解,就是将询问的编号当成下标,然后标记边u-v这条边在哪些时间段出现,出现的时候合并,子树递归完了就撤销。

分析一下可以知道,每次的结果就是包含fans的连通块数量,假如某个连通块只有fans,那么输出-1,因为没有player能够使他去看球赛。所以我们只要在写SJFZ的同时,维护包含fans的连通块数量,和是否有fans单独成一个连通块来判断答案是不是-1。

fans连通块数量:最初可以对每个fans进行标记,hav[x] = true表示该连通块包含fans,假如x合并到y,那么此时hav[y] |= hav[x],撤销的时候再更新回去即可。

是否有fans单独成连通块:在fans连第一条边和删除最后一条边的时候做一下标记就好。

如果你已经学会SJFZ的话,只看合并和撤销的函数应该就会了,其他都是模板。

Accepted code

#pragma GCC optimize(3)
#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;

#define sc scanf
#define Min(x, y) x = min(x, y)
#define Max(x, y) x = max(x, y)
#define ALL(x) (x).begin(),(x).end()
#define SZ(x) ((int)(x).size())
#define pir pair <int, int>
#define MK(x, y) make_pair(x, y)
#define MEM(x, b) memset(x, b, sizeof(x))
#define MPY(x, b) memcpy(x, b, sizeof(x))
#define lowbit(x) ((x) & -(x))
#define P2(x) ((x) * (x))

typedef long long ll;
const int Mod = 1e9 + 7;
const int N = 2e5 + 100;
const int M = 7e5 + 100;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
inline ll dpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t) % Mod; b >>= 1; t = (t*t) % Mod; }return r; }
inline ll fpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t); b >>= 1; t = (t*t); }return r; }

struct node
{
	int u, v, id;
};
struct Node
{
	int x, y, l, r;
}st[M];
vector <pir> ver[M * 4];
vector <node> ask[N];
int fz[N * 2], sz[N * 2], n, m, q;
map <pir, int> vis;
map <int, int> fan;
int ans[N], fans, cut, top;
bool ok[N * 2];   // 是否有fans

void Init() {
	for (int i = 1; i <= n + m; i++) {
		fz[i] = i, sz[i] = 1;
		if (i > n)
			ok[i] = true;
	}
	cut = fans = m;  // 连通块,单独粉丝
}
int Find(int x) {
	while (x != fz[x])
		x = fz[x];
	return x;
}
void Merge(int x, int y) {
	x = Find(x), y = Find(y);
	if (x == y) {
		st[++top] = { 0, 0 };
		return;
	}
	if (sz[x] > sz[y])
		swap(x, y);
	fz[x] = y, sz[y] += sz[x];
	st[++top] = { x, y, ok[x], ok[y] }; // 保留当前状态
	if (ok[x] && ok[y]) // fans合并
		cut--;
	ok[y] |= ok[x];

	if (x > n && sz[x] == 1) // 单独的fans少了
		fans--;
}
void Cancle() {
	int x = st[top].x, y = st[top].y;
	int ok1 = st[top].l, ok2 = st[top].r;
	top--;
	if (!x || !y)
		return;
	fz[x] = x, sz[y] -= sz[x];
	ok[x] = ok1, ok[y] = ok2;
	if (ok1 && ok2)  // 都有fans断开
		cut++;
	if (x > n && sz[x] == 1)
		fans++;
}
#define ls (o << 1)
#define rs (ls | 1)
void Update(int o, int L, int R, int l, int r, pir val) {
	if (L >= l && R <= r)
		ver[o].push_back(val);
	else {
		int mid = (L + R) >> 1;
		if (mid >= l)
			Update(ls, L, mid, l, r, val);
		if (mid < r)
			Update(rs, mid + 1, R, l, r, val);
	}
}
void Ask(int o, int L, int R) {
	for (auto it : ver[o])
		Merge(it.first, it.second);
	if (L == R) {
		for (auto it : ask[L]) {
			int u = it.u, v = it.v;
			ans[it.id] = fans ? -1 : cut;
		}
	}
	else {
		int mid = (L + R) >> 1;
		Ask(ls, L, mid), Ask(rs, mid + 1, R);
	}
	int sz = SZ(ver[o]);
	while (sz--)
		Cancle();
}

int main()
{
#ifdef OlaMins
	freopen("D:/input.txt", "r", stdin);
	//freopen("D:/output.txt", "w", stdout);
#endif
	
	cin >> n >> m >> q;
	Init();
	for (int i = 1; i <= n; i++) {
		int k, u;
		sc("%d", &k);
		while (k--) {
			sc("%d", &u);  // fans
			u += n;
			vis[{u, i}] = 1;
		}
	}
	for (int i = 2; i <= q + 1; i++) {
		int x, y;
		sc("%d %d", &x, &y); // fans, player
		x += n; 

		if (vis.count({ x, y })) {  // 有边,此时要删除
			int lst = vis[{x, y}];
			vis.erase({ x, y });
			Update(1, 1, q + 1, lst, i - 1, { x, y });
		}
		else  // 无边,标记时间
			vis[{x, y}] = i;
		ask[i].push_back({ x, y, i });
	}
	for (auto it : vis)
		Update(1, 1, q + 1, it.second, q + 1, { it.first.first, it.first.second });
	Ask(1, 1, q + 1);
	for (int i = 2; i <= q + 1; i++)
		printf("%d\n", ans[i]);
	return 0; // 改数组大小!!!用pair改宏定义!!!
}

猜你喜欢

转载自blog.csdn.net/weixin_43851525/article/details/110847011