HNOI2006 潘多拉的盒子

题目描述

题解:

题目的描述比较长,理解起来也有一定难度。仔细读题后我们发现整个任务可以分成两个部分:找出咒语机之间所有的升级关系、求最长升级序列。

1、 求升级关系:

容易看出,咒语机i可以抽象成一个图Gi,其顶点集Vini个元件,每个顶点发出两条边——“0”边和“1”边,分别表示将信号加“0”和加“1”。

我们枚举两个咒语机AB(AB),判断B是否是A的升级。最简单的想法是生成出AB的所有咒语源,然后判断前者是否为后者的子集。但是,一个咒语机产生的咒语源可能有无限多个,无法逐一判断。

其实,只要存在一个咒语源,A能够产生而B不能产生,那么这一升级关系就不成立。为了找到(或者证明不存在)这样的一个咒语源,我们构造图H,其顶点是一个二元组(i,j),表示图GA的顶点i和图GB的顶点j。如果图GA的顶点i“c”边到达顶点ic(c=0,1),图GB的顶点j“c”边到达顶点jc,那么从图H的顶点(i,j)连有向边到(ic,jc)。我们将图H中的某些顶点(i,j)称为“关键顶点”,其特点是:图GA的顶点i是输出元而图GB的顶点j不是输出元。存在一个A能够产生而B不能的咒语源,等价于图H中存在从顶点(0,0)到关键顶点的路径。我们只需用广度优先搜索遍历图H即可。

2、 求最长升级序列:

假设第1部分求出的升级关系保存在图G中:如果咒语机BA的升级,那么图G中从AB连一条有向边。最长升级序列在图中对应最长路经。如果G是有向无环图,那么可以用拓扑排序加上动态规划的方法求最长路径。可惜的是,G有可能存在环,并且只有一种可能:存在若干个咒语机,它们产生的咒语源完全相同。显然,在这种情况下,只要选择其中一个,那么与之相同的所有咒语机都可以被选择。因此,我们把图中所有相同的咒语机合并成一个结点,并用num域记录该结点是由多少个结点合并而来(如图1)。

这样,问题转化为在一个有向无环图中求带权最长路经,这同样可以用拓扑排序加上动态规划的方法解决。具体的方法是:首先将图的顶点重新编号使得1,2,…,n是图的拓扑序,然后利用状态转移方程求解即可。

以上两个部分中,第一部分的时间复杂度为O(n2s2),第二部分的时间复杂度为O(s2),所以算法的总时间复杂度为O(n2s2)

代码:

 

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 55;
int s;
struct node
{
    int n,m;
    bool ot[N];
    int ch[N][2];
    void read()
    {
        scanf("%d%d",&n,&m);
        for(int a,i=1;i<=m;i++)
        {
            scanf("%d",&a);
            ot[a]=1;
        }
        for(int i=0;i<n;i++)
        {
            scanf("%d%d",&ch[i][0],&ch[i][1]);
        }
    }
}p[N];
int hed[N],cnt;
struct EG
{
    int fr,to,nxt;
}e[N*N],e0[N*N];
void ae(int f,int t)
{
    e[++cnt].fr = f;
    e[cnt].to = t;
    e[cnt].nxt = hed[f];
    hed[f] = cnt;
}
struct Pair
{
    int x,y;
    Pair(){}
    Pair(int x,int y):x(x),y(y){}
};
bool vs[N][N];
bool check(node &a,node &b)
{
    memset(vs,0,sizeof(vs));
    queue<Pair>q;
    q.push(Pair(a.ch[0][0],b.ch[0][0]));
    q.push(Pair(a.ch[0][1],b.ch[0][1]));
    while(!q.empty())
    {
        Pair u = q.front();
        q.pop();
        if(!a.ot[u.x]&&b.ot[u.y])return 0;
        if(vs[u.x][u.y])continue;
        vs[u.x][u.y]=1;
        q.push(Pair(a.ch[u.x][0],b.ch[u.y][0]));
        q.push(Pair(a.ch[u.x][1],b.ch[u.y][1]));
    }
    return 1;
}
int dep[N],low[N],tot;
bool vis[N];
int bel[N],bc,siz[N],sta[N],tl;
void tarjan(int u)
{
    dep[u]=low[u]=++tot;
    vis[u]=1;
    sta[++tl] = u;
    for(int j=hed[u];j;j=e[j].nxt)
    {
        int to = e[j].to;
        if(!dep[to])
        {
            tarjan(to);
            low[u] = min(low[u],low[to]);
        }else if(vis[to])
        {
            low[u] = min(low[u],dep[to]);
        }
    }
    if(dep[u]==low[u])
    {
        bc++;
        int c = -1;
        while(c!=u)
        {
            c = sta[tl--];
            bel[c]=bc;
            siz[bc]++;
            vis[c] =0;
        }
    }
}
bool eg[N][N];
int Hed[N],Cnt;
void AE(int f,int t)
{
    e0[++Cnt].to = t;
    e0[Cnt].nxt = Hed[f];
    Hed[f] = Cnt;
}
int dp[N];
int dfs(int u)
{
    if(dp[u])return dp[u];
    if(!Hed[u])return dp[u]=siz[u];
    for(int j=Hed[u];j;j=e0[j].nxt)
        dp[u]=max(dp[u],dfs(e0[j].to)+siz[u]);
    return dp[u];
}
int main()
{
//  freopen("pandora.in","r",stdin);
//  freopen("pandora.out","w",stdout);
    scanf("%d",&s);
    for(int i=1;i<=s;i++)p[i].read();
    for(int i=1;i<=s;i++)
        for(int j=1;j<=s;j++)
            if(i!=j&&check(p[i],p[j]))
                    ae(i,j);
    for(int i=1;i<=s;i++)
        if(!dep[i])tarjan(i);
    for(int j=1;j<=cnt;j++)
    {
        int f = bel[e[j].fr],t = bel[e[j].to];
        if(f!=t&&!eg[f][t])eg[f][t]=1,AE(f,t);
    }
    int ans = 0;
    for(int i=1;i<=bc;i++)ans=max(ans,dfs(i));
    printf("%d\n",ans);
    return 0;
}

 

posted @ 2019-01-22 16:57  LiGuanlin  阅读(183)  评论(0编辑  收藏  举报