[SHOI2008]cactus仙人掌图 (tarjan + dp)

Description
如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人图(cactus)。所谓简单回路就是指在图上不重复经过任何一个顶点的回路。  举例来说,上面的第一个例子是一张仙人图,而第二个不是——注意到它有三条简单回路:(4,3,2,1,6,5,4)、(7,8,9,10,2,3,7)以及(4,3,7,8,9,10,2,1,6,5,4),而(2,3)同时出现在前两个的简单回路里。另外,第三张图也不是仙人图,因为它并不是连通图。 显然,仙人图上的每条边,或者是这张仙人图的桥(bridge),或者在且仅在一个简单回路里,两者必居其一。定义在图上两点之间的距离为这两点之间最短路径的距离。定义一个图的直径为这张图相距最远的两个点的距离。现在我们假定仙人图的每条边的权值都是1,你的任务是求出给定的仙人图的直径。

早就开始做了。。。最近 czm 提起才想到。。。我觉得这是一道很好的题目。。。

首先求点双连通分量,然后对于每一个环 dp,可能的答案为 max{dis[i] + dis'[j], dis[x] + dist(x, y) + dis[y]}, dis 表示搜索树向下的最长路,dis‘表示第二长路(临时变量记之即可),dist(x, y) 表示仙人掌环上两点间的最短距离。

dp 就单(Ou)调(Shao)队列水过了。。。tarjan 什么的也就水过了。。。

然后爆栈什么的调了我一两个小时。。。最后删掉 dfs 中 3 个临时变量才过。。。

bzoj rank 10,貌似不是很颓。。。

Code :

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <map>
#include <set>
#include <queue>
#include <algorithm>
using namespace std;
#define FOR(i, j, k) for (i = (j); i <= (k); ++ i)
#define ROF(i, j, k) for (i = (j); i >= (k); -- i)
#define FER(i, j, k) for (i = j[k]; i; i = i->n)
#define maxn 50005

struct da{int t; da * n;};
da das[maxn * 4], * adj = das + 1, * edge[maxn];
int n, m, dfnn, tot, ans, i, j, k;
int dfn[maxn], low[maxn], dis[maxn];
da * sta[maxn * 2], ** top = sta;
int a[maxn * 2], q[maxn * 2], h, t;

void up(int & i, int j) {if (j > i) i = j;}
void down(int & i, int j) {if (j < i) i = j;}

void solve(int k)
{
	int i; q[h = t = 1] = 1;
	FOR (i, 2, tot)
		{
			while (i - q[h] > k) ++ h;
			up(ans, dis[a[q[h]]] - q[h] + dis[a[i]] + i);
			while (h <= t && dis[a[i]] - i > dis[a[q[t]]] - q[t]) -- t;
			q[++ t] = i;
		}
}

void dfs(int u, int fa)
{
	da * e; int l = 0;
	dfn[u] = low[u] = ++ dfnn;
	FER (e, edge, u) if (e->t != fa)
		if (dfn[e->t]) down(low[u], dfn[e->t]);
		else
			{
				* (++ top) = e, dfs(e->t, u), down(low[u], low[e->t]);
				if (low[e->t] > dfn[u])
					{
						-- top;
						if (dis[e->t] + 1 > dis[u])
							l = dis[u], dis[u] = dis[e->t] + 1;
						else if (dis[e->t] + 1 > l)
							l = dis[e->t] + 1;
					}
				else if (low[e->t] == dfn[u])
					{
						tot = j = 0;
						do a[++ tot] = (* top)->t; while (* (top --) != e);
						FOR (i, 1, tot)
							{
								up(j, dis[a[i]] + min(i, tot + 1 - i));
								a[i + tot + 1] = a[i];
							}
						a[++ tot] = u, k = tot >> 1, tot = (tot << 1) - 1;
						solve(k);
						if (j > dis[u]) l = dis[u], dis[u] = j;
						else if (j > l) l = j;
					}
			}
	up(ans, l + dis[u]);
}

void link(int i, int j)
{
	* (++ adj) = (da) {j, edge[i]}, edge[i] = adj;
	* (++ adj) = (da) {i, edge[j]}, edge[j] = adj;
}

int main()
{
	freopen("cactus.in", "r", stdin);
	freopen("cactus.out", "w", stdout);

	int u, v;
	scanf("%d%d", & n, & m);
	FOR (i, 1, m)
		{
			scanf("%d%d", & k, & u);
			FOR (j, 2, k) scanf("%d", & v), link(u, v), u = v;
		}
	dfs(1, 0);
	printf("%d\n", ans);

	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值