P2321 [HNOI2006]潘多拉的宝盒 题解

思路

我们要先找到所有的升级序列,找出所有的包含与被包含的关系,

然后找到一条最长链,满足题目要求。

  1. 如何找到所有的包含关系?

    遍历每一对盒子,对于二者,我们用 dfs 扫一遍,如果有一个盒子在一个节点输出了串,而另一个没有,那么这两者就不存在包含关系。

    反之,两者就存在包含关系。我们就可以建一条有向边,从包含者指向被包含者。

  2. 为什么求最长链?

    易得,在一个强连通分量中,肯定是存在一条链的。

    所以,我们只需要把一个个强连通分量缩点,然后找到一条把超级点(强连通分量)串起来的最长的链即可。

    最终答案,就是最长链的长度。

  3. 怎么找最长链?

    还是用 dfs,我们分别从每一个节点开始遍历,以当前节点当作当前链的链顶,

    然后用前向星搜下去,每次返回以该点作为链顶时最长链的长度。

代码实现

  1. 初始化:

    有两个细节一定要注意:

    1. 一定要开 long long

    2. 用前向星的话存边数组至少开到 400。

  2. 输入:

    我们开个结构体存一下输入条件。

    注意输入的点的编号要加一(题目编号从 0 开始)。

void input ()
{
	scanf ("%lld", &s);
	for (int i = 1; i <= s; i++)
	{
		scanf ("%lld %lld", &n, &m);
		for (int j = 1; j <= m; j++)
		{
			int t;
			scanf ("%lld", &t);
			t++;
			a[i].out[t] = 1;
		}
		for (int l = 1; l <= n; l++)
		{
			int aa, bb;
			scanf ("%lld %lld", &aa, &bb);
			aa++, bb++;
			a[i].lin[l][0] = aa, a[i].lin[l][1] = bb;
		}
	}
}
  1. 建边:

    每次对于任意两个宝盒,我们同时从 1 开始出发(题目已知条件),如果他们之间存在包含关系,

    证明他们两者间必定有完全相同的 01 串。

    所以,如果当我们访问到一个节点时,一个宝盒中的是输出元,而另一个不是,

    就证明这两个间不存在包含关系。

    反之,即存在,我们就可以在他们之间建一条有向边,指向他们之间的包含关系。

    记得要用数组储存一下每一条边的起点和终点,方便后面缩点,重新建图。

void find (int x, int y, int nx, int ny)
{
	if (a[x].out[nx] == 1 and a[y].out[ny] == 0) 
	{
		flag = 1;
		return;
	}
	if (vis[nx][ny]) return;
	vis[nx][ny] = 1;
	find (x, y, a[x].lin[nx][0], a[y].lin[ny][0]);
	find (x, y, a[x].lin[nx][1], a[y].lin[ny][1]);
}

void build ()
{
	for (int i = 1; i <= s; i++)
	{
		for (int j = 1; j <= s; j++)
		{
			if (i == j) continue;
			flag = 0, memset (vis, 0, sizeof vis);
			find (i, j, 1, 1);
			if (!flag) add (i, j), u[cnt] = i, v[cnt] = j;
		}	
	}
}
  1. 缩点:

    按照之前思路所说的我们要缩点,以找最长链。

    这里缩点就不多说了,具体可以见我的博客缩点(算法笔记)

void tarjan (int u)
{
	dfn[u] = low[u] = ++tmp;
	st[++top] = u;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan (v);
			low[u] = min (low[u], low[v]);
		}
		else if (!co[v]) low[u] = min (low[u], dfn[v]);
	}
	if (dfn[u] == low[u])
	{
		co[u] = ++col;
		siz[col] = 1;
		while (st[top] != u)
		{
			co[st[top]] = col;
			siz[col]++;
			top--;
		}
		top--;
	}
}

void rebuild ()
{
	for (int i = 1; i <= s; i++) if (!dfn[i]) tarjan (i);
	int recoc = cnt;
	cnt = 0;
	memset (hd, 0, sizeof hd);
	memset (e, 0, sizeof e);
	for (int i = 1; i <= recoc; i++)
	{
		if (co[u[i]] == co[v[i]]) continue;
		add (co[u[i]], co[v[i]]);
	}
}
  1. 输出答案:

    这时候我们就可以直接记忆化搜索最长链了,最后边搜边记录最长链长度即可。

int get (int u)
{
	if (ans[u]) return ans[u];
	ans[u] = siz[u];
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		ans[u] = max (ans[u], get (v) + siz[u]);
	}
	return ans[u];
}

void output ()
{
	int anss = 0;
	for (int i = 1; i <= col; i++) if (!ans[i]) anss = max (anss, get (i));
	printf ("%lld\n", anss);
}

完整代码

#include<bits/stdc++.h>
using namespace std;

#define int long long
int s, n, m;
const int maxn = 205;
int cnt, hd[maxn];
struct node{
	int to, nxt;
}e[305];
struct node2{
	int lin[maxn][5], out[maxn];
}a[maxn];
int dfn[maxn], low[maxn], siz[maxn], co[maxn], st[maxn];
int tmp, top, col;
int flag, vis[maxn][maxn];
int u[305], v[305], ans[maxn];

void add (int u, int v)
{
	e[++cnt].to = v;
	e[cnt].nxt = hd[u];
	hd[u] = cnt;
}

void input ()
{
	scanf ("%lld", &s);
	for (int i = 1; i <= s; i++)
	{
		scanf ("%lld %lld", &n, &m);
		for (int j = 1; j <= m; j++)
		{
			int t;
			scanf ("%lld", &t);
			t++;
			a[i].out[t] = 1;
		}
		for (int l = 1; l <= n; l++)
		{
			int aa, bb;
			scanf ("%lld %lld", &aa, &bb);
			aa++, bb++;
			a[i].lin[l][0] = aa, a[i].lin[l][1] = bb;
		}
	}
}

void find (int x, int y, int nx, int ny)
{
	if (a[x].out[nx] == 1 and a[y].out[ny] == 0) 
	{
		flag = 1;
		return;
	}
	if (vis[nx][ny]) return;
	vis[nx][ny] = 1;
	find (x, y, a[x].lin[nx][0], a[y].lin[ny][0]);
	find (x, y, a[x].lin[nx][1], a[y].lin[ny][1]);
}

void build ()
{
	for (int i = 1; i <= s; i++)
	{
		for (int j = 1; j <= s; j++)
		{
			if (i == j) continue;
			flag = 0, memset (vis, 0, sizeof vis);
			find (i, j, 1, 1);
			if (!flag) add (i, j), u[cnt] = i, v[cnt] = j;
		}	
	}
}

void tarjan (int u)
{
	dfn[u] = low[u] = ++tmp;
	st[++top] = u;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan (v);
			low[u] = min (low[u], low[v]);
		}
		else if (!co[v]) low[u] = min (low[u], dfn[v]);
	}
	if (dfn[u] == low[u])
	{
		co[u] = ++col;
		siz[col] = 1;
		while (st[top] != u)
		{
			co[st[top]] = col;
			siz[col]++;
			top--;
		}
		top--;
	}
}

void rebuild ()
{
	for (int i = 1; i <= s; i++) if (!dfn[i]) tarjan (i);
	int recoc = cnt;
	cnt = 0;
	memset (hd, 0, sizeof hd);
	memset (e, 0, sizeof e);
	for (int i = 1; i <= recoc; i++)
	{
		if (co[u[i]] == co[v[i]]) continue;
		add (co[u[i]], co[v[i]]);
	}
}

int get (int u)
{
	if (ans[u]) return ans[u];
	ans[u] = siz[u];
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		ans[u] = max (ans[u], get (v) + siz[u]);
	}
	return ans[u];
}

void output ()
{
	int anss = 0;
	for (int i = 1; i <= col; i++) if (!ans[i]) anss = max (anss, get (i));
	printf ("%lld\n", anss);
}

signed main ()
{
	input ();
	build ();
	rebuild ();
	output ();
	return 0;
}

如有问题,求大佬斧正,感谢。

posted @ 2022-03-25 07:25  pldzy  阅读(49)  评论(0)    收藏  举报