zoukankan      html  css  js  c++  java
  • 【LOJ523】[LibreOJ β Round #3]绯色 IOI(悬念)(霍尔定理_基环树)

    题目

    LOJ523

    官方题解

    分析

    由于某些原因,以下用「左侧点」和「右侧点」分别代替题目中的「妹子」和「男生」。

    根据题意,显然能得出一个左侧点只能向一个或两个右侧点连边。这似乎启发我们把左侧点不看成点,而看成关于右侧的两个点之间的「关系」(更正:不看题解根本想不到)。换句话说,把左侧点看成边,右侧点看成点,就得到了一张 (n) 个点 (m) 条边的新图。如果一个左侧点向 (a)(b) 两个右侧点连边,就在新图中加入一条连接 ((a,b)) 的边(左侧点只连接一个右侧点就是自环)。

    考虑左侧点必须选且仅选一个右侧点(注意题目中「保证左侧满匹配」),即新图中每条边必须选择它的一个端点;而新图中每个点只能被一条边选择。说得直观一点,就是要给每条边定向,指向哪个点就表示选择了这个点。同时每个点的入度最多为 (1)

    那么问题就转化成了:给定一张无向图,要给每条边选择一个方向,满足每个点入度最多为 (1) 。每条边的两个方向分别有一个权值,要求最大化权值和。

    看起来好像还是不可做?通过膜拜题解, 请再次注意这句话:保证左侧满匹配 。根据 霍尔定理 (证明先咕着),左侧满匹配当且仅当在左侧选出任意一个大小为 (k(0leq kleq m)) 的子集,与这个子集有边相连的右侧点至少有 (k) 个。放在新图上,任意选出 (k) 条边所对应的点集的大小不少于 (k)

    这说明什么呢?考虑对于新图中的一个连通分量(即极大连通块),它的点数必须大于等于边数。而既然连通,点数最多是边数加 (1) 。因此一个连通分量的边数要么比点数少 (1) ,要么等于点数。即:要么是树,要么是基环树。这个图竟然有如此优美的性质。

    基环树必然定向为基环外向树。对于基环树中的边,如果是树边,那么一定往远离环的方向连。而所有环边都必须连成一个方向(即要么全部顺时针,要么全部逆时针)。因此每个基环树只有两种方案:要么所有顺时针的环边加上外向树边,要么所有逆时针环边加上外向树边。维护这两种情况的答案即可。修改时如果是内向树边就不理(必不选),外向树边直接给最终答案加上差值(必选),环边就更新对应方向的答案。

    树会定向成以一个点为根的有向树,因此要分别维护以每个点为根的答案。先随便以一个点为根求出 dfs 序,然后考虑每条边的贡献:如果一条边是从上往下的,那么当根不在 下端点 的子树内时会贡献答案;如果一条边是从下往上的,那么当根在下端点的子树内时会贡献答案。在线段树上区间修改并维护最大值即可。

    代码

    众所周知,基环树的题嘴起来容易(虽然这个题也不容易),写起来恶心。所以耐心地去写和调吧。

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <cctype>
    #include <vector>
    using namespace std;
    
    namespace zyt
    {
    	template<typename T>
    	inline bool read(T &x)
    	{
    		char c;
    		bool f = false;
    		x = 0;
    		do
    			c = getchar();
    		while (c != EOF && c != '-' && !isdigit(c));
    		if (c == EOF)
    			return false;
    		if (c == '-')
    			f = true, c = getchar();
    		do
    			x = x * 10 + c - '0', c = getchar();
    		while (isdigit(c));
    		if (f)
    			x = -x;
    		return true;
    	}
    	template<typename T>
    	inline void write(T x)
    	{
    		static char buf[20];
    		char *pos = buf;
    		if (x < 0)
    			putchar('-'), x = -x;
    		do
    			*pos++ = x % 10 + '0';
    		while (x /= 10);
    		while (pos > buf)
    			putchar(*--pos);
    	}
    	typedef pair<int, int> pii;
    	const int N = 5e5 + 10;
    	vector<pii> g[N];
    	int m, n, T, a[N], b[N], dfn[N], fa[N], out[N], dfncnt, ecnt, v[N << 1], val[N][2], rot[N], opp[N << 1];
    	bool iscir[N], incir[N], vis[N], dir[N << 1];
    	pii e[N << 1];
    	int f(const int x)
    	{
    		return x == fa[x] ? x : fa[x] = f(fa[x]);
    	}
    	void dfs(const int u, const int r)
    	{
    		vis[u] = true;
    		dfn[u] = ++dfncnt;
    		rot[u] = r;
    		for (vector<pii>::iterator it = g[u].begin(); it != g[u].end(); it++)
    		{
    			int v = it->first;
    			if (vis[v] || incir[v])
    				continue;
    			dfs(v, r);
    		}
    		out[u] = dfncnt;
    	}
    	int findcir(const int u, const int from)
    	{
    		static bool insta[N];
    		if (insta[u])
    			return u;
    		insta[u] = true;
    		for (vector<pii>::iterator it = g[u].begin(); it != g[u].end(); it++)
    		{
    			int v = it->first, tmp;
    			if (opp[it->second] == from)
    				continue;
    			if (~(tmp = findcir(v, it->second)))
    			{
    				insta[u] = false;
    				val[f(u)][0] += ::zyt::v[it->second];
    				val[f(u)][1] += ::zyt::v[opp[it->second]];
    				dir[it->second] = 0, dir[opp[it->second]] = 1;
    				incir[u] = true;
    				if (u != tmp)
    					return tmp;
    				else
    					return -1;
    			}
    		}
    		insta[u] = false;
    		return -1;
    	}
    	namespace Segment_Tree
    	{
    		struct node
    		{
    			int mx, tag;
    		}tree[N << 2];
    		void add(const int rot, const int x)
    		{
    			tree[rot].mx += x, tree[rot].tag += x;
    		}
    		void pushdown(const int rot)
    		{
    			if (tree[rot].tag)
    			{
    				add(rot << 1, tree[rot].tag);
    				add(rot << 1 | 1, tree[rot].tag);
    				tree[rot].tag = 0;
    			}
    		}
    		void update(const int rot)
    		{
    			tree[rot].mx = max(tree[rot << 1].mx, tree[rot << 1 | 1].mx);
    		}
    		void add(const int rot, const int lt, const int rt, const int ls, const int rs, const int x)
    		{
    			if (ls <= lt && rt <= rs)
    				return void(add(rot, x));
    			int mid = (lt + rt) >> 1;
    			pushdown(rot);
    			if (ls <= mid)
    				add(rot << 1, lt, mid, ls, rs, x);
    			if (rs > mid)
    				add(rot << 1 | 1, mid + 1, rt, ls, rs, x);
    			update(rot);
    		}
    		int query(const int rot, const int lt, const int rt, const int ls, const int rs)
    		{
    			if (ls <= lt && rt <= rs)
    				return tree[rot].mx;
    			int mid = (lt + rt) >> 1;
    			pushdown(rot);
    			if (rs <= mid)
    				return query(rot << 1, lt, mid, ls, rs);
    			else if (ls > mid)
    				return query(rot << 1 | 1, mid + 1, rt, ls, rs);
    			else
    				return max(query(rot << 1, lt, mid, ls, rs), 
    							query(rot << 1 | 1, mid + 1, rt, ls, rs));
    		}
    	}
    	int work()
    	{
    		using namespace Segment_Tree;
    		read(m), read(n), read(T);
    		for (int i = 0; i < n; i++)
    			fa[i] = i;
    		for (int i = 0; i < m; i++)
    			read(a[i]);
    		for (int i = 0; i < m; i++)
    			read(b[i]);
    		for (int i = 0; i < m; i++)
    		{
    			int x, y;
    			x = (a[i] - b[i] + n) % n;
    			y = (a[i] + b[i]) % n;
    			if (x < y)
    				swap(x, y);
    			read(v[ecnt]);
    			e[ecnt] = pii(x, y);
    			//fprintf(stderr, "%d %d %d
    ", x, y, v[ecnt]);
    			g[x].push_back(pii(y, ecnt++));
    			if (x != y)
    			{
    				read(v[ecnt]);
    				e[ecnt] = pii(y, x);
    				opp[ecnt - 1] = ecnt, opp[ecnt] = ecnt - 1;
    				//fprintf(stderr, "%d %d %d
    ", y, x, v[ecnt]);
    				g[y].push_back(pii(x, ecnt++));
    			}
    			else
    				opp[ecnt - 1] = ecnt - 1;
    			if (f(x) == f(y))
    				iscir[f(x)] = true;
    			else
    			{
    				iscir[f(y)] |= iscir[f(x)];
    				fa[f(x)] = f(y);
    			}
    		}
    		for (int i = 0; i < n; i++)
    			if (i == f(i) && iscir[i])
    				findcir(i, -1);
    		for (int i = 0; i < n; i++)
    			if ((!iscir[f(i)] && i == f(i)) || incir[i])
    				dfs(i, i);
    		for (int i = 0; i < ecnt; i++)
    		{
    			if (iscir[f(e[i].first)])
    			{
    				if (!incir[e[i].first] || !incir[e[i].second])
    				{
    					if (dfn[e[i].first] < dfn[e[i].second])
    						val[f(e[i].first)][0] += v[i], val[f(e[i].first)][1] += v[i];
    				}
    			}
    			else
    			{
    				if (dfn[e[i].first] < dfn[e[i].second])
    				{
    					int r = rot[e[i].first];
    					add(1, 1, n, dfn[r], dfn[e[i].second] - 1, v[i]);
    					if (out[e[i].second] < out[r])
    						add(1, 1, n, out[e[i].second] + 1, out[r], v[i]);
    				}
    				else
    					add(1, 1, n, dfn[e[i].first], out[e[i].first], v[i]);
    			}
    		}
    		int q, ans = 0;
    		for (int i = 0; i < n; i++)
    			if (i == f(i))
    			{
    				if (iscir[i])
    					ans += max(val[i][0], val[i][1]);
    				else
    					ans += query(1, 1, n, dfn[rot[i]], out[rot[i]]);
    			}
    		write(ans), putchar('
    ');
    		read(q);
    		while (q--)
    		{
    			int x, vv;
    			read(x), read(vv);
    			x -= ans * T, vv -= ans * T;
    			--x;
    			if (iscir[f(e[x].first)])
    			{
    				int t = f(e[x].first);
    				ans -= max(val[t][0], val[t][1]);
    				if (!incir[e[x].first] || !incir[e[x].second])
    				{
    					if (dfn[e[x].first] < dfn[e[x].second])
    						val[t][0] += vv - v[x], val[t][1] += vv - v[x];
    				}
    				else
    				{
    					val[t][dir[x]] += vv - v[x];
    					if (e[x].first == e[x].second)
    						val[t][dir[x] ^ 1] += vv - v[x];
    				}
    				ans += max(val[t][0], val[t][1]);
    			}
    			else
    			{
    				int r = rot[e[x].first];
    				ans -= query(1, 1, n, dfn[r], out[r]);
    				if (dfn[e[x].first] < dfn[e[x].second])
    				{
    					add(1, 1, n, dfn[r], dfn[e[x].second] - 1, vv - v[x]);
    					add(1, 1, n, out[e[x].second] + 1, out[r], vv - v[x]);
    				}
    				else
    					add(1, 1, n, dfn[e[x].first], out[e[x].first], vv - v[x]);
    				ans += query(1, 1, n, dfn[r], out[r]);
    			}
    			v[x] = vv;
    			write(ans), putchar('
    ');
    		}
    		return 0;
    	}
    }
    int main()
    {
    	return zyt::work();
    }
    
  • 相关阅读:
    Python标准模块--concurrent.futures 进程池线程池终极用法
    线程,线程池
    常用英文单词
    进程池
    三种方法实现 生产者消费者模型
    进程间通信IPC -- 管道, 队列
    jquery 和 css 属性
    javascript的 Object 和 Function
    nodejs 返回html页面--使用 ejs 模板
    jquery属性
  • 原文地址:https://www.cnblogs.com/zyt1253679098/p/12010138.html
Copyright © 2011-2022 走看看