P2321 [HNOI2006]潘多拉的宝盒 题解
思路
我们要先找到所有的升级序列,找出所有的包含与被包含的关系,
然后找到一条最长链,满足题目要求。
-
如何找到所有的包含关系?
遍历每一对盒子,对于二者,我们用 dfs 扫一遍,如果有一个盒子在一个节点输出了串,而另一个没有,那么这两者就不存在包含关系。
反之,两者就存在包含关系。我们就可以建一条有向边,从包含者指向被包含者。
-
为什么求最长链?
易得,在一个强连通分量中,肯定是存在一条链的。
所以,我们只需要把一个个强连通分量缩点,然后找到一条把超级点(强连通分量)串起来的最长的链即可。
最终答案,就是最长链的长度。
-
怎么找最长链?
还是用 dfs,我们分别从每一个节点开始遍历,以当前节点当作当前链的链顶,
然后用前向星搜下去,每次返回以该点作为链顶时最长链的长度。
代码实现
-
初始化:
有两个细节一定要注意:
-
一定要开
long long; -
用前向星的话存边数组至少开到 400。
-
-
输入:
我们开个结构体存一下输入条件。
注意输入的点的编号要加一(题目编号从 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 开始出发(题目已知条件),如果他们之间存在包含关系,
证明他们两者间必定有完全相同的 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;
}
}
}
-
缩点:
按照之前思路所说的我们要缩点,以找最长链。
这里缩点就不多说了,具体可以见我的博客缩点(算法笔记)。
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);
}
完整代码
#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;
}
如有问题,求大佬斧正,感谢。

浙公网安备 33010602011771号