【题解】POJ3694

Link \text{Link} Link

题意

给定一张有 n n n 个点和 m m m 条边的无向连通图,有 q q q 次操作,每次往图中添加一条无向边,并询问添加后图中 的数量。

思路

先用求出图中的边双并缩点,顺便求出一开始桥的数量,设 c x c_x cx​ 表示点 x x x​ 所属的边双编号。

对于添加边 ( u , v ) (u,v) (u,v)

  1. c u = c v c_u=c_v cu=cv:添加后桥的数量不变。
  2. c u ≠ c v c_u\ne c_v cu=cv:在缩点后, c u ∼ c v c_u\sim c_v cucv 的路径上的边处在一个环内,都不是桥。求出 LCA ⁡ ( c u , c v ) \operatorname{LCA}(c_u,c_v) LCA(cu,cv),从 c u c_u cu 一直走到 L C A LCA LCA,再从 c v c_v cv​ 一直走到 L C A LCA LCA​,把经过的边标记为非桥边,统计有多少条边被新标记了,则将桥的总数减去被新标记的边的数量即可。

时间复杂度为 O ⁡ ( q n ) \operatorname{O}(qn) O(qn)​​​,因为数据范围较水可以过,但热爱 卡时 思考的我们可以进一步优化。很显然当一条边是非桥边后,如何添加边也不会使得其重新变为桥边,所以被标记后直接并入父节点所在的集合即可,这里直接用并查集维护,时间复杂度为 O ⁡ ( q log ⁡ n ) \operatorname{O}(q\log n) O(qlogn)

Code \text{Code} Code

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 1e5 + 5;
const int MAXM = 2e5 + 5;

int n, m, dcc, ans;

struct node1
{
	int cnt, Time;
	int head[MAXN], dfn[MAXN], low[MAXN], c[MAXN];
	bool bridge[MAXM << 1];
	
	struct edge
	{
		int to, nxt;
	}e[MAXM << 1];
	
	void init()
	{
		cnt = 1;
		Time = dcc = 0;
		memset(head, 0, sizeof(head));
		memset(dfn, 0, sizeof(dfn));
		memset(c, 0, sizeof(c));
		memset(bridge, false, sizeof(bridge));
	}
	
	void add(int u, int v)
	{
		e[++cnt] = edge{v, head[u]};
		head[u] = cnt;
	}
	
	void tarjan(int u, int in_edge) 
	{
		dfn[u] = low[u] = ++Time;
		for (int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if (!dfn[v])
			{
				tarjan(v, i);
				low[u] = min(low[u], low[v]);
				if (dfn[u] < low[v])
				{
					bridge[i] = bridge[i ^ 1] = true;
				}
			}
			else if (i != (in_edge ^ 1))
			{
				low[u] = min(low[u], dfn[v]);
			}
		}
	}
	
	void dfs(int u)
	{
		c[u] = dcc;
		for (int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if (c[v] || bridge[i])
			{
				continue;
			}
			dfs(v);
		}
	}
}old;

struct node2
{
	int cnt;
	int head[MAXN], fa[MAXN][18], dep[MAXN], f[MAXN];
	
	struct edge
	{
		int to, nxt;
	}e[MAXM << 1];
	
	void Init()
	{
		for (int i = 1; i <= dcc; i++)
		{
			f[i] = i;
		}
	}
	
	void init()
	{
		cnt = 0;
		memset(head, 0, sizeof(head));
		memset(dep, 0, sizeof(dep));
		Init();
	}
	
	void add(int u, int v)
	{
		e[++cnt] = edge{v, head[u]};
		head[u] = cnt;
	}
	
	void dfs(int u, int father)
	{
		fa[u][0] = father;
		dep[u] = dep[father] + 1;
		for (int i = 1; i <= 17; i++)
		{
			fa[u][i] = fa[fa[u][i - 1]][i - 1];
		}
		for (int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if (!dep[v])
			{
				dfs(v, u);
			}
		}
	}
	
	int lca(int x, int y)
	{
		if (dep[x] < dep[y])
		{
			swap(x, y);
		}
		for (int i = 17; i >= 0; i--)
		{
			if (dep[fa[x][i]] >= dep[y])
			{
				x = fa[x][i];
			}
		}
		if (x == y)
		{
			return x;
		}
		for (int i = 17; i >= 0; i--)
		{
			if (fa[x][i] != fa[y][i])
			{
				x = fa[x][i], y = fa[y][i];
			}
		}
		return fa[x][0];
	}
	
	int find(int x)
	{
		if (x == f[x])
		{
			return x;
		}
		return f[x] = find(f[x]);
	}
	
	void work(int x, int y)
	{
		x = find(x);
		while (dep[x] > dep[y])
		{
			f[x] = fa[x][0];
			ans--;
			x = find(x);
		}
	}
}now;

int main()
{
	int t = 0;
	while (scanf("%d%d", &n, &m), n + m)
	{
		printf("Case %d:\n", ++t);
		dcc = 0;
		old.init();
		for (int i = 1; i <= m; i++)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			old.add(u, v);
			old.add(v, u);
		}
		for (int i = 1; i <= n; i++)
		{
			if (!old.dfn[i])
			{
				old.tarjan(i, 0);
			}
		}
		for (int i = 1; i <= n; i++)
		{
			if (!old.c[i])
			{
				dcc++;
				old.dfs(i);
			}
		}
		now.init();
		for (int u = 1; u <= n; u++)
		{
			for (int i = old.head[u]; i; i = old.e[i].nxt)
			{
				int v = old.e[i].to;
				if (old.c[u] != old.c[v])
				{
					now.add(old.c[u], old.c[v]);
					now.add(old.c[v], old.c[u]);
				}
			}
		}
		now.dfs(1, 0);
		int q;
		scanf("%d", &q);
		ans = dcc - 1;
		while (q--)
		{
			int a, b;
			scanf("%d%d", &a, &b);
			a = old.c[a], b = old.c[b];
			int c = now.lca(a, b);
			now.work(a, c);
			now.work(b, c);
			printf("%d\n", ans);
		}
		putchar('\n');
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
校园悬赏任务平台对字典管理、论坛管理、任务资讯任务资讯公告管理、接取用户管理、任务管理、任务咨询管理、任务收藏管理、任务评价管理、任务订单管理、发布用户管理、管理员管理等进行集中化处理。经过前面自己查阅的网络知识,加上自己在学校课堂上学习的知识,决定开发系统选择小程序模式这种高效率的模式完成系统功能开发。这种模式让操作员基于浏览器的方式进行网站访问,采用的主流的Java语言这种面向对象的语言进行校园悬赏任务平台程序的开发,在数据库的选择上面,选择功能强大的Mysql数据库进行数据的存放操作。校园悬赏任务平台的开发让用户查看任务信息变得容易,让管理员高效管理任务信息。 校园悬赏任务平台具有管理员角色,用户角色,这几个操作权限。 校园悬赏任务平台针对管理员设置的功能有:添加并管理各种类型信息,管理用户账户信息,管理任务信息,管理任务资讯公告信息等内容。 校园悬赏任务平台针对用户设置的功能有:查看并修改个人信息,查看任务信息,查看任务资讯公告信息等内容。 系统登录功能是程序必不可少的功能,在登录页面必填的数据有两项,一项就是账号,另一项数据就是密码,当管理员正确填写并提交这二者数据之后,管理员就可以进入系统后台功能操作区。项目管理页面提供的功能操作有:查看任务,删除任务操作,新增任务操作,修改任务操作。任务资讯公告信息管理页面提供的功能操作有:新增任务资讯公告,修改任务资讯公告,删除任务资讯公告操作。任务资讯公告类型管理页面显示所有任务资讯公告类型,在此页面既可以让管理员添加新的任务资讯公告信息类型,也能对已有的任务资讯公告类型信息执行编辑更新,失效的任务资讯公告类型信息也能让管理员快速删除。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值