P9170 [省选联考 2023] 填数游戏 题解

先考虑怎么判断 1-1

对于 Ti=2|T_i|=2,我们考虑连无向边 Ti,1Ti,2T_{i,1} \leftrightarrow T_{i,2}。对于 Ti=1|T_i|=1,连自环 Ti,1Ti,1T_{i,1} \leftrightarrow T_{i,1}

考虑每天边 Bob 会怎样?显然是选择其中一个端点,或者可以说,对无向边定向,从没选的指向选了的。

对于每个连通块,如果存在边数大于点数,那么无论怎么选都有重复,即为 1-1

这样我们已经判断了是否可以凑出每个都不同。那么接着就已经离正解不远了。

注意到当边数大于点数,输出 1-1。于是只需要考虑边数 \leq 点数。然而这是连通块,所以要么是树,要么是基环树。

我们现在将 SiS_i 纳入进来。显然 Alice 最优策略下一定会选 SiTiS_i \cap T_i 的一些点,如果两个集合没有交集,那么无论怎么选都没有贡献。交集为 11 时 Alice 选的确定,现在只需要考虑交集等于 22,即 Si=TiS_i = T_i 的选法。

先考虑基环树。比较显然我们的边定向方案是一棵外向基环树,不在环上的边只能往外定向,这些贡献可以直接处理。环上只有两种,都选左端点或者右端点。环上的边的 Si=TiS_i =T_i 可以容易处理,假设全都选左边,只计算 SiTiS_i \neq T_i 的贡献是 c1c_1,全都选右边,只计算 SiTiS_i \neq T_i 的贡献是 c2c_2Si=TiS_i = T_i 的环上边数量为 dd。则贡献为 maxi=0dmin{c1+i,c2+di}\max \limits_{i=0}^d \min\{c_1+i,c_2+d-i\},计算并累加贡献即可。

考虑另一部分,即树。对于 Bob 来说,选法有树的点数种,每种都是确定一个根,然后连。直接做是 O(n2)O(n^2) 的,不妨从 Alice 的角度考虑。考虑对于 Si=TiS_i = T_i 的边,Alice 可以选择一个方向,使得定向如果定成这个方向就有代价。随便找个根 DFS,对于 Si=TiS_i = T_i 的一条边 (u,v)(u,v),不妨设 uuvv 的父亲。如果选的代价是 uvu \rightarrow v 的,那么以 vv 子树外所有点为根时,Bob 都需要花贡献。反之,vuv \rightarrow u,则是以 vv 为根的子树有贡献。

我们称 uvu \rightarrow v 的边为后向边,vuv \rightarrow u 的边为前向边。感性理解可以发现,前向边一定同时在一条链上且一定是连续的。可以通过反证法。读者自证不难。于是我们可以 DFS 的时候线段树维护之。复杂度 O(nlogn)O(n \log n)

总体复杂度线性对数,22 秒可以接受。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
using namespace std;

constexpr int N = 2e6 + 5;
int T, n, m;

int e[N * 2], h[N * 2], ne[N * 2], gg[N * 2], idx;
int s[N], ss[N][3], t[N], tt[N][3];
void add(int u, int v, int id)
{
	e[idx] = v, gg[idx] = id, ne[idx] = h[u], h[u] = idx++;
}
int cicp;

bool vis[N];
int cv, ce;
int fa[N];
bool cir;
bool both[N];
int dep[N];
vector<int> vv;

void dfs(int u, int from)
{
	vv.emplace_back(u);
	vis[u] = 1;
	cv++;
	int gg = 0;
	for (int i = h[u]; ~i; i = ne[i])
	{
		int j = e[i];
		if (i == (from ^ 1)) continue;
		if (!vis[j] || dep[j] <= dep[u]) ce++;
		if (j == u)
		{
			gg++;
		}
		if (vis[j])
		{
			cicp = j;
		}
		if (!vis[j])
		{
			dep[j] = dep[u] + 1;
			fa[j] = u;
			dfs(j, i);
		}
	}
	if (gg) ce -= gg / 2;
}

class SegmentTree
{
public:
	struct Node
	{
		int l, r, minn, tag;
	}tr[N << 2];
	void pushup(int u)
	{
		tr[u].minn = min(tr[u << 1].minn, tr[u << 1 | 1].minn);
	}
	void build(int u, int l, int r)
	{
		tr[u] = { l, r, 0, 0 };
		if (l == r) return;
		int mid = l + r >> 1;
		build(u << 1, l, mid);
		build(u << 1 | 1, mid + 1, r);
	}
	void pushdown(int u)
	{
		if (tr[u].tag)
		{
			tr[u << 1].minn += tr[u].tag;
			tr[u << 1].tag += tr[u].tag;
			tr[u << 1 | 1].minn += tr[u].tag;
			tr[u << 1 | 1].tag += tr[u].tag;
			tr[u].tag = 0;
		}
	}
	void update(int u, int l, int r, int v)
	{
		//if (l > m) exit(-1);
		if (tr[u].l >= l and tr[u].r <= r)
		{
			tr[u].tag += v;
			tr[u].minn += v;
			return;
		}
		pushdown(u);
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) update(u << 1, l, r, v);
		if (r > mid) update(u << 1 | 1, l, r, v);
		pushup(u);
	}
	int query(int u, int l, int r)
	{
		if (tr[u].l >= l and tr[u].r <= r) return tr[u].minn;
		pushdown(u);
		int mid = tr[u].l + tr[u].r >> 1, res = (int)1e9;
		if (l <= mid) res = query(u << 1, l, r);
		if (r > mid) res = min(res, query(u << 1 | 1, l, r));
		return res;
	}
}sgt;

bool cmp(int x)
{
	return (ss[x][1] == tt[x][1] && ss[x][2] == tt[x][2]) || (ss[x][1] == tt[x][2] && ss[x][2] == tt[x][1]);
}
int tr_idx[N], tridx, trsz[N];
int res = 0, ans;
bool inc[N];
bool flag[N];

void pre_dfs(int u, int fa)
{
	tr_idx[u] = ++tridx;
	trsz[u] = 1;
	for (int i = h[u]; ~i; i = ne[i])
	{
		int j = e[i];
		if (j == fa) continue;
		pre_dfs(j, u);
		trsz[u] += trsz[j];
		int id = gg[i];
		if (s[id] == 2 && t[id] == 2 && cmp(id))
		{
			both[i] = 1;
			sgt.update(1, 1, m, 1);
			sgt.update(1, tr_idx[j], tr_idx[j] + trsz[j] - 1, -1);
		}
		else if (s[id] == 2)
		{
			if (ss[id][1] == j || ss[id][2] == j)
			{
				sgt.update(1, 1, m, 1);
				sgt.update(1, tr_idx[j], tr_idx[j] + trsz[j] - 1, -1);
			}
			else if (ss[id][1] == u || ss[id][2] == u)
			{
				sgt.update(1, tr_idx[j], tr_idx[j] + trsz[j] - 1, 1);
			}
		}
		else
		{
			if (ss[id][1] == j)
			{
				sgt.update(1, 1, m, 1);
				sgt.update(1, tr_idx[j], tr_idx[j] + trsz[j] - 1, -1);
			}
			else if (ss[id][1] == u)
			{
				sgt.update(1, tr_idx[j], tr_idx[j] + trsz[j] - 1, 1);
			}
		}
	}
}

int nowl, nowr;

void dfs2(int u, int fa)
{
	res = max(res, sgt.query(1, nowl, nowr));
	for (int i = h[u]; ~i; i = ne[i])
	{
		int j = e[i];
		if (j == fa) continue;
		if (both[i])
		{
			//cout << "!!!: " << u << " " << j << "\n";
			sgt.update(1, 1, m, -1);
			sgt.update(1, tr_idx[j], tr_idx[j] + trsz[j] - 1, 2);
		}
		dfs2(j, u);
		if (both[i])
		{
			sgt.update(1, 1, m, 1);
			sgt.update(1, tr_idx[j], tr_idx[j] + trsz[j] - 1, -2);
		}
	}
}

void solve_tree(int u)
{
	nowl = tridx + 1;
	nowr = tridx + cv;
	for (int i = nowl; i <= nowr; i++) sgt.update(1, i, i, -sgt.query(1, i, i));
	pre_dfs(u, -1);
	res = sgt.query(1, nowl, nowr);
	//cout << "!!!: " << nowl << " " << nowr << " " << res << "\n";
	//if (u == 2) cout << "yoxi: " << res << " " << nowl << " " << nowr << "\n";
	dfs2(u, -1);
	ans += res;
}

int tc;
int ccnt, c1, c2;

int get_res(int id)
{
	if (ss[id][1] == tt[id][1]) return tt[id][1];
	else if (t[id] >= 2 && ss[id][1] == tt[id][2]) return tt[id][2];
	if (s[id] == 1) return -1;
	else
	{
		if (ss[id][2] == tt[id][1]) return tt[id][1];
		else if (t[id] >= 2 && ss[id][2] == tt[id][2]) return tt[id][2];
		return -1;
	}
}

namespace looped_base_tree
{
	void dfs(int u, int f, int from)
	{
		vis[u] = 1;
		fa[u] = f;
		for (int i = h[u]; ~i; i = ne[i])
		{
			int j = e[i];
			if (i == (from ^ 1)) continue;
			if (vis[j])
			{
				if (dep[u] < dep[j]) continue;
				int now = u;
				if (u == j)
				{
					inc[u] = 1;
					continue;
				}
				do
				{
					inc[now] = 1;
					now = fa[now];
				} while (now != j);
				inc[j] = 1;
			}
			else
			{
				dep[j] = dep[u] + 1;
				dfs(j, u, i);
			}
		}
	}
	void dfs2(int u)
	{
		vis[u] = 1;
		for (int i = h[u]; ~i; i = ne[i])
		{
			int j = e[i];
			int id = gg[i];
			if (!flag[i] && !flag[i ^ 1])
			{
				flag[i] = flag[i ^ 1] = 1;
				if (inc[u] && inc[j])
				{
					if (s[id] == 2 && t[id] == 2 && cmp(id))
					{
						ccnt++;
					}
					else
					{
						int g = get_res(id);
						if (~g)
						{
							if (g == u) c1++;
							if (g == j) c2++;
						}
					}
				}
				else
				{
					bool fg = 0;
					for (int k = 1; k <= s[id]; k++)
					{
						if (ss[id][k] == j)
						{
							fg = 1;
							break;
						}
					}
					if (fg) tc++;
				}
			}
			if (!vis[j]) dfs2(j);
		}
	}
}

void solve_looped_base_tree(int u)
{
	tc = c1 = c2 = ccnt = 0;
	for (auto& i : vv) vis[i] = dep[i] = 0;
	looped_base_tree::dfs(cicp, 0, -1);
	for (auto& i : vv) vis[i] = 0;
	looped_base_tree::dfs2(cicp);
	int res = 0;
	for (int i = 0; i <= ccnt; i++) res = max(res, min(c1 + i, c2 + ccnt - i));
	ans += res + tc;
}

int main()
{
	//freopen("C:\\Users\\60215\\Downloads\\Compressed\\game\\game\\game9.in", "r", stdin);
	//freopen("C:\\Users\\60215\\Downloads\\Compressed\\game\\game\\game9.out", "w", stdout);\
	memset(h, -1, sizeof h);
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> T;
	while (T--)
	{
		ans = 0;
		cin >> n >> m;
		sgt.build(1, 1, m);
		for (int i = 1; i <= m; i++) trsz[i] = dep[i] = inc[i] = 0, h[i] = -1, vis[i] = fa[i] = 0;
		for (int i = 0; i <= idx; i++) both[i] = flag[i] = 0;
		idx = tridx = 0;
		res = 0;
		for (int i = 1; i <= n; i++)
		{
			cin >> s[i];
			for (int j = 1; j <= s[i]; j++) cin >> ss[i][j];
		}
		for (int i = 1; i <= n; i++)
		{
			cin >> t[i];
			for (int j = 1; j <= t[i]; j++) cin >> tt[i][j];
			if (t[i] == 1) add(tt[i][1], tt[i][1], i), add(tt[i][1], tt[i][1], i);
			else add(tt[i][1], tt[i][2], i), add(tt[i][2], tt[i][1], i);
		}
		for (int i = 1; i <= m; i++)
		{
			if (vis[i]) continue;
			cv = ce = cir = 0;
			vv.clear(), vv.shrink_to_fit();
			dep[i] = 0;
			cicp = -1;
			dfs(i, -1);
			if (ce > cv)
			{
				cout << "-1\n";
				goto E;
			}
			if (ce == cv - 1)
			{
				solve_tree(i);
			}
			else
			{
				solve_looped_base_tree(i);
			}
		}
		cout << ans << "\n";
	E:;
	}
	return 0;
}
posted @   HappyBobb  阅读(5)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示